【寫在前面】這篇文章是我?guī)煾底罱难獊沓睂懙模饕榻B如何在無密鑰的情況下使用數(shù)學方法破解維吉尼亞密碼,一共分為三個部分,會預(yù)設(shè)讀者——也就是你——已經(jīng)了解了維吉尼亞密碼的基本操作方法,并具備一定的數(shù)學知識。當然,如果你完全不了解這些,他在最后也附上了破解的操作性方法和部分C++代碼以供使用。不過,如果你能聽得懂前面的內(nèi)容,建議還是聽一聽,這樣如果破解的時候出了問題,自己就能排查。
第一部分:如何把維吉尼亞密碼寫成誰也看不懂的樣子
為了讓計算機也能處理維吉尼亞密碼的密文,我們要先定義這種加密方式。為了更通俗易懂地說明,我們先從凱撒密碼開始。
代換密碼的本質(zhì)是用另一個符號代替明文中的符號,即明文所使用的符號集 和密文所使用的符號集 是一一對應(yīng)的雙射關(guān)系。用數(shù)學語言表示就是:對于映射 ,如果集合 中的任意兩個不同元素 ,都有 ,且對于集合 中的任一元素 ,都有集合 中的元素 使得 ,那么這個映射 就是雙射。
破解密碼的過程實質(zhì)上就是尋找這兩個集合之間的對應(yīng)關(guān)系,即求解逆映射 的過程。在凱撒密碼中,代換方式是使用26個字母相互替換,因此可以表示為英文字母集的自映射,即 。凱撒密碼的加密方式是:對于集合 中的某一個字母,都使用其后一個字母來替換(如果是字母z,那么使用字母a來替換)。因此,我們可以先將字母用數(shù)字代替,然后使用一個定義在模26剩余類加群上的函數(shù)來表示凱撒密碼。具體定義過程如下:
首先,令a=0,b=1,c=2,以此類推到z=25,,這樣一來,凱撒密碼的加密方式就基本等同于“+1”,只有當“25+1=26”后,才需要將26換成0。因此,我們可以用“取余”的方式來統(tǒng)一定義:“(25+1)mod 26=0”。
所以其次,定義一個剩余類群 ,其代數(shù)運算為模26剩余類加法,即對于任意兩個元素 ,都有 。
這樣一來,我們就能定義凱撒密碼的加密方式了:存在映射 ,使得任意的明文 都存在密文 滿足 ,其中映射 的具體形式為 。所以,反過來講,其解密方式為逆映射 。
當然,繼凱撒密碼之后,我們還可以衍生出類似的加密方式,比如每次不再用b替代a,而是用c替代a,即向后移動兩位來加密,那么這時候的映射 就變?yōu)?a id="gallery_post_O3Xwg" data-fancybox="gallery-post-1355992"> 。我們可以定義一個“密鑰”的概念來統(tǒng)合這些升級版本:設(shè)密鑰為自然數(shù) ,那么可以將凱撒密碼升級為 。
類似地,代換密碼都可以用 的形式來定義,例如有名的仿射密碼,其密鑰為定義在 上的二元數(shù)組 ,加密方式為 ,解密方式為 。當然,這不在我們今天的討論范圍之內(nèi)。
注意,上面的 、 都是代表單個字母,如果你要加密解密一整段文字,那么需要不斷重復(fù)這個過程。當然,我們也可以直接用向量的方式來表示對一整段文字的凱撒加密:即定義n維列向量 ,令密鑰為n維列向量 ,其中 ,加密方式就能寫成 。
準備工作完成,我們可以定義維吉尼亞密碼了。
維吉尼亞密碼也是凱撒密碼的升級版,即使用一個密鑰令明文中的不同字母使用不同的凱撒移位來加密。例如,密鑰是LOVE——對應(yīng)的數(shù)學表示就是(11,14,21,4),明文是Do you like apples——對應(yīng)的數(shù)學表示就是(3,14,24,14,20,11,8,10,4,0,15,15,11,4,18)。對于第一個字母D,使用密鑰中的L來加密,即(3+11)mod 26=14,代換為O;對于第二個字母o,使用密鑰中的O來加密,即(14+14)mod 26=2,代換為c;以此類推,直到第五個字母,再回頭使用密鑰中的L來加密,代換為f,以此循環(huán)往復(fù),直至寫出密文Oc tsf zdop oktwsn。
根據(jù)上述例子,我們將密鑰拓展到和明文長度對應(yīng)后(例如,將密鑰擴寫成LOVELOVELOVELOV,長度和明文Do you like apples一致),就能定義維吉尼亞密碼:對于映射 ,存在密鑰 ,使得對于任意的明文 ,都存在密文 ,使得 。
完成了數(shù)學上的定義后,下一個問題是,如何破解呢?
第二部分:惟密文攻擊法,又名坑爹上司連個密鑰都沒法給我搞到讓我怎么開展工作
相信密碼圈的朋友乃至是一般的網(wǎng)推圈朋友都聽說過“字頻統(tǒng)計法”。對于一段有意義文本,每個字母出現(xiàn)的頻率是不一樣的。例如,在英文中,e的使用頻率最高,z、q、x等就很少使用,以及某些字母在詞尾常見/罕見,某兩個或三個字母的組合非常常見(如on、the等),等等等等。1970年Dewey,G統(tǒng)計了約438023個字母得到了一份英文字母頻率表(下稱“26字母頻率表”):字母 | 頻率 | 字母 | 頻率 | 字母 | 頻率 | A | 0.0788 | J | 0.0010 | S | 0.0634 | B | 0.0156 | K | 0.0060 | T | 0.0978 | C | 0.0268 | L | 0.0394 | U | 0.0280 | D | 0.0389 | M | 0.0244 | V | 0.0102 | E | 0.1268 | N | 0.0706 | W | 0.0214 | F | 0.0256 | O | 0.0776 | X | 0.0016 | G | 0.0187 | P | 0.0186 | Y | 0.0202 | H | 0.0573 | Q | 0.0009 | Z | 0.0006 | I | 0.0707 | R | 0.0594 | | |
這些規(guī)律有助于我們?nèi)ゲ聹y單表加密的文本。例如,如果你看到一段文本中,經(jīng)常出現(xiàn)“wkh”這個組合,那么你就可以猜測這是移位3的凱撒加密,wkh=the;又或者,你統(tǒng)計了整段文本,發(fā)現(xiàn)a出現(xiàn)的頻率遠高于其他字母,那么你可以大膽猜測a=e。假設(shè),驗證,根據(jù)詞義和構(gòu)詞規(guī)則不斷發(fā)展,逐漸地解開一段密文。這就是字頻統(tǒng)計法。
然而,字頻統(tǒng)計法的前提是,密文本身沒有破壞符號的分布規(guī)律。但對于維吉尼亞這種不同字母使用不同凱撒的加密方式,你就沒法單純從密文文本的統(tǒng)計數(shù)據(jù)來進行推測了。因此,我們需要更高端的方法。
首先需要確定的是密鑰長度:如果能知道密鑰長度  ,那么就可以將整個文本拆分為  組(例如第1個、第  個、第  個等等為一組),每一組就等于是一種凱撒加密。因此,首先介紹的是“克思斯基測試”。
因為密鑰是周期性循環(huán)的,而文本中又常常會有the、and、for這類常見的詞組,因此,我們期望一段長文本中,總會有那么幾次恰好是同樣的密鑰字母加密了同樣的詞組,得到了一樣的字母組合。反過來講,若密文中出現(xiàn)兩個相同的字母組,它們所對應(yīng)的明文字母組未必相同,但相同的可能性很大。因此,克思斯基測試就是,將密文中相同的字母組找出來,并對其相同字母數(shù)綜合研究,找出它們的相同字母數(shù)的最大公因子,就有可能提取出有關(guān)密鑰字的長度  的信息。
我們結(jié)合一個栗子來講解。下面是一段維吉尼亞加密的文本:
CHREEVOAHMAERATBIAXXWTNXBEEOPHBSBQMQEQERBWRVXUOAKXAOSXXWEAHBWGJMMQMNKGRFVGXWTRZXWIAKLXFPSKAUTEMNDCMGTSXMXBTUIADNGMGPSRELXNJELXVRVPRTULHDNQWTWDTYGBPHXTFALJHASVBFXNGLLCHRZBWELEKMSJIKNBHWRJGNMGJSGLXFEYPHAGNRBIEQJTAMRVLCRREMNDGLXRRIMGNSNRWCHRQHAEYEVTAQEBBIPEEWEVKAKOEWADREMXMTBHHCHRTKDNVRZCHRCLQOHPWQAIIWXNRMGWOIIFKEE
可以發(fā)現(xiàn),CHR這個字母組出現(xiàn)了足足五次,因此我們可以猜測這恰好是同樣的詞組被同樣的密鑰給加密了。因此,數(shù)出它們中間間隔的字母個數(shù),求取最大公因數(shù),很可能就是密鑰的長度。例如,這段文本中的五個CHR中的每個C之間間隔165、70、40、10個字母,最大公因數(shù)為5,因此猜測密鑰長度  。
但是,克思斯基測試也有缺陷,至少它不能排除有其他詞組被其他密鑰加密成同樣的CHR的可能性。而且,如果找不到足夠多的重復(fù)字母組,那么克思斯基測試也等于無用。所以我們需要新的工具。
我們假設(shè)一門語言由  個字母構(gòu)成,每個字母發(fā)生的概率為  ,其中  ,那么用概率論的語言來講,就是將你寫下一個字母視為一個事件,該事件的隨機變量  一共有  種取值  、  、……、  ,其分布律為  。如果是完全隨機的文本,那么所有  的值應(yīng)該是一樣的,都是  ;但前面提到,有意義的英文文本中每個字母出現(xiàn)的概率并不一致。
接下來,我們定義一個名叫“重合指數(shù)”(Coincidence Index)的概念。
我們知道字母e在英文文本中出現(xiàn)的概率是12.68%,那么如果在一段長文本中,隨機選取兩個字母,發(fā)現(xiàn)都是e的概率是多少呢?沒錯,是  。類似地,對于任一概率為  的字母  ,隨機選取兩個字母后都是  的概率為  。因此,對于一段文本,隨機選擇其中兩個字母,它們相同的概率為  。我們將這個數(shù)據(jù)記為重合指數(shù)  。對于一段完全隨機的英文字母文本,  ,而一段有意義的英文文本的  。
重合指數(shù)  可以用來描述這段文本的分布律,你可以將其類比為一種“特征值”。如果密文的重合指數(shù)非常接近0.065,那么說明它使用了單表替換;如果兩段密文的重合指數(shù)相似,那么說明它們使用了同一種代換加密方式。
當然,在處理實際密文的時候,我們不可能知道在這種加密方式下每種字母出現(xiàn)的真實概率  ,所以往往會用其估計值  來替代,其中  為密文長度,  為某個字母  出現(xiàn)的頻次。這個估計值是怎么得到的呢?這就要用到組合數(shù)學的知識了!從  個字母中選擇兩個,共有  種方法,而兩個字母都是  的選法有  種,所以,滿足“兩個字母相同”的選法共有  種。因此,選到兩個字母相同的概率即為“能讓兩個字母相同的選法”比上“隨便選兩個字母的選法”,得到  。
好,我們把話題扯回來。那么,面對一段維吉尼亞密文,我們就可以嘗試將其進行分組,然后計算不同組的重合指數(shù),如果各組的重合指數(shù)平均看下來很接近0.065,那就說明這種分組方法是正確的。例如,我們將上面那段密文5份5份地切開,然后將每份的第一個字母組成一組,每份的第二個字母組成一組,以此類推組成5組,計算其重合指數(shù):0.06298、0.0681004、0.0686124、0.0608144、0.0724484,平均約為0.06659,比其他分組方法得到的數(shù)據(jù)更接近0.065,說明密鑰長度更有可能是5。
解決了密鑰長度  的問題后,我們將密文分為  組,每組內(nèi)都使用一種單一的凱撒加密:
第一組:CVABWEBQBUAWWQRWWXANTBDPXXRDWBFAXCWMNJJFAIACNRNCATBWKDMCDCQQXWK
第二組:HOEITESEWOOEGMFTIFUDSTNSNVTNDPASNHESBGSEGEMRDRSHEAIEORTHNHOANOE
第三組:RARANOBQRASAJNVRAPTCXUGRJRUQTHLVGRLJHNGYNQRRGINRYQPVEEBRVRHIRIE
第四組:EHAXXPQEVKXHMKGZKSEMMIMEEVLWYXJBLZEIWMLPRJVELMRQEEEKWMHTRCPIMI
第五組:EMTXBHMRXXXBMGXXLKMGXAGLLPHTGTHFLBKKRGXHBTLMXGWHVBEAAXHKZLWWGF
考慮到每組對應(yīng)的明文的可能性只有26種,所以我們需要暴力的范圍一下子從  (  為密文全長)降低到了  。可喜可賀,可喜可賀……個屁嘞!
此時字頻統(tǒng)計依舊不能用——因為每組內(nèi)的密文都是間隔  個字母取到的,根本沒法猜測詞組、詞尾字母頻數(shù)等,而文本量的驟然減少也讓對單個字母的字頻統(tǒng)計變得不夠準確(說不定這組密文對應(yīng)的明文中,a出現(xiàn)的次數(shù)反而比e多呢?),導(dǎo)致原本在大文本的情況下使用的“邊猜邊湊”的方法已經(jīng)不能用了。如果是暴力破解,那等于是每組都要把26種位移可能性都試一遍,試個  組,這依舊不是人類能完成的工程。
那么有什么思路可以使用嗎?既然單個字頻不靠譜,那我們可不可以整體性地將密文字母的分布律和“26字母頻率表”的分布律相比較?雖然可能在單個字母的概率上會有些許出入,但只要整體的那張“圖景”足夠相似不就好了?
你或許會問,對于每一組密文,我們都有26種位移可能,即便有時間將它們和26字母頻率表一一比較,我們又該憑什么斷言某一種位移可能比另一種要更“像”26字母頻率表呢?
還記得前面提到的重合指數(shù)法嗎?只不過,前面是在同一段文本中隨機選取兩個字母,這次是在兩段文本中各隨機選取一個字母,這樣計算出來的“擬重合指數(shù)”就是  ,其中  就是密文的某一種位移后得到的字母的頻率,  就是字母的一般概率,也就是上面的26字母頻率表。這個“擬重合指數(shù)”越大,說明兩段文本的字母的分布律越相像。而且據(jù)我的經(jīng)驗來看,一般最大的那個指數(shù)也接近0.065。
因此,破譯的“擬重合指數(shù)法”就分為五個步驟:
第一步,統(tǒng)計每組密文中每個字母的頻數(shù)  、  、……、  。
第二步,計算每組密文的長度  ,計算26個字母在該組中的頻率分布  、  、……、  。
第三步,找到一般文本的字母概率分布(即上面的頻率表)  、  、……、  。
第四步,對于頻率分布  進行移位,得到26種排序。如果我草稿沒打錯,當位移量為  的時候,原本的排序  、  、……、  就會變?yōu)?a id="gallery_post_Sv7rx" data-fancybox="gallery-post-1355992">  、  、……、  、  、  、……、  ,可將這種排序設(shè)為26維向量  ,將第三步中的一般頻率的排序設(shè)為向量  。
第五步,計算26個相關(guān)值  ,其中最大的值所對應(yīng)的  即為該組密文的位移量。
只要按照這五步驟,將每組的位移量都找到,就可以拼湊出密鑰,獲得明文。
還是拿前面的例子,對于第一組字母,首先獲得  ,自然也就能得到其他25種排序。接著計算相關(guān)值,得到下表: k | 0 | 1 | 2 | 3 | 4 | 5 | CI | 0.0347667 | 0.0310079 | 0.0356968 | 0.0382286 | 0.0358683 | 0.0389302 | k | 6 | 7 | 8 | 9 | 10 | 11 | CI | 0.0273635 | 0.0281905 | 0.0490778 | 0.0616127 | 0.0390984 | 0.0316079 | k | 12 | 13 | 14 | 15 | 16 | 17 | CI | 0.0397143 | 0.0381111 | 0.0380333 | 0.0448175 | 0.0354571 | 0.0300444 | k | 18 | 19 | 20 | 21 | 22 | 23 | CI | 0.0421222 | 0.0422667 | 0.035781 | 0.0332317 | 0.0486841 | 0.043381 | k | 24 | 25 | | | | | CI | 0.0415127 | 0.0356937 | | | | |
可以發(fā)現(xiàn),除了  時相關(guān)值約為0.06,其他時候相關(guān)值都在0.03或0.04左右徘徊。因此可以確定,第一組的位移量為9,即密鑰的首位是J。
以此類推,我們可以得到密鑰的五位分別是9、0、13、4、19,即JANET。而明文為:
THE ALMOND TREE WAS IN TENTATIVE BLOSSOM. THE DAYS WERE LONGER OFTEN ENDING WITH MAGNIFICENT EVENINGS OF CORRUGATED PINK SKIES. THE HUNTING SEASON WAS OVER, WITH HOUNDS AND GUNS PUT AWAY FOR SIX MONTHS. THE VINEYARDS WERE BUSY AGAIN AS THE WELL-ORGANIZED FARMERS TREATED THEIR VINES AND THE MORE LACKADAISICAL NEIGHBORS HURRIED TO DO THE PRUNING THEY SHOULD HAVE DONE IN NOVEMBER.
第三部分:保姆式操作說明與部分C++代碼
要在沒有密鑰的情況下破解維吉尼亞密碼的步驟:
第一步:使用克思斯基測試或重合指數(shù)法,確定密鑰長度;
第二步:根據(jù)密鑰長度對密文進行分組;
第三步:對每組密文使用擬重合指數(shù)法,確定位移量;
第四步:寫出明文。
以下是我寫的C++代碼,可自行取用。當然,如果你聽懂了前面兩部分,那你自己也能寫。另外,如果密文長度太短,則也不適合用這個方法,因為統(tǒng)計得到的字母頻率和正常的概率偏差過大,導(dǎo)致相關(guān)系數(shù)不夠準確,此時建議大家直接暴力。
#include<iostream>#include<cstring> usingnamespace std; intmain() { //這是我看到的另一種字母概率表,你可以用它替換下一行使用:const float q[26] = {8.17,1.49,2.78,4.25,12.70,2.23,2.02,6.09,6.97,0.15,0.77,4.03,2.41,6.75,7.51,1.93,0.10,5.99,6.33,9.06,2.76,0.98,2.36,0.15,1.97,0.07}; const float q[26] ={7.88,1.56,2.68,3.89,12.68,2.56,1.87,5.73,7.07,0.10,0.60,3.94,2.44,7.06,7.76,1.86,0.09,5.94,6.34,9.78,2.80,1.02,2.14,0.16,2.02,0.06};//英文字母的概率 char t[500];//用于存儲密文 int s; int ss; cout << "密文是大寫字母請輸入1,是小寫請輸入0:"; cin >> ss; if (ss==1) { s = 65; } else { s = 97; }//區(qū)分大小寫 cout << "請輸入密文:"; cin >> t;//輸入密文 const int long_of_text = strlen(t);//確定密文長度 int m_min; int m_max; int m;//實際的密鑰長度,之后有用 cout << "請輸入密鑰長度范圍(最小值):"; cin >> m_min; cout << "請輸入密鑰長度范圍(最大值):"; cin >> m_max; for (int fenzu=m_min; fenzu<=m_max;fenzu++) { int number_of_zu = long_of_text /fenzu; int shen_yu = long_of_text % fenzu; if (shen_yu != 0) { number_of_zu++; } else { shen_yu = fenzu; }//完成分組 int mi_wen[number_of_zu][fenzu]; int fenchai = 0; for (int row=0; row<number_of_zu;row++) { for (int col=0; col<fenzu;col++) { if (fenchai<long_of_text)//fenchai的編號是從0開始,所以不能用小于等于 { mi_wen[row][col] =t[fenchai] - s;//因為大寫字母A的數(shù)字是65 } fenchai++; } }//將密文轉(zhuǎn)化為數(shù)字,存進二維數(shù)組中 float CI_guji = 0.0; for (int col=0; col<fenzu; col++) { int chang_du_L = number_of_zu; if (col>=shen_yu) { chang_du_L--; }//得到該組實際的密文長度 int f[26] = {0};//用于計算字頻 for (int row=0; row<chang_du_L;row++) { f[mi_wen[row][col]]++; }//計算每個字母的頻數(shù) for (int i=0; i<26; i++) { CI_guji = CI_guji +1.0*f[i]*(f[i]-1)/chang_du_L/(chang_du_L-1); } }//針對每一組,進行重合指數(shù)分析 CI_guji = CI_guji / fenzu; cout << "m=" <<fenzu << ":" << CI_guji << endl; } cout << "請輸入最接近0.065的m:"; cin >> m;//輸入密鑰長度 cout << "密鑰為:"; //不要問我為什么不把下面這段和上面一模一樣的內(nèi)容做成函數(shù),因為我不知道怎么返回數(shù)組。咱只是一個數(shù)學系的半吊子,其實根本不懂寫代碼 int number_of_zu = long_of_text / m; int shen_yu = long_of_text % m; if (shen_yu != 0) { number_of_zu++; } else { shen_yu = m; } int mi_wen[number_of_zu][m];//存儲密文的二維數(shù)組 int ming_wen[number_of_zu][m];//將用于存儲明文的二維數(shù)組 int fenchai = 0; for (int row=0; row<number_of_zu; row++) { for (int col=0; col<m; col++) { if (fenchai<long_of_text) { mi_wen[row][col] = t[fenchai] - s; } fenchai++; } } for (int col=0; col<m; col++) { int chang_du_L = number_of_zu; if (col>=shen_yu) { chang_du_L--; } int f[26] = {0};//字母頻數(shù) float p[26] = {0.0};//字母頻率 for (int row=0; row<chang_du_L;row++) { f[mi_wen[row][col]]++; } for (int i=0; i<26; i++) { p[i] = 1.0 * f[i] / chang_du_L; }//計算每個字母的頻率 double corr = 0.0;//相關(guān)值卷積 double corr_max = 0.0;//最大相關(guān)值 int max_number = 0;//最大對應(yīng)的位移數(shù) float pp[26] = {0.0};//用于移動26個字母頻率的不同序列 for (int k=0; k<26; k++) { for (int i=0; i<26; i++) { pp[i] = p[(k+i)%26]; } for (int j=0; j<26; j++) { corr = corr + pp[j] *(q[j]/100); } if (corr > corr_max) { corr_max = corr; max_number = k; } corr = 0; } for (int i=0; i<chang_du_L; i++) { ming_wen[i][col] = (mi_wen[i][col]+ 26 - max_number) % 26; } char key = max_number + s; cout << key; } for (int row=0; row<number_of_zu; row++) { for (int col=0; col<m; col++) { t[row*m+col] = ming_wen[row][col] +s; } } cout << endl << "明文為:"<< t; //實際輸出的明文末尾可能會帶有幾個亂碼,但不影響前面的內(nèi)容 return 0; }
下面是本文的例子的輸出結(jié)果:
密文是大寫字母請輸入1,是小寫請輸入0:1
請輸入密文:CHREEVOAHMAERATBIAXXWTNXBEEOPHBSBQMQEQERBWRVXUOAKXAOSXXWEAHBWGJMMQMNKGRFVGXWTRZXWIAKLXFPSKAUTEMNDCMGTSXMXBTUIADNGMGPSRELXNJELXVRVPRTULHDNQWTWDTYGBPHXTFALJHASVBFXNGLLCHRZBWELEKMSJIKNBHWRJGNMGJSGLXFEYPHAGNRBIEQJTAMRVLCRREMNDGLXRRIMGNSNRWCHRQHAEYEVTAQEBBIPEEWEVKAKOEWADREMXMTBHHCHRTKDNVRZCHRCLQOHPWQAIIWXNRMGWOIIFKEE
請輸入密鑰長度范圍(最小值):1
請輸入密鑰長度范圍(最大值):9
m=1:0.0450152
m=2:0.0432958
m=3:0.0466458
m=4:0.0416841
m=5:0.0665911
m=6:0.0438283
m=7:0.0424544
m=8:0.0420378
m=9:0.0455366
請輸入最接近0.065的m:5
密鑰為:JANET
明文為:THEALMONDTREEWASINTENTATIVEBLOSSOMTHEDAYSWERELONGEROFTENENDINGWITHMAGNIFICENTEVENINGSOFCORRUGATEDPINKSKIESTHEHUNTINGSEASONWASOVERWITHHOUNDSANDGUNSPUTAWAYFORSIXMONTHSTHEVINEYARDSWEREBUSYAGAINASTHEWELLORGANIZEDFARMERSTREATEDTHEIRVINESANDTHEMORELACKADAISICALNEIGHBORSHURRIEDTODOTHEPRUNINGTHEYSHOULDHAVEDONEINNOVEMBER礱
Process returned 0 (0x0) execution time : 19.595 s
Press any key to continue. |