c# 完成恩尼格瑪加密擴展
- 恩尼格瑪
- 擴展為可見字符
- 恩尼格瑪的設備
- 原始字符順序
- 轉子的設置
- 反射器的設置
- 連接板的設置
- 初始數據的設置
- 第一版 C# 代碼
- 第二版 C# 代碼
- 總結
恩尼格瑪
在之前,我們使用 python 實現了一版恩尼格瑪的加密算法,但是這一版,轉子的字符僅僅只支持26個字母,且無大小寫的區分,所以適用范圍就相當有限了。
具體的恩尼格瑪的說明,可以參考文章:https://blog.csdn.net/weixin_30807779/article/details/98515455
具體來說,恩尼格瑪實現了加密和解密使用的是同一套算法,關鍵就在于有一個反射器,在使用相同轉子的情況下,按照相同的順序輸入加密前和加密后的字符就可以得到互換后的字符,且一一對應。
CSDN 文盲老顧的博客,https://blog.csdn.net/superwfei
老顧的個人社區,https://bbs.csdn.net/forums/bfba6c5031e64c13aa7c60eebe858a5f?category=10003&typeId=3364713
擴展為可見字符
為了使恩尼格瑪的算法適用范圍更廣,我們需要將所有的可見 ASCII 碼都加入到編碼之中。
恩尼格瑪的設備
原始字符順序
這個字符順序,算是一個基本設置,畢竟 ASCII 從 32 到 127 的順序還是有點不太習慣,當然,按照這個順序也沒有問題,也可以自己定義字符順序
// abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()-_=+[{]}\|;:'",./<>?
轉子的設置
對于恩尼格瑪的轉子來說,其實就是原始字符串中,第一個字符對應打亂后轉子字符里的第一個字符,第二個字符對應第二個字符。
如果是多個轉子,需要注意,不是第一個轉子的第一個字符對應第二個轉子的字符,而是原始字符的第一個字符,對應第二個轉子的第一個字符哦。
反射器的設置
反射器,是一個比較特殊的路徑,它是將所有字符兩兩對應的關系,這個反射器,我們也可以用字符串來進行描述,比如第一個字符對應最后一個字符,如果是雙數數量的字符就沒什么毛病,如果是單數數量的字符,那么中間的字符對應它自身即可。
連接板的設置
同理,連接板和反射板差不多,這里使用的是和反射器一樣的設置,即字符兩兩對應,如果想增加復雜度,也可以和轉子一樣,不按照兩兩對應的方式,這個之后我們再討論
初始數據的設置
那么,對于恩尼格瑪的算法來說,我們需要一個初始字符串,然后一個反射板,一個連接板,至少一個轉子這樣的數據。
那么,我們就只需要一個原始字符串,然后將原始字符串隨機打亂至少3次,每次打亂的數據作為原始數據存放到一個數組中。
這個數組至少是四個元素,第一個元素,就是原始字符串,第二個元素作為連接板的數據,第三個元素作為反射器的設置,第四個及之后的所有元素,作為轉子的數據即可。
第一版 C# 代碼
那么我們的第一版代碼就可以根據之前我們的 py 代碼進行構建了
public static string Enigma(string keyword, string[] EnigmaRotors){string result = string.Empty;int l = EnigmaRotors.Length;int cl = EnigmaRotors[1].Length;if (l < 4){return keyword;}Hashtable link = new Hashtable();Hashtable reverser = new Hashtable();for (int i = 0; i < cl; i++){link[EnigmaRotors[1].Substring(i, 1)] = EnigmaRotors[1].Substring(cl - i - 1, 1);}for (int i = 0; i < cl; i++){reverser[EnigmaRotors[2].Substring(i, 1)] = EnigmaRotors[2].Substring(cl - i - 1, 1);}for (int i = 0; i < keyword.Length; i++){List<string> lst = new List<string>();for (int j = 0; j < EnigmaRotors.Length - 3; j++){lst.Add(EnigmaRotors[3 + j]);int t = (int)Math.Floor(i / Math.Pow(cl, j)) % cl;if (t > 0){// 正轉lst[lst.Count - 1] = lst[lst.Count - 1].Substring(t) + lst[lst.Count - 1].Substring(0, t);// 反轉//lst[lst.Count - 1] = lst[lst.Count - 1].Substring(cl - t) + lst[lst.Count - 1].Substring(0, cl - t);}}string r = keyword.Substring(i, 1);r = link[r].ToString();for (int j = 0; j < lst.Count; j++){string rotor = lst[j];r = rotor.Substring(EnigmaRotors[0].IndexOf(r), 1);}r = reverser[r].ToString();for (int j = 0; j < lst.Count; j++){string rotor = lst[lst.Count - j - 1];r = EnigmaRotors[0].Substring(rotor.IndexOf(r), 1);}r = link[r].ToString();result += r;}return result;}
在代碼中,我們對于原始數據 EnigmaRotors 的長度進行了驗證,當該數據至少有四個元素的時候,才進行加密算法。當然,我這里沒有對每個元素的字符是否一致進行驗證,其實基本上也不太需要。
其中,在方法中,link 就是連接板,reverser 就是反射器,我們使用 Hashtable 來代替字典。
int t = (int)Math.Floor(i / Math.Pow(cl, j)) % cl;
這一行來計算當前輸入字符的順序,是否需要對每個轉子進行轉動。
string r = keyword.Substring(i, 1);
而循環中,這行代碼之前,為轉子確定狀態
之后,則是按照連接板,轉子,反射器,轉子,連接板的順序,對字符進行替換的過程了。
不過,這一版需要傳輸一個非常大的 string[] 對象作為參數,實在是有點不太友好。
第二版 C# 代碼
public static string Enigma(string keyword, string chars,int seed,int len = 1)
在這一版,我們可以不再輸入一個長字符串數組當做參數了
我們只需要將原始字符串作為參數
然后給出一個隨機函數的種子,用來限定每次使用隨機函數打亂字符串的時候,可以得出相同的結果
最后,給出一個轉子的數量,最低是1
這個版本,與上一版的區別就在于,EnigmaRotors 的數據,是由隨機種子,原始字符串和轉子數量來自動生成的。
即,我們使用一個 List 來存放臨時數據,最后將該數據的 ToArray() 結果保存為 EnigmaRotors 即可。
Random rnd = new Random(seed);
List<string> lst = new List<string>();
// 原始字符串
lst.Add(chars);
// 連接版
lst.Add(Shuffle(rnd,chars));
// 反射器
lst.Add(Shuffle(rnd,chars));
for (int i=0;i<len;i++){// 增加轉子lst.Add(Shuffle(rnd,chars));
}
EnigmaRotors = lst.ToArray();
以上代碼,插入到第一版的代碼中函數定義的開始部分即可。后續代碼與第一版并無區別。
然后,我們還需要實現一個洗牌的函數 Shuffle,具體該怎么打亂字符串,自己簡單實現一下即可
public string Shuffle(Random rnd,string chars){List<byte> result = new List<byte>();List<byte> lst = new List<byte>();lst.AddRange(Encoding.UTF8.GetBytes(chars));while (lst.Count > 0){int n = rnd.Next(lst.Count);result.Add(lst[n]);lst.RemoveAt(n);}return Encoding.UTF8.GetString(result);
}
總結
由于 c# 沒有 python 那么多的語法糖,所以很多內容,需要自己靠循環一點點來實現,也因為 c# 沒有那么多第三方包,類似洗牌,打亂字符串這種方法,也得自己來實現。
但其實這些內容都是基本內容,自己實現起來,也沒有那么復雜。
總的來說,用 c# 來實現恩尼格瑪加密,也是一件比較簡單的事情。