凯撒加解密实验

  本篇博客主要讲述了用java和matlab来实现凯撒加解密的过程。

理论

  对于现代密码体制来说,尽管加解密的方式不同,但大都数加解密都是基于对明文信息的“置换”和“代换”或者通过二者的乘积来完成的。凯撒密码是密码算法中最简单的操作“移位代换”中的一种最为古老的对称加密体制。

凯撒加密

  基本思想:通过把字母移动一定的位数来实现加密和解密。明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。例如,当偏移量是3的时候,所有的字母A将被替换成D,B变成E,以此类推X将变成A,Y变成B,Z变成C。由此可见,位数就是凯撒密码加密和解密的密钥。

凯撒解密

  凯撒密码的频度分析解密法:
  字母频度的差异可以用于破解密码。9世纪的科学家阿尔金迪在《关于破译加密信息的手稿》对其做了最早的描述。
  “如果我们知道一条加密信息所使用的语言,那么破译这条加密信息的方法就是找出同样的语言写的一篇其他文章,大约一页纸长,然后我们计算其中每个字母的出现频率。我们将频率最高的字母标为1号,频率排第2的标为2号,第三标为3号,依次类推,直到数完样品文章中所有字母。然后我们观察需要破译的密文,同样分类出所有的字母,找出频率最高的字母,并全部用样本文章中最高频率的字母替换。第二高频的字母用样本中2号代替,第三则用3号替换,直到密文中所有字母均已被样本中的字母替换。”

表1 英文字母出现频率统计

这里写图片描述

代码实现

java实现
import org.junit.Test;
import java.io.IOException;
import static java.lang.System.out;

/**
 * 实验五,凯撒密码的加解密实验
 * @author kanlon
 * @Time 2018/5/20
 *
 */
public class Test5 {
    /**
     * 26个字母的常用频率
     */
    public int[] pubCharFreq = {804,154,306,399,1251,230,196,549,726,016,067,414,253,709,760,200,011,612,654,925,271,99,192,19,173,9};

    /**
     * 未加密的字符串
     */
    String fileStr  = "";

    /**
     * 凯撒密码加密密钥
     */
    int key  = 3;

    /**
     * 存储26个字母的数组
     */
    char[] char26 = new char[26];

