维吉尼亚解密

Posted on 2019-11-07 17:32  Volcano3511  阅读(280)  评论(0编辑  收藏  举报

维吉尼亚密码破解

toc

思路

主要就是计算重合指数来进行破解
遍历尝试秘钥长度
在确定秘钥长度的基础上,对于每一个分组尝试计算偏移量——相当于破解单个的凯撒密码,用到的也是重合指数的计算

代码分析

在代码所在的文件夹中"密文.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;
}

<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">