不是所測任何語言的簡單替換 就現有掃描件而言已結案
用一個以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 | 數字3 | 16 |
n | 普通的 n | 13 |
B | 後面帶點的反斜線(\.) | 14 |
F | 後面帶點的斜線(/.) | 8 |
P | 加號 | 6 |
A | 尖角或 lambda(^) | 6 |
D | 位於筆畫之前的獨立圓點 | 8 |
o | 小圓字母 o | 5 |
2 | 數字2 | 5 |
0 | 高的零 | 5 |
v | 照寫 | 4 |
9 | 數字9 | 3 |
1 | 數字1 | 2 |
8 | 數字8 | 2 |
7 6 X | 照寫 | 各1次 |
N | 上方帶兩點的 n | 1 |
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.69 | fxsisted tine togeteom scie ... |
| 丹麥語 | −2.77 | junindel dige dskedesf ... / ... udstødes ... velsigne ... |
| 挪威語 | −2.81 | bærersag sena stjasatm ... |
| 瑞典語 | −2.81 | honensam sera stfasatv ... |
| 中文釋義 | −2.88 | cydidsea sine stbesetf ... |
| 德語 | −2.93 | uchthien itze irbeierm ... |
| 法語 | −2.97 | chiailes 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次。準確率是復原出的段落所佔比例。
| 語言 | 試驗 | 金鑰中的字母數 | 準確率 | 找到的得分 | 真實文字的得分 | 結果 |
|---|---|---|---|---|---|---|
| 丹麥語 | 1 | 19 | 0.99 | −1.99 | −1.85 | 已復原 |
| 丹麥語 | 2 | 19 | 0.99 | −1.93 | −1.96 | 已復原 |
| 丹麥語 | 3 | 18 | 0.99 | −1.85 | −1.76 | 已復原 |
| 德語 | 1 | 22 | 0.96 | −1.88 | −1.78 | 已復原 |
| 德語 | 2 | 19 | 0.79 | −2.64 | −1.75 | 部分 |
| 德語 | 3 | 22 | 0.14 | −3.42 | −1.81 | 搜尋失敗 |
| 中文釋義 | 1 | 20 | 1.00 | −1.44 | −1.44 | 已復原 |
| 中文釋義 | 2 | 21 | 0.99 | −1.99 | −1.85 | 已復原 |
| 中文釋義 | 3 | 20 | 1.00 | −1.67 | −1.67 | 已復原 |
| 拉丁語 | 1 | 18 | 1.00 | −2.09 | −2.09 | 已復原 |
| 拉丁語 | 2 | 18 | 1.00 | −1.74 | −1.74 | 已復原 |
| 拉丁語 | 3 | 19 | 0.99 | −1.87 | −1.72 | 已復原 |
| 瑞典語 | 1 | 22 | 1.00 | −1.95 | −1.95 | 已復原 |
| 瑞典語 | 2 | 22 | 0.05 | −3.49 | −1.59 | 搜尋失敗 |
| 瑞典語 | 3 | 22 | 1.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 現狀及重啟的條件
未破解 就現有掃描件而言已結案。有三條途徑可以重啟。
- 原始紙條或更好的掃描件。ACA 是從博物館收到這張紙條的,所以應去 ACA 檔案或 Kent Ramliden 的遺稿中查詢。第二張影象就能解決第02節中拆分還是合併的問題。
- 那幅畫。哥本哈根一座軍事博物館中一幅1835年丹麥將軍的肖像,指向 Tøjhusmuseet,即今天的丹麥戰爭博物館。查明這位將軍,就能得到一個名字、一個日期和一種語言。
- 速記或私人程式碼。如果這些筆畫和圓點是速記或私人程式碼而不是字母,那麼任何替換模型都不適用,而這裡沒有任何東西檢驗了這種可能。
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 記在會話日誌裡。