维吉尼亚密码加解密原理及其实现

维吉尼亚密码(又译维热纳尔密码)是使用一系列凯撒密码组成密码字母表的加密算法,属于多表密码的一种简单形式。

为了说清楚维吉尼亚密码得从移位替换密码说起,比较典型的就是凯撒密码。

恺撒密码是一种替换加密的技术,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。

例如,当偏移量是3的时候,所有的字母A将被替换成D,B变成E,以此类推。

 

原文 F l y   i n   t h e   s k y
密文 I O B   L Q   W K H   V N B

 

因为概率论的出现这种简单的移位或替换就容易破解了,其原理很简单,英文中字母出现的频率是不一样的。比如字母e是出现频率最高的,占12.7%;其次是t,9.1%;然后是a,o,i,n等,最少的是z,只占0.1%。

 

                  英语中字母频率统计

除了英语,其它语言也有详细统计。

                      各语言中字母频率统计

只要字符总量足够,全部收集到一起,统计各个字符出现的频率,然后再加上字母前后的关联关系,以及所要加密的语言本身语法搭配就可大幅度降低字母

的排列组合的可能性,这样密码就破解了。

当然维吉尼亚密码也属于古典密码学的范畴,都是对单个字符或符号进行移位或替代

维吉尼亚加密原理

核心:为了掩盖字母使用中暴露的频率特征,解决的办法就是用多套符号代替原来的文字。

比如原来的字母是A,从前只把它替换成F,现在把它替换成F或者G这两个。那什么时候用F什么时候用G呢?可以自行规定,比如说,字母在奇数位时用F代替,字母在偶数位时用G代替。

这样频率分析法暂时失效了。

而且,多套符号加密法并没满足于2~3套,后来典型使用的是26套,这就是第三代密码“维吉尼亚加密法”。

它是一个表格,第一行代表原文的字母,下面每一横行代表原文分别由哪些字母代替,每一竖列代表我们要用第几套字符来替换原文。一共26个字母,一共26套代替法,所以这个表是一个26*26的表。

                             维吉尼亚密码表

每一行就可以代表一套凯撒密码加密方法。

加密方法

密公式:C = (P + K)%26

C:密文

P:原文

K:第几套加密方式

 

使用第几套加密方式是通过约定一个规则来确定的,这个规则就是“密钥”。

这样一个密钥字母代表一套加密方式,比如:t代表第19套加密方式,h代表第7套加密方式,i代表第8套加密方式,s代表第18套加密方式。

这样密钥和原文每个字符一一对应,如果密钥长度不足,那么循环替代。

 

原文 h e l l o   w o r l d
密钥 t h i s t h i s t h i
密文 A L T D H   E G K S L

 

原文中的两个“l”分别加密成T和D,而且密文中的同样的字符也可能代表不同的原文,比如密文中的L,分别代表了原文中的“e”和“d”。

解密方法

解密公式:P = (C - K)%26

C:密文

P:原文

K:第几套加密方式

如果P<0,P+26取得正序

 

