本节摘要:恩尼格玛是维吉尼亚"多表轮换"思想的机械化极致:三只转子逐字轮换替换表,接线板叠加字母对交换,反射板让加解密共用一台机器。本节拆开它的电流路径,算清约 1.07×10²³ 的密钥空间从何而来,并指出反射板留下的"字母不自映射"这一致命代数把柄——它将成为 2.2 节图灵反攻的支点。
1918 年,德国工程师谢尔比乌斯为一种"带转子的加密打字机"申请专利,产品取名 Enigma(希腊语"谜")。它最初作为商业保密通信设备推销,生意惨淡;德国军方的介入改变了它的命运——二十年代末起,德军各军种陆续装备改进型恩尼格玛(陆军与空军主力为恩尼格玛 I),到二战爆发,德国高层通信几乎全部跑在这台约 34 厘米见方的木盒机器上。
它解决的正是上一章留下的死结:维吉尼亚的密钥周期太短、轮换太规律。转子机用机械旋转替代人工查表——**每敲入一个字母,转子组就转动一格,整张替换表随之换一副面孔。**打满一整圈才进位到下一只转子,三只转子串起来,替换表在 26×26×26 = 17,576 个状态之间循环,远超人手的追踪极限。密钥则体现为三样可每日更换的物理配置:选用哪三只转子及排列顺序、每只转子的初始位置、接线板上十根双头电缆的插法。

恩尼格玛的威慑力要用乘法算。以陆军恩尼格玛 I 为例,四个因子相乘:
60 × 17,576 × 676 × 1.51×10¹⁴ ≈ 1.07×10²³。作为对比,维吉尼亚五字母密钥只有约 1.2×10⁷ 种;假设人工每秒核对一种配置,搜完恩尼格玛的密钥空间需要的时间以亿年计。德军的信任由此而来:密钥每天午夜更换,就算某天密钥本泄露,第二天天一亮防线又会满血。
但这笔算术有一个隐含前提——敌手只能瞎猜。一旦敌手能从每条密文里提取哪怕一点结构性线索,空间再大也无济于事。恩尼格玛恰恰留了两条结构裂缝:其一,操作规程要求报务员把每条消息的"消息密钥"用日钥加密两遍后随文发出(防止无线电误码),密文开头六个字母因此永远遵循"第一与第四、第二与第五、第三与第六同源"的模式;其二,反射板保证任何字母的密文绝不会是它自己。前一条被波兰人吃干榨净,后一条被图灵用到了极致。
反射板是恩尼格玛工程上最巧、数学上最亏的部件。电流经三只转子走到终端后不直接点亮灯泡,而是经一块固定的 13 对交换板沿原路折回,最终亮起的字母与按下的字母永远不会相同。它带来的便利是决定性的:加密与解密完全对称——收方把机器调到与发方相同的状态,键入密文 Q,灯亮即明文;发报员不需要两台机器、两套流程。
代价则是把一条铁律写进了每一份密文:**Enigma(P) ≠ P 对所有字母 P 恒成立。**这个性质看似只是个小瑕疵,实则让恩尼格玛的全部 1.07×10²³ 种状态在代数上都成了"自反置换的复合"——它们是一类非常特殊的置换。对不知道群论的大多数人这无感,但对 1940 年的图灵,它是可以落笔计算的漏洞:只要猜出某段明文(crib,例如每天早晨都会出现的天气电报词 WETTER),把"猜测字母绝不等于对应密文字母"的约束逐位排布成回路,凡出现自映射的转子配置立即整批排除。这就是炸弹机的工作原理,细节留给下一节。
用 Python 搭一个没有接线板的三转子恩尼格玛骨架,可以亲手体会"每键换表"的机制:
# 三转子恩尼格玛极简模拟:省略接线板与环设置,保留转子与反射板机制 ROTOR = ["EKMFLGDQVZNTOWYHXUSPAIBRCJ", # 转子Ⅰ的乱配表 "AJDKSIRUXBLHWTMCQGZNPYFVOE", # 转子Ⅱ "BDFHJLCPRTXVZNYEIWGAKMUSQO"] # 转子Ⅲ REFLECT = "YRUHQSLDPXNGOKMIEBFZCWVJAT" # 反射板:13 对交换,无自映射 ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" def step(pos, i): # 进位:模拟里程表 pos[i] += 1 if pos[i] == 26 and i > 0: pos[i], pos[i - 1] = 0, pos[i - 1] + 1 return pos def enigma_char(ch, pos): """先过三只转子正向,反射板折返,再过三只转子逆向""" step(pos, 2) c = ALPHA.index(ch) for i in (2, 1, 0): # 正向:快、中、慢 c = ALPHA.index(ROTOR[i][(c + pos[i]) % 26]) - pos[i] c %= 26 r = REFLECT[c] # 反射:永不命中自身 assert r != ALPHA[c], "反射板保证字母不自映射" c = ALPHA.index(r) for i in (0, 1, 2): # 逆向:慢、中、快 c = ALPHA.index(ROTOR[i][(c + pos[i]) % 26]) - pos[i] c %= 26 return ALPHA[c] pos = [4, 2, 15] # 每日密钥决定的初始位置 ct = "".join(enigma_char(c, pos) for c in "ATTACKATDAWN") print(ct) # 同一字母每次按键的变换都不同
⚠️ 常见误解:"密钥空间大 = 安全"。恩尼格玛的 10²³ 从未阻止破译——空间挡得住瞎猜,挡不住结构性线索。今天评估任何密码系统,先问"敌手能免费获得什么规律",再谈空间。
下一节走进布莱切利园:三个波兰人先用群论撕开口子,图灵再以炸弹机把裂缝扩成通道,机械密码的丧钟由电子计算机敲响。