利用计算机模拟实现 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>

感谢阅读。

comments powered by Disqus