本文由 莫潇羽@复源AI社区(bbs.fuyuan7.com)撰写,转载请注明出处。
快速摘要
2026年9月,美国工程师 Carter Church 借助 OpenAI 的 GPT-6 Astra,用大约6小时模型运行时间,从一张1969年法国军事期刊上的低清翻拍图里,读出了一封长期挂在“未解历史密码”名单上的法军密信。这封信不是拿破仑亲笔,而是拿破仑在1809年3月16日下令、由意大利副王欧仁(Eugène)司令部在3月下旬发给驻达尔马提亚的马尔蒙将军的战前简报;当时马尔蒙还不是元帅。它用的是一种“同音替换+代码本”的混合密码:全信约1300个密码单元、155种不同符号,其中有的符号代表字母,有的代表整个单词,还有的是故意掺进去的空符。破译的关键不是什么神秘算力,而是三件事:先把图片可靠地转录成可计算的符号序列,再用前人公开的33个已知字母值当锚点,最后用模拟退火搜索剩下的符号。我自己用同样思路做了个缩小版实验:没有锚点时只能对三成左右,给三分之一锚点后准确率直接跳到九成上下。信的内容能和拿破仑3月16日的原始命令逐条对上,也补出了1865年版《拿破仑书信集》里一处省略号背后的意思,但它是转述而非原话,还有5个符号没有完全定论。
这件事被不少中文转述写成了“AI一夜终结人类百年悬案”,热闹是热闹,细节却错了不少:把信的发出人、马尔蒙的军衔、空缺出现在哪本书里都搞混了,还顺手夸大了破译的确定性。我在复源AI社区写这篇,想把它拆回到能检验的层面:这封信是什么、它为什么一直没人读、这类密码的原理、我亲手跑的复现实验、真信的破译流水线、信里写了什么、以及这次破译的边界在哪。
想看完整拆解,往下翻。
一、先把事情说清楚:这封信到底是什么,被谁、用什么方式读出来的
我习惯先把一件事的“硬事实”摆成一张表,再去讨论意义。下面这张表里的每一项,都来自当事人公开的解题文章、解题包、密码史网站 Cryptiana 的条目或者相关的新闻报道,我交叉核对过一遍。
|
项目 |
核实后的情况 |
|
信件性质 |
法军战前形势简报,用密码书写,收信人是驻达尔马提亚的马尔蒙将军 |
|
下令者与发信方 |
拿破仑1809年3月16日写信给继子、意大利副王欧仁,命令他用“密码信+一名机灵的军官”把各军部署告诉马尔蒙;实际发出的这封信出自欧仁的副王司令部 |
|
写信时间 |
1809年3月下旬,在欧仁3月20日的信之后,在奥地利4月10日越过因河入侵巴伐利亚之前 |
|
原来的标注年份 |
Cryptiana 名单上一直写作1807年,这次破译后更正为1809年 |
|
唯一来源 |
1969年《法国军事史评论》(Revue historique des Armées)第25卷第4期一篇讲第一帝国密码的文章所附插页,原图约1202×1836像素,平均一个符号只有十几到二十个像素宽 |
|
规模 |
一行明文开头,之后24行密文,约1300个密码单元,155种不同符号 |
|
破译者 |
Carter Church,一位此前没有破过任何密码的工程师 |
|
工具 |
GPT-6 Astra,模型运行时间约6小时,外加他本人几个晚上的整理工作 |
|
核验 |
他把完整的解题包(转录、密钥、逐行解码、验证脚本)公开发布;Cryptiana 主理人友清理士(Satoshi Tomokiyo)审阅后把该条目改为“已解决” |
这里面有几处,和网上流传的版本差别挺大,我单独拎出来说。
1.1 信不是拿破仑亲手写给马尔蒙的
拿破仑那封3月16日的信,收件人是欧仁,不是马尔蒙。那封信在1865年就印进了法兰西第二帝国官修的《拿破仑一世书信集》第18卷,内容是一长串“告诉马尔蒙这个、告诉马尔蒙那个”。真正寄到亚得里亚海对岸的密信,是欧仁的办公室照着这份命令改写、加密后发出的。所以说它是“拿破仑的密信”只能算半对:意思是拿破仑的,文字和加密是副王司令部的书记员完成的。
这个区别不是抠字眼。后面讲到“补全160年空白”时,它直接决定了我们能把结论说到多满。
1.2 收信时的马尔蒙还不是元帅
马尔蒙是拿破仑在土伦围城时就认识的老部下,1806年起管着达尔马提亚。按法国拿破仑基金会公布的履历,他是在1809年7月12日、也就是瓦格拉姆会战之后才拿到元帅权杖的。1809年3月读这封信的时候,他的身份是师级将军,手下大约一万三四千人,被奥地利领土隔在亚得里亚海东岸,是整个帝国最孤立的一支部队。
1.3 “6小时”指的是模型运行时间,不是一个人从零到结果的全部时间
Church 在文章里写得很清楚:模型跑了大约6个小时,他自己另外花了几个晚上。这6小时里包含了从图片切行、识别符号、合并书写变体,一直到搜索密钥、回到原图逐个核对的全过程。这个效率已经非常惊人,没必要再往上加码。
1.4 用的是什么模型
GPT-6 Astra 是 OpenAI 在2026年9月3日发布的新一代旗舰模型,主打电脑操作、软件工程、长链条的智能体任务。多家科技媒体报道,这是 OpenAI 第一个在其内部“准备度框架”里被评为网络安全能力“关键”级别的模型,所以初期是分批、有限制地开放。按发布时公布的 API 标准定价,大约是每百万输入 token 10美元、每百万输出 token 50美元。
大模型的定价和开放范围调整非常频繁,上面是写稿时公开报道的近似情况,请以官方页面的实时信息为准。另外要提醒一句:GPT-6 Astra 属于境外模型,境外模型使用需遵守国内网络与内容管理相关规定,本文只做技术科普,不提供任何使用渠道。
1.5 一张“流行说法”对照表
为了方便大家快速对齐,我把几个常见误读和核实结果放在一起:
|
流行说法 |
核实结果 |
|
拿破仑写给好友马尔蒙元帅的绝密信 |
拿破仑下令、欧仁司令部发出;收信时马尔蒙是将军,不是元帅 |
|
AI逐字还原了拿破仑原话 |
补出的是密信里的同一句话,属于转述,不能证明拿破仑原稿的具体用词 |
|
空缺出现在拿破仑回忆录里 |
空缺出现在1865年版《拿破仑一世书信集》,马尔蒙的回忆录是另一套书 |
|
全部符号都已确定 |
还有5个符号存在两种读法,只是不影响整体意思 |
|
只靠AI,人完全没参与 |
目标设定、结果判断、公开验证都是人做的,前人的部分密钥也起了关键作用 |
二、它为什么能在“未解名单”上一挂这么多年:卡住人的从来不是数学
很多人第一反应是:一封两百多年前的军用密信,能有多难?说实话,就密码本身而言,确实不难。Cryptiana 条目里早就有一句判断:马尔蒙的司令部1811年用过的一套代码只有大约150个条目,这一封大概率也不是什么大系统。真正把它挡在门外的,是一连串非数学的问题。
2.1 第一道坎:根本没有可以计算的“文本”
做密码分析,原料是一串符号序列,比如“47 Θ + 81 ↑ 31……”。可这封信从来没有人把它完整转录过。它的全部存在形式,就是那篇1969年期刊文章后面夹的一页插图。Cryptiana 的条目只给出了第一行明文;另一位近期非常活跃的解密爱好者 Daniel Bourdeau,在自己的笔记里对这封信的评价大意是:密文在他能找到的地方都没有公开,所以没有可以下手的东西。
这里有一个有意思的“信息差”:那本期刊其实早就被法国学术期刊数字化平台 Persée 收录,免费可看。只是插页夹在正文第24页之后,不翻到那里就发现不了。线索一直在,只是没人顺着摸过去。
2.2 第二道坎:手写符号的转录本身就是一门手艺
就算找到了图,你面对的是二十多行手写的混合符号:两位数字、普通拉丁字母、希腊字母、印刷符号,还有七十个左右完全是自造的图形。最麻烦的是,同一个符号书记员每次写得不完全一样,两个长得差不多的墨迹,到底是同一个符号的两种写法,还是两个不同的符号?判断错一个,后面的统计就全歪了。
Church 公开的数据很能说明问题:转录阶段先是把所有看起来不同的痕迹都单独命名,得到了175个临时标签、约1320个单位;经过反复比对、合并写法变体之后,才收敛到155种符号、1300个单位。这一步在传统做法里,往往要一个懂古文书的人对着图一个个过,Bourdeau 在另一篇解密笔记里也提到过,光是准备一份可供计算机处理的密文,就做了两轮转录、六轮对图核对。
2.3 第三道坎:跨学科,且“性价比”太低
想读懂这封信,你需要同时具备几样东西:
- 能认19世纪初手写体和印刷体混杂的眼力;
- 懂当时的法语,包括军事术语、贵族封号这种只在那个年代出现的词;
- 会做同音替换密码的统计攻击;
- 熟悉1809年第五次反法同盟战争的来龙去脉,能判断解出来的内容是不是“说得通”。
这些能力单拎出来都有人会,但凑到同一个人身上的不多,而这样的人手上通常有更重要的课题。为了一张期刊插页花上几周,划不来。Church 在文章结尾的看法我很认同:材料一直都在,前人的半张密钥表是公开的,拿破仑书信集19世纪就印出来了,马尔蒙回忆录在公共领域电子书项目里也能找到,一位耐心的专家在1970年就可能做完这件事,缺的只是值不值得投入的判断。
2.4 第四道坎:名单本身的标签不一定可靠
这一点很少有人提,但我觉得特别重要。Cryptiana 是友清理士个人维护了十多年的历史密码资料站,他在2026年9月的公告里专门写了两件事:一是最近收到的解题投稿太多,已经来不及逐条更新;二是名单上列着的东西,并不都意味着“从来没有人破译过”,有些其实在档案或出版物里早就有解读,只是没被关联起来。
这封马尔蒙密信本身就有一个“标签错误”:年份标成了1807年。如果你真按1807年的历史背景去理解内容,各路法军的位置会完全对不上。Church 后来正是通过把破译结果和1809年3月的拿破仑书信逐条比对,才把年份改正过来的。
所以“217年无人能解”这种说法,更准确的表述应该是:这封信在公开资料里一直缺少可用的转录和完整解读,而不是一代代密码学家前赴后继地攻克失败。这两者给人的感受差别很大,但对于理解这件事的意义,后者才是对的。
flowchart LR
A[1809年3月 密信发出] --> B[原件与密钥下落不明]
B --> C[1969年 期刊插页刊出一张图]
C --> D[Persée 数字化 但无人转录]
D --> E[Cryptiana 列入未解名单 年份误标1807]
E --> F[2026年9月 AI辅助完成转录与破译]
F --> G[公开解题包 Cryptiana 改标已解决 年份更正1809]
我(莫潇羽)的看法是:这次真正被AI显著压缩的,是“转录+统计攻击+文献比对”这几步的组合成本,而不是密码学难度本身。弄明白这一点,后面你看任何“AI破解XX悬案”的新闻,都能先问一句:卡住它的到底是哪一道坎?
三、拿破仑时代的军用密码长什么样:同音替换和小代码本的混合体
要看懂破译过程,得先知道对手是什么。这一章我尽量用白话讲,懂行的朋友可以快速扫过。
3.1 最朴素的替换密码,以及它为什么不安全
替换密码:把明文里的每个字母,按一张固定的对照表换成另一个符号。比如 A 换成“47”,B 换成“!”。收信人拿着同一张表反查就能还原。
这种密码有个致命弱点:语言里每个字母出现的频率是很不均匀的。法语里 E 一个字母就要占到百分之十几,S、A、I、T、N 紧随其后,而 K、W 几乎不用。只要密文够长,攻击者数一数哪个符号出现最多,基本就能猜出它是 E,然后像拼图一样往下推。这就是所谓的频率分析,九世纪的阿拉伯学者就已经系统讲过。
3.2 同音替换:给高频字母多发几张“马甲”
同音替换密码(homophonic substitution)就是针对频率分析的补丁:高频字母不止一个代号。E 可以写成九种不同的符号中的任意一种,A 有十一种,而 Z 只有一种。书记员加密时,每遇到一个 E 就从九个代号里随手挑一个。这样一来,密文中每个符号的出现次数被摊平了,“数最多的那个就是 E”这招就不灵了。
从马尔蒙密信的解题包公布的密钥看,各字母分到的符号数量大致和法语字母频率成正比。我把它整理成一张表(W 在这封信里没出现,所以没有分配符号):
|
字母 |
符号数 |
字母 |
符号数 |
字母 |
符号数 |
|
A |
11 |
J |
3 |
S |
6 |
|
B |
2 |
K |
2 |
T |
9 |
|
C |
4 |
L |
4 |
U |
4 |
|
D |
4 |
M |
5 |
V |
4 |
|
E |
9 |
N |
7 |
W |
0 |
|
F |
3 |
O |
5 |
X |
2 |
|
G |
4 |
P |
4 |
Y |
2 |
|
H |
6 |
Q |
3 |
Z |
1 |
|
I |
5 |
R |
6 |
合计 |
115 |
数据来源:Carter Church 公开的解题包中的密钥文件,统计为写稿时版本,后续若有修订以解题包为准。
3.3 再加一层:代表整词的符号和空符
光是同音替换还不够狠。当时法国军队普遍用的是一种叫分类代码本(nomenclator)的体系:在字母表之外,再给一批高频词、人名、地名单独发一个代号。马尔蒙这封信里,就有大约27个符号对应15个常用词,比如 de、que、les、vous、général、pas、pour;还有两个符号专门表示双写的 S,两个表示逗号。
另外还有空符(null):什么意义都没有的符号,书记员会时不时往密文里塞一个,专门用来打乱统计规律、浪费破译者的时间。这封信里至少有4个符号被判定为空符。
把这几类加起来,就是那155种符号的来源:
|
符号类别 |
数量 |
作用 |
|
字母符号 |
115 |
同音替换,代表单个字母 |
|
整词符号 |
27 |
代表 de、que、les、vous 等常用词 |
|
其他功能符号 |
8 |
双写S、逗号、空符 |
|
尚未定论 |
5 |
有两种可能读法,给出了最佳猜测 |
|
合计 |
155 |
|
3.4 当时的人怎么用这套东西
按 Cryptiana 对拿破仑时期密码的整理,法军司令部一般准备两张表:一张叫“加密表”(tableau chiffrant),左边是明文字母和词,右边列出可以用的符号;另一张叫“解密表”(tableau déchiffrant),按符号排序,供收信方反查。书记员拿着长官写好的草稿,一个字母一个字母地换,同一个 E 尽量别连着用同一个符号,偶尔丢一个空符进去。
这套体系并不是攻不破。最有名的反例就和马尔蒙本人有关:1811年马尔蒙在伊比利亚半岛指挥葡萄牙军团时,他的司令部用的是一套只有约150个条目、E 有9个代号的代码,英军参谋军官乔治·斯科维尔(George Scovell)两天就把它读出来了,帮了大忙的是马尔蒙的书记员经常在密文里夹杂明文单词。相比之下,1809年这封信的书记员要谨慎得多,整封信除了第一行,几乎没有留下明文破绽,唯一的例外是第14行一个用普通字母拼出来的 CONSEQUENT(意思是“因此”),这个后面会讲到。
flowchart TB
P[长官写好的法语草稿] --> Q{逐词处理}
Q -->|高频词或人名| W[查整词代号 例如 vous 对应专用符号]
Q -->|普通单词| L[逐个字母查同音表 随机挑一个代号]
L --> N[随手插入空符]
W --> C[密文]
N --> C
C --> R[信使携带 穿过敌占区]
R --> D[收信方用解密表反查]
讲到这里,大家应该能体会这封信的难点组合了:符号多(155种)、单个符号出现次数少(很多符号整封信只出现一两次)、字母和整词混在一起、还有空符干扰。任何一项单独拿出来都不算可怕,叠在一起,再加上只有一张低清图,就成了一个没人愿意碰的活儿。
四、我亲手复现了一遍:用模拟退火破一封“仿制”的同音密信
只看别人的结论心里不踏实,我决定自己动手做个缩小版实验,验证两个问题:一是模拟退火这种方法对同音替换密码到底有多大用;二是“已知三分之一密钥”这个锚点,到底起了多大作用。
先声明实验边界:我没有去转录那张真的插页(低清图像加上手写符号,单靠我一个人很难保证转录准确),而是自己写了一段内容相似的法语军务短文,按真信的思路加密,再尝试破解。这是一个原理验证,不是对真信的重复破译。
4.1 先讲清楚:模拟退火是什么
模拟退火(simulated annealing)这个名字来自冶金:把金属烧热再慢慢冷却,原子有机会跳出不好的排列,最后形成更稳定的晶体。放到破解密码上,思路是这样的:
- 先随便给每个密码符号指定一个字母,得到一份乱七八糟的“译文”;
- 给这份译文打个分,看它有多像真正的法语;
- 随机改动一个符号对应的字母,再打一次分;
- 分数变高了就接受这次改动;分数变低了,也按一定概率接受,这个概率随“温度”下降而变小;
- 反复成千上万次,温度逐渐降低,最后停在一个分数很高的方案上。
为什么要“偶尔接受变差的改动”?因为如果只接受变好的,算法很容易卡在一个局部还不错、但整体是错的方案里出不来。早期温度高,它敢大胆尝试;后期温度低,它就只做精修。
接受一个变差改动的概率,常见写法是:
P(接受) = e^(Δ / T)
其中:
Δ = 新分数 − 旧分数(变差时 Δ < 0)
T = 当前温度,随迭代次数逐渐降低
Δ 越负、T 越低,接受概率越接近 0
4.2 “像不像法语”怎么打分:n-gram 语言模型
打分用的是 n-gram 统计:把一大段正常法语文本按连续 n 个字母切开,统计每种组合出现的频率。以 n=4 为例,“tion”“ment”“ourl”这种组合很常见,“qxzk”几乎不会出现。一段译文的得分,就是把它所有连续4字母组合的对数概率加起来:
得分 = Σ log₁₀ P(第 i 个四字母组合)
没见过的组合给一个很低的保底值,避免直接变成负无穷
真信的破译里,Church 他们用的是3、4、5字母组合,语料来自雨果、大仲马的作品和马尔蒙本人的回忆录。我手边没有现成的大规模19世纪法语语料,就用了一个开源的法语词频库,按真实词频随机抽取了40万个词拼接成训练文本。语料粗糙一些,但足够说明问题。
4.3 实验设置
我写了一段大约440个字母的法语短文,内容是给一位将军通报部队位置、催促他接到命令就出发、要求用密码定期写信,口吻模仿那个时代的军务信件,但每一句都是我自己编的。去掉空格、重音和标点后,用下面的规则加密:
- 每个字母分到的代号数量,和它在原文中的出现频率成正比;
- 总共约60个代号,实际用上了65个(四舍五入导致略多);
- 每遇到一个字母,从它的代号里随机挑一个。
核心代码大致是这样(为了阅读做了删减):
# 同音加密:频率越高的字母分到越多的代号
alloc = {c: max(1, round(freq[c] / len(plain) * 60)) for c in letters}
key, sid = {}, 0
for c in letters:
for _ in range(alloc[c]):
key[sid] = c
sid += 1
cipher = [random.choice(homophones[c]) for c in plain]
# 模拟退火:每次随机改一个符号对应的字母
for i in range(iters):
s = random.choice(free_symbols)
old = mapping[s]
mapping[s] = random.choice(letters)
new_score = score(decode(cipher, mapping))
delta = new_score - cur_score
if delta >= 0 or random.random() < math.exp(delta / T):
cur_score = new_score # 接受
else:
mapping[s] = old # 撤回
T = max(0.2, 10.0 * (1 - i / iters)) # 线性降温
我对比了两种条件,各跑3组随机种子,每组6次重启、每次6万步迭代:
- 无锚点:所有符号都要靠算法自己找;
- 三分之一锚点:随机挑三分之一的符号,直接告诉算法它们的正确字母,模拟“前人已经公开了33个字母值”的情况。
4.4 实验结果
先看一个直观的指标。重合指数(index of coincidence,简称IC)衡量的是随便从文本里抽两个字符、恰好相同的概率。正常法语明文的IC明显高于随机文本,这正是频率分析能起作用的根源。
|
指标 |
数值 |
|
明文的重合指数 |
0.0775 |
|
同音加密后密文的重合指数 |
0.0157 |
|
26个字母完全随机时的重合指数 |
0.0385 |
加密后的IC甚至比“26个字母随机分布”还低,因为符号种类从26个变成了65个,规律被摊得非常平。这就是同音替换的威力:单看频率,你几乎得不到任何信息。
再看破解结果:
|
条件 |
符号正确率(3组) |
字母位置正确率(3组) |
3组总耗时 |
|
无锚点 |
22%、29%、29% |
23%、33%、32% |
约83秒 |
|
三分之一锚点 |
86%、86%、89% |
89%、89%、93% |
约86秒 |
以上为我本人在一台普通云端Linux环境里的实测结果,语料、文本长度和参数不同,结果会有明显差异,仅供理解原理。
无锚点时,算法收敛到了一堆“des”“les”“ses”“de”反复出现的伪法语,分数不低,但完全不对,这就是典型的局部最优陷阱。给了三分之一锚点之后,第一句话基本就出来了,译文开头是这样的:
算法输出:moncherienereltetousentoieparunofficiersurlapositiondenostroupes...
真实明文:monchergeneraljevousenvoieparunofficiersurlapositiondenostroupes...
剩下的错误集中在出现次数很少的符号上,比如“general”里的 g 被认成了 i,这种错误人一眼就能看出来并修正,这其实也是真信破译中“回到原图逐个核对”那一步要干的活。
4.5 这个小实验让我想明白的三件事
锚点的价值远超直觉。 三分之一的已知值,不是让准确率提高三分之一,而是从三成直接跳到九成。原因在于,已知符号把很多n-gram片段“钉”住了,算法在评估其他符号时有了可靠的上下文,搜索空间一下子缩小了好几个数量级。真信破译中,法国 ARCSI 协会网站上公开的那份 Daniel Tant 整理的残缺对照表(33个字母值,覆盖约435个密码单元,占全信三分之一左右),地位就相当于我实验里的锚点。
文本越短、符号越多,越难。 我的仿制信只有440个字母、65种符号;真信有1300个单元、155种符号,其中还夹着整词符号和空符,单个符号的平均出现次数比我的实验还低。这类问题在学术界有专门研究,比如德国学者 Nils Kopal 在2019年历史密码学会议上发表过用“固定温度”模拟退火攻击同音替换密码的论文,美国圣何塞州立大学的 Mark Stamp 团队2013年也在《Cryptologia》期刊上发表过高效破解方法,结论都指向同一个方向:密文长度与符号数之比是决定难度的关键。
算法只负责“像不像”,不负责“对不对”。 无锚点那组的伪法语,分数其实相当高。如果没有外部证据去核对,你很可能被一份“统计上很像法语、实际上胡说八道”的结果骗过去。这也解释了为什么真信破译里,后面那几步核验比搜索本身更重要。
我自己折腾下来最大的感受是:模拟退火这种工具几十年前就有了,代码也不复杂,一个有编程基础的人一个下午就能写出来。真正耗人的,是把“图片上的墨迹”变成“干净的符号序列”,以及最后的人工判读。这两头恰好是多模态大模型最能帮上忙的地方。
五、回到那封真信:一条完整的破译流水线是怎么搭起来的
有了上面的铺垫,再看真信的破译过程就清楚多了。我按 Church 公开的技术说明,把整个流程重新梳理成一条流水线,并在每一步标注它解决的是哪一道坎。
flowchart TB
S1[第1步 图像转录<br/>切行 识别 合并书写变体] --> S2[第2步 固定已知值<br/>Tant的33个字母值 加上明文单词CONSEQUENT]
S2 --> S3[第3步 统计搜索<br/>模拟退火 3到5字母组合打分]
S3 --> S4[第4步 识别整词符号<br/>反复破坏通顺度的符号改按单词测试]
S4 --> S5[第5步 回图逐符号核对<br/>每个取值必须在所有出现处都成立]
S5 --> S6[第6步 反向压力测试<br/>删掉拿破仑相关语料后重跑]
S6 --> S7[公开解题包<br/>转录 密钥 验证脚本 哈希值]
S5 -.发现问题.-> S1
5.1 转录:把“墨迹”变成“数据”
这是整个流程里最容易被低估、也最体现多模态能力的一步。模型先把1202×1836像素的插页切成一行一行的小图,对每个看起来不同的痕迹给一个临时名字,比如“上箭头”“带点的矩形”“希腊字母θ”“47”。凡是拿不准是不是同一个符号的,先分开记,宁可多不可少。
然后才是合并:两个临时标签,如果在破译后期发现它们总是代表同一个字母、而且形状只是笔画轻重或连笔的差别,就判定为同一个符号的不同写法。175个临时标签最后合并成155个,就是这么来的。
我想特别强调这里的顺序:先保守地拆开,再根据证据合并。如果一开始就武断地把两个相似符号当成一个,一旦判断错了,后面所有统计都会被污染,而且很难回头发现。这个思路做任何数据清洗都适用。
5.2 固定已知值:别从零开始
转录完成后,并没有马上开始暴力搜索。法国的 ARCSI(一个由密码与信息安全领域预备役人员组成的协会)在官网上收录了一批由 Daniel Tant 整理、按编号排列的古代密码表,其中第373号是一份标注为1809年、与欧仁·德·博阿尔内有关的代码表(网址为 www.arcsi.fr/doc/Tant/373.pdf,写稿时可以打开)。这份表记录了这套密码的33个字母值,但所有“整词”格子都是空的。Church 在文章里称 Tant 为法国密码史研究者,我没有查到更详细的个人资料,这里只按他整理的资料来介绍。把这33个值固定住,全信约三分之一的单元就能直接读出来。
另外,第14行有10个普通拉丁字母连起来拼成了 CONSEQUENT,这是书记员“偷懒”留下的一个明文单词,正好可以当作额外的确定信息。
5.3 统计搜索:剩下的三分之二交给算法
剩下约865个单元对应的符号,交给模拟退火去找。打分用的法语统计特征来自19世纪的文学作品和马尔蒙的回忆录。这一步和我前面的实验在原理上完全一样,只是规模更大、语料更贴近时代。
5.4 识别整词符号:发现“这不是字母”
这一步特别有意思。搜索过程中,总有一些符号无论分配哪个字母,都会把附近本来很通顺的法语弄得别扭。换个角度想:也许它根本不代表一个字母,而是一个完整的单词?
把这些“捣乱”的符号改成按整词测试之后,问题一个个解开了:最终有29个符号被认定为整词或特殊功能符号,其中包括 de、que、les、vous、général 这些高频词,以及两个表示双写 S 的符号。这其实是在搜索中途修改模型假设,而不是死守最初“每个符号都是一个字母”的设定。
我在自己的实验里没有加入整词符号,后来专门想了一下,如果加进去会怎样:一个代表 vous 的符号,如果被算法当成单个字母,不管赋成哪个字母,它所在位置都会凭空少掉几个字母,n-gram 打分就会在这里持续“报警”。所以一个很实用的信号是:某个符号无论怎么赋值,局部得分都上不去,那就该怀疑它不是字母。这个判断标准简单,但非常管用,懂了它,你再看别的代码本类密码也会有思路。还有一个旁证可以用:整词符号前后通常带着词边界的特征,紧跟在它后面的字母组合,往往像一个新单词的开头,而不像一个单词的中间部分。
5.5 回图核对:每个取值都要经得起所有出现位置的检验
一个符号如果被赋值为字母 A,那它在全信每一个出现的位置都必须读得通。只在某一处让句子变顺、在别处造成矛盾的取值,一律不收。这一步把我实验里“g 被认成 i”那类低频错误压了下去。
5.6 反向压力测试:这是我认为最关键、也最常被忽略的一步
这一步在很多转述里完全没提,但在我看来是整个破译可信度的压舱石。
有一个很自然的质疑:你用来打分的语料里有马尔蒙回忆录、有拿破仑时期的文本,那算法会不会只是“背出”了它见过的历史文本,而不是真的解出了密码?Church 的做法是:把马尔蒙回忆录和所有拿破仑时期的文本从语言模型里全部删掉,从零重新跑一遍求解器,结果得到了同样的读法。
这就排除了“模型靠记忆凑答案”的可能。打个比方,这相当于考试时把参考书收走,学生还能独立做出同样的答案,说明他是真会,而不是抄的。以后大家再看到“AI破解XX”的新闻,可以把“有没有做类似的消融测试”当作一条判断标准。
5.7 公开可复现:结论不靠嘴说
最后,Church 把全部材料打包公开:原始插页图像、转录文件、包含每个符号图样的密钥表、逐行解码结果、编辑后的法语和英语译文、剩余的不确定符号说明,以及一个验证脚本和一个复现脚本,可以离线从头重算一遍,压缩包还附了 SHA-256 哈希值防篡改。
|
文件 |
用途 |
|
转录文件 |
24行、1300个单元的符号序列 |
|
密钥文件 |
155个符号及其取值 |
|
验证脚本 |
用密钥对转录逐个解码 |