最近在看密码学,讲到用同一个密码重复加密一段原文,可以用重合因子来破解,上网查到的资料大部分是英文,所以准备把看到的资料http://en.wikipedia.org/wiki/Index_of_coincidence总结一下。
重合因子(index of coincidence)就是任意拿出两个字母,两个字母相同的概率。
以英文字母为例,从26个字母中随机拿出一个字母的概率是1/26,随机选择两个字母,选择出相同字母对的概率是
26x(1/26)x(1/26)=0.0385.
我们知道,英文中字母的出现频率是有规律的,比如e为13%,a为8%,c为3%……所以,通过计算英文的重合因子可得:
(13/100)x(13/100)+(8/100)x(8/100)+(3/100)x (3/100)...=0.0667
对于一段文字,我们可以通过如下公式计算其重合因子
其中,fi表示某个字母在该段文字中出现的个数。
那么,这又和密码有什么关系呢?
当我们用简单的替换密码多次替换原文得到明文,这时候,原文中字母的统计属性是被保留下来的。
破解过程:
(1)得到密码长度
1.Kasiski 测试法
2.对密码长度keylen进行猜测,将所有密文分为keylen*up(cipherlen/keylen)的二维矩阵(up(float a)是上取整函数),那么对于每一列,都是用一个密钥分量ki来加密的(对于这一列,相当于凯撒加密法),因此,该列加密后的密文保持加密前的统计属性!那么我们对不同的keylen进行测试,从其中得到和英文重合因子差别最小的那个keylen很可能就是密钥长度。
(2)对每一列密文通过相关性寻找密码分量ki,最后全部ki拼接成key=k1k2..kn.
得到了密钥长度,我们就可以对每一列求其密钥分量ki,做法是穷举所有a-z解密,计算解密后该列字母和英文的相关度,如何计算相关度呢,按照如下公式:
其中,ni表示解密后你得到的第i个字母的频率,fi为对应英文字母在英文中的统计频率。效果就是:
(解密后a的频率 x a在英文中的频率)+(解密后b的频率 x b在英文中的频率)+...(解密后z的频率 x z在英文中的频率),可以证明该值最大的情况时,对应的ki是正确的密钥分量
附上测试代码:
#include<stdio.h>
#include<stdlib.h>
#include<limits.h>
#include<memory.h>
#define N 200
#define MAX_KEY_LEN (10+1)
char cipher[N];//="qpwkalvrxcqzikgrbpfaeomfljmsdzvdhxcxjyebimtrqwnmeaizrvkcvkvlxneicfzpzczzhkmlvzvzizrrqwdkechosnyxxlspmykvqxjtdciomeexdqvsrxlrlkzhov";
int a[26];
float english[26]={0.082,0.015,0.028,0.043,0.127,0.022,0.020,0.061,0.070,0.002,0.008,0.040,0.024,
0.067,0.075,0.019,0.001,0.060,0.063,0.091,0.028,0.010,0.002,0.001,0.002,0.001};
float target=0.067;
float saveIC[MAX_KEY_LEN];
#define ABS(x) ((x)>0?(x):-(x))//总括号必须有啊!
/*float ABS(float x){
return x>0?x:-x;
}*/
float ic(char cipher[],int clen,int keylen){
int i,j,k,n=0;
float sum=0;
char *p;
for(i=0;i<keylen;i++){
memset(a,0,sizeof(a));
p=cipher+i;
n=0;
for(j=0;j<(clen/keylen+1);j++){
if((p-cipher)>clen||*p == '\0') break;
a[*p-'a']++;
p+=keylen;
n++;
}
for(k=0;k<26;k++){
if(n==0||n==1) break;
sum+=((float)a[k]*(a[k]-1)/(n*(n-1)));
}
}
return sum/keylen;
}
int keylen(float saveIC[],float target){
int i,len;
float best=100.0;
for(i=1;i<MAX_KEY_LEN;i++){
float t=target-saveIC[i];
if(best > ABS(t)){
best=ABS(t);
len=i;
}
}
return len;
}
char key(char cipher[],int clen,int keylen,int column){
int i,j,k,n;
int temp[26];
float cor,bestcor=0;
char bestkey;
char *p;
for(i=0;i<26;i++){//进行a-z的穷举
memset(temp,0,sizeof(temp));
n=0;
cor=0.0;
p=cipher+column;
for(j=0;j<(clen/keylen+1);j++){//统计解密后的字母,保存在temp[]中
if((p-cipher)>clen||*p=='\0') break;
temp[(*p-('a'+i)+26)%26]++;
n++;
p+=keylen;
}
for(k=0;k<26;k++){//计算相关度
cor+=(float)temp[k]/n * english[k];
}
/*printf("%d----",i);
for(k=0;k<26;k++){
printf("%d ",temp[k]);
}
printf("\n");
*/
if(cor>bestcor){
bestcor=cor;
bestkey='a'+i;
}
}
return bestkey;
}
void encryption(char plaintext[],int len,char key[],char cipher[]){//加密函数
char* p=key;
int i=0;
while(i<len && plaintext[i]!='\0'){
if(*p=='\0') p=key;
cipher[i]='a'+(plaintext[i]-'a' + *p-'a')%26;
i++;
p++;
}
}
void getkey(char cipher[],int clen,char k[]){
int i,klen,col;
memset(k,0,sizeof(k));
for(i=1;i<MAX_KEY_LEN;i++){
saveIC[i]=ic(cipher,clen,i);
}
klen=keylen(saveIC,target);
// printf("len %d\n",klen);
//---------------------------
for(col=0;col<klen;col++){
k[col]=key(cipher,clen,klen,col);
}
}
void decryption(char cipher[],int clen,char key[],char plaintext[]){
char* p=key;
int i=0;
while(i<clen && cipher[i]!='\0'){
if(*p=='\0') p=key;
plaintext[i]='a'+(cipher[i]-'a'-(*p-'a')+26)%26;
//printf("%c",plaintext[i]);
i++;
p++;
}
}
int main(){
int i=0;
char c,key1[MAX_KEY_LEN],key2[MAX_KEY_LEN];
char mess[N],plaintext[N];
memset(key1,0,sizeof(key1));
memset(key2,0,sizeof(key2));
memset(mess,0,sizeof(mess));
memset(plaintext,0,sizeof(plaintext));
memset(cipher,0,sizeof(cipher));
printf("input your message:");
scanf("%s",mess);
printf("input your key:");
scanf("%s",key1);
encryption(mess,sizeof(mess),key1,cipher);
printf("after encryption,cipher is :%s\n",cipher);
getkey(cipher,sizeof(cipher),key2);
printf("the key is:%s\n",key2);
decryption(cipher,sizeof(cipher),key2,plaintext);
printf("the message is:%s\n",plaintext);
return 0;
}
测试发现:
(1)加密样本必须尽可能接近自然语言,而且加密文本长度必须达到一定的长度,才能满足统计规律。
(2)同时,如果密码长度过大,那么这种统计规律也不是很好(因为解密时每列字母变少,统计规律减弱)。
(3)满足足够多的字母且密码长度适宜时,统计规律较好,解密效果明显。
局限和不足:
(1)只能解决纯小写字母的加解密,不能处理带有空格和标点的情况,这是本程序的局限性
(2)某些情况下发生崩溃,不知什么原因,猜测是解密函数有BUG,还需调试。
收获:
(1)利用重合因子和英文的统计规律,解密用同一段密钥加密明文的加密方法。
(2)找资料还是要找老外啊!
(3)英文水平太渣,需要提高。
(4)程序中发现,对于
函数void f(char str[])
{
int len=sizeof(str)//==4
}
对于声明char str[N];
int len=sizeof(str);//==N
对于指针char *str;
int len=sizeof(str);//==4
写带参数宏的时候,不要吝惜括号的使用
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。