1

最近在看密码学,讲到用同一个密码重复加密一段原文,可以用重合因子来破解,上网查到的资料大部分是英文,所以准备把看到的资料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
写带参数宏的时候,不要吝惜括号的使用


binta
73 声望5 粉丝

The Magic Words are Squeamish Ossifrage


引用和评论

0 条评论