    @Test
    public void test(){
        try {
            Test2.countNum();
        }catch (IOException e){
            e.printStackTrace();
        }
        fileStr = Test2.fileStr;
        //去除所有特殊字符(包含空格)
        fileStr = fileStr.replaceAll("[[\\s+]`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]+","").toLowerCase();
        out.println("文本里的字符串为:"+fileStr);
        for(int i=0;i<char26.length;i++){
            char26[i]=(char)('a'+i);
        }
        //加密
        String ciphertext = encoding(fileStr);
        out.println("加密后的字符串为:"+ciphertext);

        double sumAllNum = 0;
        double[] frequency = new double[26];
        out.println("要统计的字符串为:\r\n"+ciphertext);
        //统计字符串各个字母的个数
        char[] textChar = ciphertext.toCharArray();
        //存放26个字母和对应的次数
        char[][] char26AndNum = new char[2][26];
        int intA = 97;
        int intZ = 123;
        for(int i=intA;i<intZ;i++){
            char26AndNum[0][i-intA]=(char)(i);
        }
        //比较字符串和26个字母的是否相等,并且计算次数
        for(int i=0;i<textChar.length;i++){
            if(textChar[i] >= 'a' && textChar[i]<='z'){
                char26AndNum[1][textChar[i]-'a']++;
            }
        }
        //输出26个字母及其所对应次数,即计算频数
        for(int i=0;i<char26AndNum[1].length;i++){
            sumAllNum += (double)char26AndNum[1][i];
        }
        //计算频率
        for(int i=0;i<char26AndNum[1].length;i++) {
            frequency[i] = char26AndNum[1][i] / sumAllNum;
        }
        int[] freqCiphertextInt = new int[frequency.length];
        for(int i=0;i<frequency.length;i++){
            freqCiphertextInt[i] = (int)Math.round(frequency[i]*1000);
        }

        //冒泡排序,将字母频率有大到小排列
        /**
         * 用于判断是否已经按照大小拍好序了
         */
        boolean flag = true;
        /**
         * 存放26个字母的数组
         */
        char[] temp26Char = char26AndNum[0].clone();
        for(int i =0;i<freqCiphertextInt.length;i++){
            flag = true;
            for(int j=1;j<freqCiphertextInt.length;j++){
                if(freqCiphertextInt[j]>freqCiphertextInt[j-1]){
                    int temp =  freqCiphertextInt[j-1];
                    char tempChar = temp26Char[j-1];
                    freqCiphertextInt[j-1] = freqCiphertextInt[j];
                    temp26Char[j-1] = temp26Char[j];
                    freqCiphertextInt[j]=temp;
                    temp26Char[j] = tempChar;
                    flag = false;
                }
            }
            if(flag == true){
                break;
            }
        }

        //密文的字母频率,为了方便比较已经乘以1000
        //Arrays.sort(frequency);
        for(int i=0;i<frequency.length;i++){
            out.println("字母"+temp26Char[i]+"的频率为:"+freqCiphertextInt[i]);
        }

        //冒泡排序,将字母频率有大到小排列
        /**
         * 存放26个字母的数组
         */
        char[] temp26Char2 = char26AndNum[0].clone();
        for(int i =0;i<pubCharFreq.length;i++){
            flag = true;
            for(int j=1;j<pubCharFreq.length;j++){
                if(pubCharFreq[j]>pubCharFreq[j-1]){
                    int temp =  pubCharFreq[j-1];
                    char tempChar = temp26Char2[j-1];
                    pubCharFreq[j-1] = pubCharFreq[j];
                    temp26Char2[j-1] = temp26Char2[j];
                    pubCharFreq[j]=temp;
                    temp26Char2[j] = tempChar;
                    flag = false;
                }
            }
            if(flag == true){
                break;
            }
        }
        //公共的字母频率,为了方便比较已经乘以1000
        for(int i=0;i<pubCharFreq.length;i++){
            out.println("公共的字母"+temp26Char2[i]+"的频率为:"+pubCharFreq[i]);
        }

        //假设明文的字母概率最大的字母对应的刚好是公共字母概率最大的字母
        int key = temp26Char[0]-temp26Char2[0];
        //凯撒密码解密
        String plaintext =  decoding(ciphertext,key);
        out.println("明文为:"+plaintext);

        //如果以上假设不成立,即解出来的明文英文不能构成单词,则尝试假设明文某字母出现概率最大为公共字母出现概率最大的前五之一
        int [] keys = new int[5];
        for(int i=0;i<5;i++){
            keys[i]=temp26Char[i]-temp26Char2[0];
            //凯撒密码解密
            plaintext =  decoding(ciphertext,keys[i]);
            out.println("假设"+(i+1)+"明文为:"+plaintext);
        }
        //这时候查看解出来的明文结果的单词逻辑,一般就能得到正确的明文
    }


    /**
     * 凯撒加密过程
     * @param fileStr 需要加密的字符串
     */
    public String encoding(String fileStr){
        String codeFileStr = "";
        //测试字符串
        //fileStr = "abdozez";
        //凯撒加密过程
        for(int i = 0;i<fileStr.length();i++){
            codeFileStr += char26[(fileStr.charAt(i)-'a'+key)%26];
        }
        return codeFileStr;
    }

    /**
     * 凯撒解密
     * @param codeFileStr 密文
     * @param key 密钥
     * @return 明文
     */
    public String decoding(String codeFileStr,int key){
        String plaintext="";
        key = 26-key;
        //凯撒解密过程
        for(int i = 0;i<codeFileStr.length();i++){
            plaintext += char26[(codeFileStr.charAt(i)-'a'+key)%26];
        }
        return plaintext;
    }
}

代码中涉及到的Test2类和Types of Speech.txt文件,请点击这里查看

java运行结果
这里写图片描述
这里写图片描述

MATLAB实现

  这里的MATLAB实现是借鉴别人的(仅供参考,版权归拥有者所有)。
1. 凯撒密码加密函数程序CaesarCode.m

function Y=CaesarCode(X,K)
%其中X表示明文,K表示密钥
A=['z','a','b','c','d','e','f','g','h','i','j','k','l','m','n',...
      'o','p','q','r','s','t','u','v','w','x','y'];
B=['Z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y'];
L=length(X);%获取明文的长度
for i=1:L
    emp=abs(X(i));%获取明文对应的ASCII码
    if emp>=97 & emp<=123 %小写字母a到z的ASCII码是97~123
      for j=1:26
          if X(i)==A(j)%判断是哪一个小写的26个字母
         n=mod(j+K-1,26);%由于是从z开始而不是从a开始所以要减1才是真正移动的位数
Y(i)=A(n+1);
end
      end
    elseif emp>=65 & emp<=90
