维吉尼亚密码破解
思路
主要就是计算重合指数来进行破解
遍历尝试秘钥长度
在确定秘钥长度的基础上,对于每一个分组尝试计算偏移量——相当于破解单个的凯撒密码,用到的也是重合指数的计算
代码分析
在代码所在的文件夹中"密文.txt","明文.txt"分别存放要破解的密文和破解得到的明文,通过文件输入输出函数进行处理。用到向量将密文的字母全部读入。
int main()
{
char * name[3]={NULL,NULL,NULL};
name[0]="密文.txt";
name[1]="明文.txt";
if( !name[0] )
{
cout<<"cracker: too few parameters"<<endl;
//printUsage();
return 0;
}
vector<char> m;
//读入文章内容
readFile(name[0],m);
//破解秘钥
crack(name[0],"cracked.txt",1);
//破解密文
decrypt(name[0],name[1],key);
return 0;
}
然后先尝试不同的秘钥长度下,重合指数和统计学的重合指数的方差,遍历1~256不同的长度的可能秘钥长度,认为方差最小的秘钥长度就为实际的秘钥长度。
//mode==1的时候是计算重合指数,mode==0的时候是检验一定偏移的时候重合指数是否符合要求
double calcKasiski(vector<char> seq, int mode = 1)//mode 0:check mode
{
int freq[26]={0};
double result=0;
int n = seq.size();
for(int i=0;i<n;i++)
{
if( (seq[i]-'a'<0) || (seq[i]-'a'>=26) )
{
cout<<"calcKasisiki():seq-\'a\' overflow"<<endl;
return 0;
}
freq[seq[i]-'a']++;
}
double Pi=0;
for(int j=0;j<26;j++)
{
if(mode==1)
{
Pi = freq[j]*1.0/n;
}
else
{
Pi = freq_table[j]/100;
}
result = result + Pi * (freq[j]-1)*1.0/(n-1);
}
return result;
}
// 秘钥长度分析
double klenAssess(const vector<char> raw_text,int klen)
{
vector<char> m[klen];
char c;
int n=raw_text.size();
for(int i=0;i<n;i++)
{
c=raw_text[i];
if(c==EOF)
break;
m[i%klen].push_back(c);
};
double sqdiff=0;
for (int j=0;j<klen;j++)
{
double ksk = calcKasiski(m[j],1);
sqdiff = sqdiff + (ksk-KASISKI)*(ksk-KASISKI);
}
sqdiff=sqdiff*1.0/klen;
cout<<"square diff of ksk for klen="<<klen<<": "<<sqdiff<<endl;
return sqdiff;
}
在确定秘钥长度的基础上,将密文以秘钥长度为步长分组,对于每一个分组,遍历26个可能的偏移量,此时也是用到重合指数计算,选择重合指数最小的偏移量作为该组的偏移量,并得到对应的秘钥的字母。
//计算每一组的偏移,选择检验模式的,计算重合指数最符合的偏移
char kCrackShift(const vector<char> list)//only a-z in list
{
double result[26]={0};
for(int j=0;j<26;j++)
{
char token = 'a'+ j;
vector<char> seq;
int n=list.size();
for(int i=0;i<n;i++)
{
char c = offsetChar(list[i],token,0);
seq.push_back(c);
}
double ksk = calcKasiski(seq,0);
result[j]=(ksk-KASISKI)*(ksk-KASISKI);
}
int k=0;
for(int i=0;i<26;i++)
{
if(result[i]<result[k])
k=i;
}
return 'a'+k;
}
//破解秘钥内容 ,将原文按照秘钥长度分组,然后计算每一组的偏移
void kCrack(const vector<char> raw_text,const int klen)
{
int n=raw_text.size();
vector<char> m[klen];
char c;
key[klen]='\0';
for(int i=0;i<n;i++)
{
c=raw_text[i];
if(c==EOF)
break;
if(c>'z'||c<'a')
continue;
m[i%klen].push_back(c);
}
for(int i=0;i<klen;i++)
{
key[i]=kCrackShift(m[i]);
}
cout<<"result:"<<key<<endl;
}
输出秘钥长度,并将破解的明文输出到文件中。
void crack(const char * src_name,const char * dst_name,int klen)
{
FILE *in = fopen(src_name,"r");
FILE *out = fopen(dst_name,"w");
double klen_assess_result[256];
vector<char> raw_text;
char c;
int i=0;
//对读入文件中的字符基本处理,放到向量中间去
while(1)
{
c=fgetc(in);
if(c==EOF)
break;
if(c>'z'||c<'a')
continue;
raw_text.push_back(c);
i++;
};
fclose(in);
int min_klen=1;
int best_klen=0;
//计算不同偏移的时候的重合指数值
for(int i=0;i<256;i++)
{
klen_assess_result[i]=klenAssess(raw_text,i+min_klen);
}
//选择重合指数方差最小的作为秘钥长度
best_klen=0;
for(int i=0;i<256;i++)
{
if(klen_assess_result[i]<klen_assess_result[best_klen])
best_klen=i;
}
cout<<"cracker:mining ended"<<endl;
best_klen+=min_klen;
cout<<"best k len is possibly:"<<best_klen<<endl;
cout<<"start cracking the key..."<<endl;
//破解秘钥,对于秘钥中的每一个值进行尝试
kCrack(raw_text,best_klen);
fclose(out);
}
void crypt(const char * src_name,const char * dst_name,const char * key,int direction)
{
FILE *in = fopen(src_name,"r");
FILE *out = fopen(dst_name,"w");
char c;
int i=0;
int len = strlen(key);
while(1)
{
c=fgetc(in);
if(c==EOF)
break;
if(c>'z'||c<'a')
{
fprintf(out,"%c",c);
continue;
}
char k=offsetChar(c,key[i%len],direction);
fprintf(out,"%c",k);
i++;
};
fclose(in);
fclose(out);
}
void decrypt(const char * src_name,const char * dst_name,const char * key)
{
crypt(src_name,dst_name,key,0);
}
PS:
在统计字频的时候,用到了无偏估计,使得结果更加可靠。
实验结果
- 密文
- 明文
- 程序运行结果
代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <map>
#include <functional>
#include <vector>
#include <fstream>
#include <stdlib.h>
using namespace std;
#define KASISKI (0.065)
char key[65535];
double freq_table[]={
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,
};
char offsetChar(int c, int offset, int direction=1)
{
offset = offset-'a';
if(direction==1)
{
if( (c+offset)<='z' )
return c+offset;
return c+offset-26;
}
else
{
if( (c-offset)>='a' )
return c-offset;
return c-offset+26;
}
}
//mode==1的时候是计算重合指数,mode==0的时候是检验一定偏移的时候重合指数是否符合要求
double calcKasiski(vector<char> seq, int mode = 1)//mode 0:check mode
{
int freq[26]={0};
double result=0;
int n = seq.size();
for(int i=0;i<n;i++)
{
if( (seq[i]-'a'<0) || (seq[i]-'a'>=26) )
{
cout<<"calcKasisiki():seq-\'a\' overflow"<<endl;
return 0;
}
freq[seq[i]-'a']++;
}
double Pi=0;
for(int j=0;j<26;j++)
{
if(mode==1)
{
Pi = freq[j]*1.0/n;
}
else
{
Pi = freq_table[j]/100;
}
result = result + Pi * (freq[j]-1)*1.0/(n-1);
}
return result;
}
//计算每一组的偏移,选择检验模式的,计算重合指数最符合的偏移
//only a-z in list
char kCrackShift(const vector<char> list)
{
double result[26]={0};
for(int j=0;j<26;j++)
{
char token = 'a'+ j;
vector<char> seq;
int n=list.size();
for(int i=0;i<n;i++)
{
char c = offsetChar(list[i],token,0);
seq.push_back(c);
}
double ksk = calcKasiski(seq,0);
result[j]=(ksk-KASISKI)*(ksk-KASISKI);
}
int k=0;
for(int i=0;i<26;i++)
{
if(result[i]<result[k])
k=i;
}
return 'a'+k;
}
//破解秘钥内容 ,将原文按照秘钥长度分组,然后计算每一组的偏移
void kCrack(const vector<char> raw_text,const int klen)
{
int n=raw_text.size();
vector<char> m[klen];
char c;
key[klen]='\0';
for(int i=0;i<n;i++)
{
c=raw_text[i];
if(c==EOF)
break;
if(c>'z'||c<'a')
continue;
m[i%klen].push_back(c);
}
for(int i=0;i<klen;i++)
{
key[i]=kCrackShift(m[i]);
}
cout<<"result:"<<key<<endl;
}
// 秘钥长度分析
double klenAssess(const vector<char> raw_text,int klen)
{
vector<char> m[klen];
char c;
int n=raw_text.size();
for(int i=0;i<n;i++)
{
c=raw_text[i];
if(c==EOF)
break;
m[i%klen].push_back(c);
};
double sqdiff=0;
for (int j=0;j<klen;j++)
{
double ksk = calcKasiski(m[j],1);
sqdiff = sqdiff + (ksk-KASISKI)*(ksk-KASISKI);
}
sqdiff=sqdiff*1.0/klen;
cout<<"square diff of ksk for klen="<<klen<<": "<<sqdiff<<endl;
return sqdiff;
}
void crack(const char * src_name,const char * dst_name,int klen)
{
FILE *in = fopen(src_name,"r");
FILE *out = fopen(dst_name,"w");
double klen_assess_result[256];
vector<char> raw_text;
char c;
int i=0;
//对读入文件中的字符基本处理,放到向量中间去
while(1)
{
c=fgetc(in);
if(c==EOF)
break;
if(c>'z'||c<'a')
continue;
raw_text.push_back(c);
i++;
};
fclose(in);
if(klen_assess_result[best_klen]>MAX_DIFF)
{
min_klen=1;
cout<<"cracker:diff not accepted, start deep-mining"<<endl;
for(int i=0;i<256;i++)
{
klen_assess_result[i]=klenAssess(raw_text,i+min_klen);
}
best_klen=0;
for(int i=0;i<256;i++)
{
if(klen_assess_result[i]<klen_assess_result[best_klen])
best_klen=i;
}
cout<<"cracker:mining ended"<<endl;
}
*/
int min_klen=1;
int best_klen=0;
//计算不同偏移的时候的重合指数值
for(int i=0;i<256;i++)
{
klen_assess_result[i]=klenAssess(raw_text,i+min_klen);
}
//选择重合指数方差最小的作为秘钥长度
best_klen=0;
for(int i=0;i<256;i++)
{
if(klen_assess_result[i]<klen_assess_result[best_klen])
best_klen=i;
}
cout<<"cracker:mining ended"<<endl;
best_klen+=min_klen;
cout<<"best k len is possibly:"<<best_klen<<endl;
cout<<"start cracking the key..."<<endl;
//破解秘钥,对于秘钥中的每一个值进行尝试
kCrack(raw_text,best_klen);
fclose(out);
}
void readFile(const char * filename,vector<char> & raw_text)
{
FILE *in = fopen(filename,"r");
char c;
int i=0;
while(1)
{
c=fgetc(in);
if(c==EOF)
break;
if(c>'z'||c<'a')
continue;
raw_text.push_back(c);
i++;
};
fclose(in);
}
void crypt(const char * src_name,const char * dst_name,const char * key,int direction)
{
FILE *in = fopen(src_name,"r");
FILE *out = fopen(dst_name,"w");
char c;
int i=0;
int len = strlen(key);
while(1)
{
c=fgetc(in);
if(c==EOF)
break;
if(c>'z'||c<'a')
{
fprintf(out,"%c",c);
continue;
}
char k=offsetChar(c,key[i%len],direction);
fprintf(out,"%c",k);
i++;
};
fclose(in);
fclose(out);
}
void decrypt(const char * src_name,const char * dst_name,const char * key)
{
crypt(src_name,dst_name,key,0);
}
int main()
{
char * name[3]={NULL,NULL,NULL};
name[0]="密文.txt";
name[1]="明文.txt";
if( !name[0] )
{
cout<<"cracker: too few parameters"<<endl;
//printUsage();
return 0;
}
vector<char> m;
//读入文章内容
readFile(name[0],m);
crack(name[0],"cracked.txt",1);
decrypt(name[0],name[1],key);
return 0;
}