利用计算机模拟实现 Enigma 加密机(C语言版)
该文章根据 CC-BY-4.0 协议发表,转载请遵循该协议。
本文地址:https://fenying.net/post/2014/07/30/simulate-enigma-by-c-language/
Enigma,二战时期与德国战车捆绑的顶级加密机,它是世界上首台具有比较强悍的加密算法的加密机器,它彻底淘汰了手功加密。而为了破解它,英国政府几乎倾家荡产。(可阅读《密码传奇》)
现在我们来用计算机模拟一个使用 Enigma 加密算法的加密器,这个加密器使用 C 语言实现,在 Visual Studio 2013 + Windows 8.1 英文企业版下测试通过。(文末会附上代码)
如果您不了解 Enigma 的机器结构,点击 http://zh.wikipedia.org/wiki/Enigma,您必须了解其结构才能理解接下来的加密算法。这里实现的加密方式是,以字节为单位进行加密。
(3个转轮的)Enigma 的加密流程是:
1输入字符 → 输入轮(一个简单的单表加密,这里简化掉了) → 转轮A → 转轮B → 转轮C → 反射板 → 转轮C → 转轮B → 转轮A → 显示
1、数据结构
Enigma 的核心是 转轮(Wheel) 和反射板(ReflectBoard)。每个转轮就是一个会转动的1对1映射表,而反射板则是具有 a→b→a 关系的映射表。
假设有3个转轮,每个的结构都是
1typedef struct {
2 unsigned char uElems[256]; /* 每个字节可以有 0 到255 这 256 个状态。*/
3 unsigned char urElems[256]; /* 反向映射表 */
4 unsigned char uPos; /* 转轮位置,初始化时应该设置为 0。*/
5} EnigmaWheel;
由于经过反射板之后,还要依次反向通过转轮加密,所以还得有一个反向映射表。
而反射板只有一个,这里不考虑反射板转动的情况,那么结构很简单,直接用
1typedef unsigned char EnigmaRefBoard[256];
即可。
2、构造转轮和反射板
如上所述,转轮是单表加密的映射表,也就是说每个字节的 256 种值每个对应唯一的映射结果。绝无重复——但是可以映射为本身,不要忽略这个情况。
举个实际的例子:
10 -> 233
21 -> 7
32 -> 254
43 -> 255
54 -> 31
6...
7253 -> 111
8254 -> 213
9255 -> 99
那么一个字节 4 经过这个转轮时就变成了 31。以此类推,经过所有转轮后进入了反射板。
再根据上表构造一个反向映射表:
1...
27 -> 1
3...
431 -> 4
5...
699 -> 255
7...
8111 -> 253
9...
反射板的的特点是:a → b → a。也就是,{x | x = f(f(x))}。
举个例子就是
10 -> 255
21 -> 100
32 -> 123
43 -> 4
54 -> 3
6...
7100 -> 1
8...
9123 -> 2
10...
11255 -> 0
这里我不说构造的算法,因为构造反射板和转轮的算法可以随意创造,甚至可以你手动设置。当然,文末附带的代码里有一个可用的算法。
但是可以说一下转轮反向映射表的构造方法,假设正向映射表已经构造完毕。
1void enigmaRWheel(EnigmaWheel *pW) {
2 int i;
3 for (i = 0; i < 256; i++)
4 pW->m_uElems[pW->m_uElems[i]] = i;
5}
3、加密过程
现在我们假设反射板和转轮的设置好了,那么就可以开始加密了。
1/* 此函数把一个字节输入到某个转轮中,得到转换结果 */
2unsigned char enigmaWheelDo(EnigmaWheel *pWheel, unsigned char bIn) {
3 /**
4 * 转轮转动的实质是 uElems 表转动到 uPos 位置,那么实际上就是
5 * pWheel->uElems[bIn + pWheel->uPos]
6 * 但是考虑到 uElems 只有 256 个元素,所以应该写成如下形式
7 */
8 return pWheel->uElems[(bIn + pWheel->uPos) % 256];
9}
10
11/* 此函数把一个字节反向输入到某个转轮中,得到转换结果 */
12unsigned char enigmaWheelRDo(EnigmaWheel *pWheel, unsigned char bIn) {
13 /**
14 * 转轮转动的实质是 uElems 表在 uPos 位置,那么实际上就是
15 * pWheel->urElems[bIn] - pWheel->uPos
16 */
17 return pWheel->urElems[bIn] - pWheel->uPos;
18}
19
20/* 此函数对一个缓冲区进行加密,并写回原缓冲区中。 */
21void enigmad(
22 EnigmaRefBoard pRB, /* 反射板 */
23 EnigmaWheel pWheels[3], /* 转轮 */
24 unsigned char *pBuf /* 输入输出缓冲区 */,
25 size_t uBufSize /* 缓冲区大小 */
26) {
27 int i, j;
28 unsigned char t;
29
30 for (i = 0; i < uBufSize; i++) {
31
32 t = pBuf[i];
33
34 for (j = 0; j < 3; j++) /* 正向通过转轮组 */
35 t = enigmaWheelDo(&pWheels[j], t);
36
37 t = pRB[t]; /* 反射板 */
38
39 for (j = 3; j > 0; ) /* 反向通过转轮组 */
40 t = enigmaWheelRDo(&pWheels[--j], t);
41
42 pBuf[i] = t;
43
44 /* 一轮转换完毕,第一个转轮转动一位,如果进位,那么uPos变成0 */
45
46 t = 1; /* 用 t 为 1 表示要进位,0 表示不需要*/
47 for (j = 0; t && j < 3; j++)
48 if (++pWheels[j].uPos)
49 t = 0;
50 /* 注:其实这里可以利用 C 语言的语言特性进行快捷进位,但是为了描述算法,就不这么做了。 */
51 }
52}
好了,算法到此结束。其实 Enigma 是个非常简单的加密算法,但是破解方法却非常复杂,至少人力是很难做到的。而且,如果密文量不够,那是几乎无法破解的。此外,Enigma 算法的加密速度非常快,在个人电脑上实测,100M的文件,只需要 2 秒即可完成加密,而且是未经过优化的情况下。
最后,附上可用代码一份:fenying/libenigma。
程序命令行是
1enigma <WHEELS-QUANTITY> <KEY> <INFILE> <OUTFILE>
感谢阅读。