維吉尼亞密碼
(又譯維熱納爾密碼)是使用一系列凱撒密碼組成密碼字母表的加密算法,屬於多表密碼的一種簡單形式。
維吉尼亞密文是通過明文加上密鑰,根據維吉尼亞密碼表來生成密文。

維吉尼亞的密碼強度是跟密鑰的長度有關,或者你可以用幾個密鑰進行加密,如果幾個密鑰進行加密,那么我們盡量讓不同密鑰的長度互質,
如果明文不長,當密鑰長度和明文一樣長,理論上是不可破譯。
然而,實際上很那做到密鑰長度和明文長度一樣長,因此這就給維吉尼亞密碼破譯提供了可能。
1、首先,我們從維吉尼亞密碼加密方式可以發現,假如密鑰的長度為 k ,那么明文中第 Xi ,Xi+k ,Xi+2*k,……是不是用同一個密碼加密,那么不就是凱撒密碼嗎? 那么問題來了,我們怎么獲取到密鑰長度是多少呢?
2、Kasiski 實驗
為了獲取到密鑰長度,我們需要進行Kasiski 實驗,什么是Kasiski 實驗呢?
假如有一段密文: ACEBTSSTRCESQSSTQRCK
那么我們從中挑選出至少三個字符以上相同的字串,並且比較他們相鄰兩個字串相鄰位置之差:比如密文中 "SST",它們相差了8。
因為在加密過程中,出現至少三個字符以上相同的字串,明文不同的概率是很小的,那么我們是不是可以知道,密鑰的長度一定是8的因子。
如果還能找到其他至少三個字符以上相同的字串,那么密鑰長度是它們的最大公約數的概率非常大。
到目前為止我們可能已經有好幾種密鑰長度的可能了,那么怎么來確定密鑰長度呢?或者說怎么求出密鑰,獲取明文呢?
3、重合指數攻擊
設一門語言由n個字母組成,每個字母出現的概率為 Pi ,則重合指數是指兩個元素隨機相同的概率之和,記作 CI =∑ Pi2 (1<= i <= n);
經分析,英文中,一段文字是隨機的話,CI =0.0385 ;如果這段文字是有意義的,那么 CI=0.065 (約等)。實際上計算的CI應該用這個公式
L:密文長度; fi :在密文中的出現次數。 (公式來源)
這個有什么好處呢?
好處就是用我們猜測的密鑰長度來進行分組,分別計算每組的CI,再求個平均,計算當前密鑰長度下,CI 的值與0.065相差多少。然后按照最接近0.065的密鑰長度進行排序,為了提高解密的成功率,一般會取前5~10個較為接近的密鑰長度作為猜測。
4、字母頻率分析
密鑰長度知道了,然而怎么獲取密鑰到底是多少呢?
還是根據統計學:我們可以知道每個字母在英文中的頻率
正常的文本中,每個字母出現的頻率是遵循上述規則。
那么破解密鑰就變得很簡單了,我們統計在某個密鑰長度下的密各個組的字母頻率,這樣對單個組來說,就是凱撒密碼,我們循環26次,判斷哪種情況下字母頻率與統計字母頻率的內積最大,即 R=∑Pi*Qi ('a'<= i <='z') 。
這樣我們對密鑰的某個單個字符破解出來了,同理我們可以破解出密鑰。
最后從你程序輸出的幾組結果進行人工判別一下,哪個是有意義的明文。
(轉載請注明出處Thanks♪(・ω・)ノ)
C++源程序:
1 #include<bits/stdc++.h>
2 using namespace std; 3 struct Node{ 4 float value; //重合指數差,與我們的標准重合指數的差值越小越好
5 int length; 6 }; 7 vector< Node > key; //存放key可能的長度和重合指數差
8 set< int > key_len; //存放key可能的長度
9 /*
10 英文字母使用頻率表 g 11 */
12 double g[]={0.08167, 0.01492, 0.02782, 0.04253, 0.12702, 0.02228, 0.02015, 0.06094, 0.06966, 0.00153, 0.00772, 0.04025,0.02406, 0.06749, 0.07507, 0.01929, 0.00095, 0.05987, 0.06327, 0.09056, 0.02758, 0.00978, 0.02360, 0.00150,0.01974, 0.00074}; 13 bool Greater_sort(Node a,Node b){ 14 return a.value<b.value; 15 } 16 /*
17 Coincidence_index,計算所選分組的重合指數 18 start表示分組的起點,length表示步長 19 重合指數CI的實際估計值是 20 X(i)=F(i)*(F(i)-1)/sum*(sum-1) 21 ('a'<=i<='z',F(i)為i字符在當前分組出現的次數) 22 對上述X(i)求和就是整個分組的重合指數CI 23 */
24 float Coincidence_index(string cipher,int start,int length){ 25 float index=0.000; 26 int sum=0; 27 int num[26]; 28 memset(num,0,sizeof(num)); 29 while(start<=cipher.length()){ 30 num[cipher[start]-'a']++; 31 start+=length; 32 sum++; 33 } 34 for(int i=0;i<26;i++){ 35 if(num[i]<=1) continue; 36 index+=(float)(num[i]*(num[i]-1))/(float)((sum)*(sum-1)); 37 } 38 return index; 39 } 40 /*
41 Find_same()函數即是根據 kasiski測試法的原理 42 我們可以獲取key可能的長度 43 */
44 void Find_same(string cipher){ 45 for(int i=3;i<5;i++){ 46 for(int j=0;j<cipher.length()-i;j++){ 47 string p=cipher.substr(j,i); 48 for(int k=j+i;k<cipher.length()-i;k++){ 49 string tmp=cipher.substr(k,i); 50 if(tmp==p){ 51 Node x; 52 x.length=k-j; 53 key.push_back(x); 54 } 55 } 56 } 57 } 58 } 59 int gcd(int a,int b){ 60 if(b==0) return a; 61 else return gcd(b,a%b); 62 } 63 /*
64
65 求出可能的key的值的最大公因子 66 經過重合指數檢驗,對key的長度進行排序 67
68 */
69 void Get_key(string cipher){ 70 Find_same(cipher); 71 for(int i=0;i<key.size();i++){ 72 int x=key[i].length; 73 for(int j=0;j<key.size();j++){ 74 if(key[i].length>key[j].length) 75 key_len.insert(gcd(key[i].length,key[j].length)); 76 else
77 key_len.insert(gcd(key[j].length,key[i].length)); 78 } 79 } 80 key.clear(); 81 set< int >::iterator it=key_len.begin(); 82 while(it!=key_len.end()){ 83 int length=*it; 84 if(length==1){ 85 it++; 86 continue; 87 } 88 float sum=0.000; 89 cout<<length<<" "; 90 for(int i=0;i<length;i++){ 91 cout<<Coincidence_index(cipher,i,length)<<" "; 92 sum+=Coincidence_index(cipher,i,length); 93 } 94 cout<<endl; 95 Node x; 96 x.length=length; 97 x.value=(float)fabsf(0.065-(float)(sum/(float)length)); 98 if(x.value<=0.1) 99 key.push_back(x); 100 it++; 101 } 102 sort(key.begin(),key.end(),Greater_sort); 103 } 104 /*
105
106 為了提高解密的成功率,我們取前面10個公因子進行求解 107 對每個公因子的每個分子進行字母的擬重合指數分析 108 由Chi測試(卡方檢驗),獲取峰值點 109 該峰值點極有可能是明文 110
111 */
112 void Get_ans(string cipher){ 113 int lss=0; 114 while(lss<key.size()&&lss<10){ 115 Node x=key[lss]; 116 int ans[cipher.length()]; 117 memset(ans,0,sizeof(ans)); 118 map< char ,int > mp; 119 for(int i=0;i<x.length;i++){ 120 double max_pg=0.000; 121 for(int k=0;k<26;k++){ 122 mp.clear(); 123 double pg=0.000; 124 int sum=0; 125 for(int j=i;j<cipher.length();j+=x.length){ 126 char c=(char)((cipher[j]-'a'+k)%26+'a'); 127 mp[c]++; 128 sum++; 129 } 130 for(char j='a';j<='z';j++){ 131 pg+=((double)mp[j]/(double)sum)*g[j-'a']; 132 } 133 if(pg>max_pg){ 134 ans[i]=k; 135 max_pg=pg; 136 } 137 } 138 } 139 cout<<endl<<"key_length: "<<x.length<<endl<<"key is: "; 140 for(int i=0;i<x.length;i++){ 141 cout<<(char)((26-ans[i])%26+'a')<<" "; 142 } 143 cout<<endl<<"Clear text:"<<endl; 144 for(int i=0;i<cipher.length();i++){ 145 cout<<(char)((cipher[i]-'a'+ans[i%x.length])%26+'a'); 146 } 147 cout<<endl; 148 lss++; 149 } 150 } 151 int main(){ 152 string cipher; 153 cin>>cipher; 154 transform(cipher.begin(), cipher.end(), cipher.begin(),::tolower); 155 Get_key(cipher); 156 for(int i=0;i<key.size();i++){ 157 cout<<key[i].length<<" and "<<key[i].value<<endl; 158 } 159 Get_ans(cipher); 160 return 0; 161 }
