維吉尼亞密碼(又譯維熱納爾密碼)是使用一系列凱撒密碼組成密碼字母表的加密算法,屬於多表密碼的一種簡單形式。
為了說清楚維吉尼亞密碼得從移位替換密碼說起,比較典型的就是凱撒密碼。
愷撒密碼是一種替換加密的技術,明文中的所有字母都在字母表上向后(或向前)按照一個固定數目進行偏移后被替換成密文。
例如,當偏移量是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套最基礎的移位法。