維吉尼亞密碼加解密原理及其實現


維吉尼亞密碼(又譯維熱納爾密碼)是使用一系列凱撒密碼組成密碼字母表的加密算法,屬於多表密碼的一種簡單形式。

為了說清楚維吉尼亞密碼得從移位替換密碼說起,比較典型的就是凱撒密碼。

愷撒密碼是一種替換加密的技術,明文中的所有字母都在字母表上向后(或向前)按照一個固定數目進行偏移后被替換成密文。

例如,當偏移量是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套最基礎的移位法。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM