中文译本 · 原作 Daniel Bourdeau《Unsolved Historical Ciphers》 以 CC BY 4.0 发布,本页由 JIC 翻译,非原作者官方版本,译文如有出入以英文原页为准 · 查看英文原页 ↗

哥本哈根 · 笔画、圆点和数字,体系未定 · 大约发现于1950年代末

哥本哈根密码

三行由笔画、圆点、尖角符号和数字组成的字迹,发现于哥本哈根一座军事博物馆中一幅1835年丹麦将军肖像的背后,在 Klaus Schmeh 的 Top 50 中排第23位。2026年9月,它被当作简单替换,在两种转录、六种约定下用十种语言加以攻击,并配有匹配的对照。没有复原出密钥,也从未有人发表过解读。

Daniel Bourdeau · 发布于 · 更新于 · 资料来源:Klaus Schmeh 在 Cipherbrain 上2015年1月、2017年8月和2021年10月的帖子及其评论;614 × 147 像素的扫描件 Danish-General-Cryptogram.jpg

方法:未破解 · 程度:未获得文本

不是所测任何语言的简单替换 就现有扫描件而言已结案

用一个以5-gram语言模型打分的求解器(模拟退火),在十种语言下攻击了流传的两种转录。各语言最好也只达到每字母−2.69 nats,300次重启也没有改变这一结果。同一求解器能复原用同样方式加密的15段随机103字母文本中的12段,而这一长度的真实文本得分在−1.4到−2.1之间。要么这张纸条不是这些语言中某段文本的简单替换,要么两种读法都带有同一个转录错误。

01 实物及其来历

纸条上有三行短字。它大约在1950年代末发现于哥本哈根一座军事博物馆中一幅1835年丹麦将军肖像的背后,并被寄给了美国密码协会(American Cryptogram Association)。ACA 没有记录任何解答,也从未撰文介绍。博物馆、将军和发现者都没有记录。

Klaus Schmeh 从 ACA 会员 Kent Ramliden 那里得到一份扫描件;Ramliden 是旅居佛罗里达的瑞典人,2016年去世。Schmeh 三次发表过它:2015年1月用德语,2017年8月作为他 Top 50 的第23号,2021年10月又以德语和英语的“悬案”帖子发表。流传的唯一图像就是那份扫描件,614 × 147 像素。2021年评论中有两条读者贡献与此相关:“ShadowWolf”的25符号转录,以及 Matthew Brown 用丹麦语四元组做的爬山法,后者一无所获。

02 转录

使用了两种转录。 cipher.txt 是本项目的转录,根据放大四倍和六倍的扫描件,每次半行做成。 cipher_sw.txt 是 ShadowWolf 在2021年10月7日那篇德语帖子评论中给出的25符号读法。以下是合并后的读法,每个符号一个记号,以空格分隔:

7 N F n F B 3 A D B n o 3 D B P 6 3 B 3 P 8 D F 2 n 3 | 2 3 A W R F 0 3
A n 2 0 B n o 9 P n P 9 2 B 3 v v 3 D B | 0 A B v 3 D B P n o F 3 8 0 D B
P X 0 3 B n o n F 9 n D B v n | 1 3 | n o n 3 F D B 3 F B A 1 2 3 A

共107个记号:20种共103个符号,外加4条长竖线,写作 | ,当作句子分隔符处理。数字4和5从未出现。

记号纸条上的标记次数
3数字316
n普通的 n13
B后面带点的反斜线(\.)14
F后面带点的斜线(/.)8
P加号6
A尖角或 lambda(^)6
D位于笔画之前的独立圆点8
o小圆字母 o5
2数字25
0高的零5
v照写4
9数字93
1数字12
8数字82
7 6 X照写各1次
N上方带两点的 n1
R第1行末尾一个形似“or”的小字形1
W上方带双重音符的尖角1
|长竖线,句子分隔符,不计入103个之内4

两种读法的分歧

两种读法逐个符号一致,只是 ShadowWolf 把这里合并的东西拆开了。他把后面带点的反斜线与两侧都带点的反斜线区分开来,后者在这里是一个独立圆点 D 后接 B。他把后面带点的斜线与前面带点的斜线区分开来,后者在这里是 D 后接 F。他把第一对尖角之后那个小小的上标双撇单列为一个符号。他区分了四种尖角形状:一种高的、像 lambda 的,即第1行的第一个尖角和第3行的最后一个符号;一种小的;一种后面带点、位于第2行开头的;还有一种在第2行的零之后。他还把第3行 1 3 附近的第二条高竖线读作数字1,而不是分隔符。这样拆分后符号数为25,与 Schmeh 的数字一致;合并后为20。两种读法都做了攻击。

两条内部规律

信件 o 出现五次,每次都紧跟在 n: no3, no9, +no, non 和 non3之后,其中 non 在第3行出现两次。成对的 vv 出现一次,在第2行。第06节说明了它们能走多远。

03 攻击

solve.py 用 Project Gutenberg 中十种语言的文本构建字符5-gram语言模型:丹麦语、瑞典语、挪威语、德语、荷兰语、法语、英语、拉丁语、冰岛语和芬兰语,每种0.6至240万字母。语料不在仓库中,而是通过 Gutendex API 按会话日志中的 id 获取。求解器是在单射的符号到字母映射上做模拟退火,每次运行重启60到80次,长竖线视为句子边界。求解器只优化5-gram得分;得分最高的候选随后再按词典覆盖率重新排序,而词典覆盖率并不在优化目标之内。

在合并读法上测试了六种记号约定:独立圆点作为字母、删去、作为边界,或与后面的笔画合并;长竖线作为字母;带点的 n 与普通 n 合并。25符号读法则按原样运行。另有一个脚本 solve_sp.py检验“两种带点笔画是单词分隔符”这一假设——那样的话,文本就是22个词,平均每词3.7个字母——它使用一个考虑空格的模型,并以整词命中来计分。

在任何语言、任何约定下都没有得出可读的东西。各语言的最佳每字母得分,以及最佳密钥得出的结果:

语言最佳每字母得分(任意约定)结果大致如何
拉丁语−2.69fxsisted tine togeteom scie ...
丹麦语−2.77junindel dige dskedesf ... / ... udstødes ... velsigne ...
挪威语−2.81bærersag sena stjasatm ...
瑞典语−2.81honensam sera stfasatv ...
中文释义−2.88cydidsea sine stbesetf ...
德语−2.93uchthien itze irbeierm ...
法语−2.97chiailes lape ltbeletr ...
荷兰语、冰岛语、芬兰语−3.07 至 −3.30

单词分隔符假设在带空格的模型下每字母得分−4.6,而真实文本约为−2.3。它只得出一些孤立的丹麦语单词:er、elle、unge、død、døde。最后两个就是第06节讨论的片段。

04 匹配对照

一次失败的搜索本身证明不了什么,所以先用求解器本应能解开的文本对它进行校准。 control.py 从同样的语料中随机抽取103字母、含三个句子断点的段落,每段用约20个符号的随机简单替换加密,再用完全相同的求解器攻击。每种语言试验三次,每次重启40次;而真正的密码用了60到80次,后来又用了300次。准确率是复原出的段落所占比例。

语言试验密钥中的字母数准确率找到的得分真实文本的得分结果
丹麦语1190.99−1.99−1.85已复原
丹麦语2190.99−1.93−1.96已复原
丹麦语3180.99−1.85−1.76已复原
德语1220.96−1.88−1.78已复原
德语2190.79−2.64−1.75部分
德语3220.14−3.42−1.81搜索失败
中文释义1201.00−1.44−1.44已复原
中文释义2210.99−1.99−1.85已复原
中文释义3201.00−1.67−1.67已复原
拉丁语1181.00−2.09−2.09已复原
拉丁语2181.00−1.74−1.74已复原
拉丁语3190.99−1.87−1.72已复原
瑞典语1221.00−1.95−1.95已复原
瑞典语2220.05−3.49−1.59搜索失败
瑞典语3221.00−1.99−1.99已复原

15段中有12段基本完全复原,真实文本的每字母得分总在−1.4到−2.1之间。两次试验彻底失败,一次部分失败。每一例中,真正的密钥得分都会远好于求解器找到的结果:−1.81对−3.42,−1.59对−3.49,−1.75对−2.64。这些是40次重启下的搜索失败,很可能与22字母的密钥有关;它们意味着,对真实文本的单次失败运行证明不了什么。正因如此,最有希望的几种候选语言在两种读法上都用300次重启重跑了一遍。

对照这一校准,该密码在任何语言、任何读法下的得分都从未好于−2.7。所以,要么它不是这十种语言中某段文本的简单替换,要么转录以两种独立读法共有的方式合并或拆分了字母。丹麦语自2015年以来一直是默认假设,但它的契合度并不比拉丁语或挪威语好。Matthew Brown 在2021年评论中做的丹麦语四元组爬山法,也同样一无所获。

05 300次重启的确认

以种子5重启300次的最佳每字母得分,对象为合并读法的 full 和 nodot 约定以及25符号读法。 dotattach 的运行在写这些笔记时仍在进行,尚未超过这些分数。

语言合并,完整合并,去掉圆点25符号读法
拉丁语−2.90−2.82−3.16
丹麦语−3.04−2.90−3.16
瑞典语−3.17−2.99−3.20
挪威语−3.12−3.01−3.17
中文释义−3.20−3.13−3.28
德语−3.29−3.32−3.46

重启次数增至五倍,结果纹丝不动;第03节取的是六种约定中的最佳值,这些运行固定了约定和种子,没有一格超过它。上限依旧,在每种语言中都比这一长度的真实文本得分低0.7到1.4 nats每字母。搜索失败这一解释可以排除。

06 唯一引人遐想的片段

non 读作 død

在 n = d, o = ø, 3 = e 的情况下,n-o-n 模式给出丹麦语 død(死的,死亡), non3 给出 døde(死了,死者),而 no3 给出 døe,即 dø(死)在1900年以前的拼法。这是整个搜索中唯一引人遐想的片段。

它出现在单词分隔符那次运行中,那次运行的其他整词命中只有 er、elle 和 unge。涉及的三个符号占103个记号中的34个,所以这一指派固定了三分之一的文本。但它无法延伸。那次解密的其余部分都是噪声,整次运行每字母得分−4.6,而真实文本在同一模型下约为−2.3。

07 现状及重启的条件

未破解 就现有扫描件而言已结案。有三条途径可以重启。

08 文件

所有内容都在项目仓库的 targets/copenhagen/ 中。 cipher.txt 是合并后的20符号读法,文件头中注明了记号约定; cipher_sw.txt 是与之对齐的 ShadowWolf 25符号读法。 solve.py 是带词典重排序的5-gram求解器(模拟退火), solve_sp.py 是单词分隔符测试, control.py 是匹配对照的测试框架。 NOTES.md 保存了这次尝试的完整记录。Gutenberg 语料不在仓库中;Gutendex id 记在会话日志里。