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 的顺序还是有点不太习惯,当然,按照这个顺序也没有问题,也可以自己定义字符顺序
C#
// abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()-_=+[{]}\|;:'",./<>?
转子的设置
对于恩尼格玛的转子来说,其实就是原始字符串中,第一个字符对应打乱后转子字符里的第一个字符,第二个字符对应第二个字符。
如果是多个转子,需要注意,不是第一个转子的第一个字符对应第二个转子的字符,而是原始字符的第一个字符,对应第二个转子的第一个字符哦。
反射器的设置
反射器,是一个比较特殊的路径,它是将所有字符两两对应的关系,这个反射器,我们也可以用字符串来进行描述,比如第一个字符对应最后一个字符,如果是双数数量的字符就没什么毛病,如果是单数数量的字符,那么中间的字符对应它自身即可。
连接板的设置
同理,连接板和反射板差不多,这里使用的是和反射器一样的设置,即字符两两对应,如果想增加复杂度,也可以和转子一样,不按照两两对应的方式,这个之后我们再讨论
初始数据的设置
那么,对于恩尼格玛的算法来说,我们需要一个初始字符串,然后一个反射板,一个连接板,至少一个转子这样的数据。
那么,我们就只需要一个原始字符串,然后将原始字符串随机打乱至少3次,每次打乱的数据作为原始数据存放到一个数组中。
这个数组至少是四个元素,第一个元素,就是原始字符串,第二个元素作为连接板的数据,第三个元素作为反射器的设置,第四个及之后的所有元素,作为转子的数据即可。
第一版 C# 代码
那么我们的第一版代码就可以根据之前我们的 py 代码进行构建了
C#
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 来代替字典。
c#
int t = (int)Math.Floor(i / Math.Pow(cl, j)) % cl;
这一行来计算当前输入字符的顺序,是否需要对每个转子进行转动。
c#
string r = keyword.Substring(i, 1);
而循环中,这行代码之前,为转子确定状态
之后,则是按照连接板,转子,反射器,转子,连接板的顺序,对字符进行替换的过程了。
不过,这一版需要传输一个非常大的 string[] 对象作为参数,实在是有点不太友好。
第二版 C# 代码
c#
public static string Enigma(string keyword, string chars,int seed,int len = 1)
在这一版,我们可以不再输入一个长字符串数组当做参数了
我们只需要将原始字符串作为参数
然后给出一个随机函数的种子,用来限定每次使用随机函数打乱字符串的时候,可以得出相同的结果
最后,给出一个转子的数量,最低是1
这个版本,与上一版的区别就在于,EnigmaRotors 的数据,是由随机种子,原始字符串和转子数量来自动生成的。
即,我们使用一个 List 来存放临时数据,最后将该数据的 ToArray() 结果保存为 EnigmaRotors 即可。
c#
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,具体该怎么打乱字符串,自己简单实现一下即可
c#
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# 来实现恩尼格玛加密,也是一件比较简单的事情。