最近在看密码学,讲到用同一个密码重复加密一段原文,能够用重合因子来破解,上网查到的资料大部分是英文,因此准备把看到的资料http://en.wikipedia.org/wiki/Index_of_coincidence总结一下。ide
重合因子(index of coincidence)就是任意拿出两个字母,两个字母相同的几率。函数
以英文字母为例,从26个字母中随机拿出一个字母的几率是1/26,随机选择两个字母,选择出相同字母对的几率是测试
26x(1/26)x(1/26)=0.0385.加密
咱们知道,英文中字母的出现频率是有规律的,好比e为13%,a为8%,c为3%……因此,经过计算英文的重合因子可得:spa
(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
写带参数宏的时候,不要吝惜括号的使用code