for j=1:26
          if X(i)==B(j)%判断是哪一个大写的26个字母
         n=mod(j+K-1,26);
Y(i)=B(n+1);
end
end
    else
        Y(i)=X(i);%对标点和空格等其他字符保持原状
    end
end

凯撒密码加密主程序kaisa_1.m

clc
clear;
d=fopen('Types of Speech.txt','r');%打开文件
string=fread(d,inf,'char');
Y=CaesarCode(string,3)%调用CaesarCode函数加密
f=fopen('OK.txt','w');
fwrite(f,Y,'char');%输出密文OK.txt文件
fclose(f);
  1. 凯撒密码解密函数程序 CaesarCodejiemi.m
function Y=Caesarjiemi(string,key)
%其中string表示密文,key表示密钥
A=['z','a','b','c','d','e','f','g','h','i','j','k','l','m','n',...
      'o','p','q','r','s','t','u','v','w','x','y'];
B=['Z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y'];
L=length(string);%获取密文的长度
for i=1:L
    emp=abs(string(i));%获取密文所有符号对应的ASCII码
    if emp>=97 & emp<=123 %小写字母a到z的ASCII码是97~123
      for j=1:26
          if string(i)==A(j)%判断是哪一个小写字母
         n=mod(j-key-1,26);%由于是从z开始而不是从a开始所以要减1才是真正移动的位数
Y(i)=A(n+1);
end
      end
    elseif emp>=65 & emp<=90%判断是哪一个大写字母
for j=1:26
          if string(i)==B(j)%判断是哪一个大写字母
         n=mod(j-key-1,26);
Y(i)=B(n+1);
end
end
    else
        Y(i)=string(i);%对标点和空格等其他字符保持原状
    end
end

凯撒密码解密函数主程序 jiemi.m

clear all
clc
temp=0;
%打开相应的文件
d=fopen('OK.txt','r');
%读入文件中的数据
str=fread(d,'*char');%这步所得的字符串存储着密文文件的所有字符。
X=tabulate(str);%对读入的字符进行频数的统计
string=lower(str);%将上述文件中的所有大写英文字母化为小写字母
%此步是方便找出凯撒密码的密钥
%%%%%%%%%%%%%利用频度分析法破解凯撒密码的密钥%%%%%%%%%%%%%%
num=string-'a';%将所有字符的ASCII码与字符'a'ASCII码相减
%统计26个字母的频数
for i=1:length(string);
    for j=1:26
        b(j)=sum(num==j-1);
    end
end
disp('26个字母出现的频数为');
disp(b)

%求出最大次数的下标jk
for j=1:26;
    if b(j)>temp
       jk= j;
       temp=b(j);
    end
end  
if jk<5%最大次数的下标小于55是出现现频率最高的字母E的位置。
    key=(jk+26)-5;
else
    key=jk-5;
end;
disp('密钥是');
disp(key);

调用解密函数main.m

扫描二维码关注公众号,回复: 1628871 查看本文章
Y=Caesarjiemi(str,key);
%将解密后的密文输出
disp('解密成功,输出解密后的文件为JM.txt');
f=fopen('JM.txt','w');
fwrite(f,Y,'char');%输出解密后的JM.txt文件
fclose(f);

总结

  这次练习,我觉得比较困难的地方在凯撒解密的部分,因为要找出正确的密钥出来是很困难的,刚开始的时候在想解密的这部分,我想了好几天,但是都没有什么方法能够让程序自动找出正确的密钥出来。后来发现,还是要加上部分人工干预才能找出正确的密钥出来。
  再后来,在网上搜了相关凯撒密码解密的方法,突然发现,自己忽略了凯撒密码的本身的密钥空间,它本身密钥空间就只有25个,原来随便遍历都可以解密了。不过还有大神设计出凯撒密码自动解密的程序,主要是利用穷举密钥,计算频率平方和的方法,链接在此(https://blog.csdn.net/wyf12138/article/details/73733339),这个方法挺好的,当初我没有考虑到这个方法,所以浪费了很多时间,而且还没有这个方法好。
  还有通过这次练习,学会了一个把两个数组的同时按照大小顺便排列的方法,就是相当于把map集合里面的值进行排列,然后key也会跟着变化,之前我还考虑怎么排列map,原来用两个数组就可以轻松实现(具体步骤看java里两个冒泡排序算法),还是自己打代码的时间太少。

猜你喜欢

转载自blog.csdn.net/weixin_37610397/article/details/80467696