代码实现

 

  1 import java.util.ArrayList;
  2 import java.util.Arrays;
  3 import java.util.List;
  4 import java.util.Optional;
  5 /**
  6  * 维吉尼亚加密解密
  7  * @author 逆熵
  8  *
  9  */
 10 public class VirginiaPassword {
 11 
 12     //Virginia Password table
 13     private static final String VIRGINIA_PASSWORD_TABLE_ARRAY[] = {"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","Z"};
 14     public static final List<String> VIRGINIA_PASSWORD_TABLE_ARRAY_LIST = Arrays.asList(VIRGINIA_PASSWORD_TABLE_ARRAY);
 15     
 16     /**
 17      * 
 18      * 判断对象是否为空,为空返回false,非空返回true
 19      * @param object
 20      * @return
 21      */
 22     public static boolean isNotNull(Object object) {
 23         return Optional.ofNullable(object).isPresent();
 24     }
 25     
 26     /**
 27      * 判断字符串非null,并且不为空字符串
 28      * @param str
 29      * @return 
 30      */
 31     public static boolean isStringNotEmpty(String str) {
 32         return isNotNull(str)&&str.trim()!="";
 33     }
 34     
 35     /**
 36      * C = P + K (mod 26).
 37      * 维吉尼亚加密
 38      * @param original    原文
 39      * @param secretKey    密钥
 40      * @return
 41      */
 42     public static String virginiaEncode(String original,String secretKey) {
 43         if(isStringNotEmpty(secretKey)&&isStringNotEmpty(original)){
 44             char[] secretCharArray = secretKey.toCharArray();
 45             char[] originalCharArray = original.toCharArray();
 46             int length = originalCharArray.length;
 47             
 48             List<Integer> list = getSecretKeyList(secretCharArray, length);
 49             
 50             StringBuffer sb = new StringBuffer();
 51             for(int m=0;m<length;m++) {
 52                 char ch = originalCharArray[m];
 53                 int charIndex = VIRGINIA_PASSWORD_TABLE_ARRAY_LIST.indexOf(String.valueOf(ch).toUpperCase());
 54                 if(charIndex==-1) {
 55                     sb.append(String.valueOf(ch));
 56                     continue;
 57                 }
 58                 
 59                 int size = VIRGINIA_PASSWORD_TABLE_ARRAY_LIST.size();
 60                 //C = P + K (mod 26). 获取偏移量索引
 61                 int tmpIndex = (charIndex + list.get(m))%size;
 62                 sb.append(VIRGINIA_PASSWORD_TABLE_ARRAY_LIST.get(tmpIndex));
 63                 
 64             }
 65             return sb.toString();
 66         }
 67         return null;
 68     }
 69     
 70     /**
 71      * P = C - K (mod 26).
 72      * 维吉尼亚解密
 73      * @param cipherText    密文
 74      * @param secretKey    密钥
 75      * @return
 76      */
 77     public static String virginiaDecode(String cipherText,String secretKey) {
 78         if(isStringNotEmpty(cipherText)&&isStringNotEmpty(secretKey)) {
 79             char[] secretCharArray = secretKey.toCharArray();
 80             char[] cipherCharArray = cipherText.toCharArray();
 81             int length = cipherCharArray.length;
 82             
 83             List<Integer> list = getSecretKeyList(secretCharArray, length);
 84             StringBuffer sb = new StringBuffer();
 85             for(int m=0;m<length;m++) {
 86                 char ch = cipherCharArray[m];
 87                 int charIndex = VIRGINIA_PASSWORD_TABLE_ARRAY_LIST.indexOf(String.valueOf(ch).toUpperCase());
 88                 if(charIndex==-1) {
 89                     sb.append(String.valueOf(ch));
 90                     continue;
 91                 }
 92                 
 93                 int size = VIRGINIA_PASSWORD_TABLE_ARRAY_LIST.size();
 94                 //P = C - K (mod 26). 模逆运算求索引
 95                 int len = (charIndex - list.get(m))%size;
 96                 //索引小于零,加模得正索引
 97                 int tmpIndex = len<0?len+size:len;
 98                 sb.append(VIRGINIA_PASSWORD_TABLE_ARRAY_LIST.get(tmpIndex));
 99                 
100             }
101             
102             return sb.toString();
103         }
104         
105         return null;
106     }
107     
108     /**
109      * 获取密钥集合
110      * @param secretCharArray 密钥字符数组
111      * @param length    原文或密文的长度
112      * @return
113      */
114     private static List<Integer> getSecretKeyList(char[] secretCharArray, int length) {
115         List<Integer> list = new ArrayList<Integer>();
116         for (char c : secretCharArray) {
117             int index = VIRGINIA_PASSWORD_TABLE_ARRAY_LIST.indexOf(String.valueOf(c).toUpperCase());
118             list.add(index);
119         }
120         
121         
122         if(list.size()>length) {
123             //截取和目标原文或密文相同长度的集合
124             list = list.subList(0, length);
125         }else {
126             Integer[] keyArray = list.toArray(new Integer[list.size()]);
127             int keySize = list.size();
128             //整除
129             int count = length/keySize;
130             for(int i=2;i<=count;i++) {
131                 for (Integer integer : keyArray) {
132                     list.add(integer);
133                 }
134             }
135             //求余
136             int mold = length%keySize;
137             if(mold>0) {
138                 for(int j=0;j<mold;j++) {
139                     list.add(keyArray[j]);
140                 }
141                 
142             }
143         }
144         
145         return list;
146     }
147 }

注:以上代码在JDK1.8上编译通过

维吉尼亚加密难以破解的关键是同一种原文对应上亿种密文,相同的密文也一样对应上亿种原文。

维吉尼亚密码的破解方法

以上代码是知道密钥的情况,通过加密的求余逆运算计算出来的,但是真实的情况下,我们只有密文,没有密钥,那么怎么破解维吉尼亚密码呢?

 

 

密钥 K I N G K I N G K I N G K I N G K I N G K I N G K I N G K I N
原文 t h e   s u n   a n d   t h e   m a n   i n   t h e   m o o n
密文 D P R   C C A   K V Q   D P R   W I A   S V   Z R M   S Y W A

 

同样的原文“the”被两次加密成“DPR”,这是巧合?

其实不然,密钥KING由4个字母构成,而在密文中代表the的DPR间隔了12个字母,间隔距离刚好是密钥的3倍。

正好在KING这个密钥循环到整数倍的时候,如果也正好赶上出现了同样的原文,那巧合就出现了——原文就会被加密成相同的密文。

而此处即是破解维吉尼亚密码的关键。

在不知道密钥的情况下,分析出密钥的长度,那么密码就被破解了。

怎么分析出密钥的长度呢?

把密文中完全一样的单词挑出来,分析总结其规律。

密文对应的原文虽然理论上数量巨大,但拼写规则一卡,数据量就急剧减少了。

而且密文里的字符串长度越长,对应真实存在的单词数量就越少。

你可以翻翻字典,或者在头脑中找找4个字母的单词和12个字母的单词,看看其数量对比。

步骤:

1.从密文中找出拼写完全相同的字符串

2.数第一次到第二次出现中间间隔的字母数

如果间隔了30个字母

密钥长度为2,那么正好反复出现了15次;

密钥长度为3,那么正好反复出现了10次;

密钥长度为5,那么正好反复出现了6次;

密钥长度为6,那么正好反复出现了5次;

密钥长度为30,那么正好反复出现了1次。

比如还存在B、C、D,只要看同样的因数都出现,那么这个因数就是密钥的长度。

我们通过密钥的长度,就可以使用频率分析方法把原文分析出来。

比如我们知道密钥的长度为6,那么第1、7、13等字母放到一组,因为它们都是通过同一套加密方式加密出来的,然后用频率分析法。

其它组别也按照此方式分析出来。

所以,猜中密钥长度就等于,把维吉尼亚密码化简为N套最基础的移位法。

 

posted @ 2018-12-25 13:34 逆熵 阅读(...) 评论(...) 编辑 收藏