1. 程式人生 > >【密碼學】維吉尼亞密碼加解密原理及其破解演算法Java實現

【密碼學】維吉尼亞密碼加解密原理及其破解演算法Java實現

1. 維吉尼亞密碼方陣

人們在愷撒移位密碼的基礎上擴展出多表密碼,稱為維吉尼亞密碼。該方法最早記錄在吉奧萬·巴蒂斯塔·貝拉索( Giovan Battista Bellaso)於1553年所著的書《吉奧萬·巴蒂斯塔·貝拉索先生的密碼》

維吉尼亞密碼方陣.jpg

第一行代表明文字母,第一列代表金鑰字母,它的明碼錶後有26個密碼錶,每個表相對前一個發生一次移位。

如果只用其中某一個進行加密,那麼只是簡單的愷撒移位密碼。但用方陣中不同的行加密不同的字母,它就是一種強大的密碼了。

加密者可用第7行來加密第一個字母,再用第25行來加密第二個字母,然後根據第8行來加密第三個字母等。

2. 使用維吉尼亞密碼

維吉尼亞密碼引入了“金鑰”的概念,即根據金鑰來決定用哪一行的密表來進行替換,以此來對抗字頻統計

2.1 加密

1.確定金鑰

首先,和訊息接收方需要在金鑰上達成一致,加密解密都是同一個金鑰,比如選用BIG

2.排列明文

把明文轉換為大寫字母排列出來,對應著重複排列金鑰,直到明文結尾:

明文:THE BUTCHER THE BAKER AND THE CANDLESTICK MAKER
金鑰:BIG BIGBIGB IGB IGBIG BIG BIG BIGBIGBIGBI GBIGB

3.加密明文

然後,每一組的兩個字母就成了我們的座標。在維吉尼亞座標圖中分別橫向縱向找出它們。橫座標和縱座標的交點就是加密後的字母。

比如例子中的第一個字母是T,它下面是B,在維吉尼亞密碼錶中第一行找到T

,第一列中找到B,所以得到的它們的交點就是U

明文:THE BUTCHER THE BAKER AND THE CANDLESTICK MAKER
金鑰:BIG BIGBIGB IGB IGBIG BIG BIG BIGBIGBIGBI GBIGB
密文:UPK CCZDPKS BNF JGLMX BVJ UPK DITETKTBODS SBSKS

2.2 解密

1.排列密文

和加密一樣,把密文排列出來,在下面對應著重複排列金鑰:

密文:UPK CCZDPKS BNF JGLMX BVJ UPK DITETKTBODS SBSKS
金鑰:BIG BIGBIGB IGB IGBIG BIG BIG BIGBIGBIGBI GBIGB

2.解密密文

解密就是加密的逆過程,比如第一個字母是U,下面是B,找到B開頭的那一行,在該行中找到U,得到的明文字母就是那一列的開頭字母T

密文:UPK CCZDPKS BNF JGLMX BVJ UPK DITETKTBODS SBSKS
金鑰:BIG BIGBIGB IGB IGBIG BIG BIG BIGBIGBIGBI GBIGB
明文:THE BUTCHER THE BAKER AND THE CANDLESTICK MAKER

3. 破解維吉尼亞密碼

3.1 確定金鑰長度m

3.1.1 Kasiski測試法

Kasiski測試法是由Friedrich Kasiski於1863年給出了其描述,然而早在約1854年這一方法就由Charles Babbage首先發現。它主要基於這樣一個事實:兩個相同的明文段將加密成相同的密文段,它們的位置間距假設為δ,則δ≡0(mod m)。反過來,如果在密文中觀察到兩個相同的長度至少為3的密文段,那麼將給破譯者帶來很大方便,因為它們實際上對應了相同的明文串。

使用流程

搜尋長度至少為3的相同的密文段,記下其離起始點的那個密文段的距離。假如得到如下幾個距離δ₁,δ₂,...,那麼,可以猜測m為這些δi的最大公因子的因子。

使用例項
金鑰:FORESTFORESTFORESTFORESTFOR
明文:better to do well than to say well
密文: GSKXWKYCUSOXQZKLSGYCJEQPJZC

第一個YC出現後到第二個YC的結尾一共有12個字母,那麼金鑰的長度應是12的約數1,2,3,4,6,12之中的一個。

當密文很長的時候,可以找出幾組重複的密文段,找出它們間距的相同約數,就是金鑰長度。

3.1.2 重合指數法

重合指數(Index of Coincidence)

x=x1x2...xn是一條n個字母的串,x的重合指數記為CI,定義為x中兩個隨機元素相同的概率。

定義公式:
重合指數公式.png
常用公式:xi為字母的頻數,L為密文的長度
常用公式.png
字母在英語文字中出現的概率:
字母在英文中出現的概率.png
對英語而言,根據上述的頻率表,我們可以計算出英語文字的重合指數為P(A)^2 + P(B)^2+……+P(Z)^2 = 0.065

利用重合指數推測金鑰長度的原理在於,對於一個由凱撒密碼加密的序列,由於所有字母的位移程度相同,所以密文的重合指數應等於原文語言的重合指數

使用重合指數法

假設使用維吉尼亞密碼加密的密文串為y=y1y2...yn。將串y分割成m個長度相等的子串,分別為y1,y2,...,ym,這樣就可以以列的形式寫出密文,組成一個m×(n/m)矩陣。矩陣的每一行對應於子串yi,1≤i≤m

舉例來說:

密文ABCDEABCDEABCDEABC

m=2分組,則每組為:

1:A C E B D A C E B
組2:B D A C E B D A C

如果y1,y2,...,ym按如上方式構造,則m實際上就是金鑰字的長度,每一組的CI值大約為0.065。另外,如果m不是金鑰字的長度,那麼子串yi看起來更為隨機,因為它們是通過不同金鑰以移位加密方式獲得的。對於一個隨機串,其重合指數為0.038

程式碼實現

重合指數法實現程式碼如下:

	// Friedman測試法確定金鑰長度
    public int Friedman(String ciphertext) {
        int keyLength = 1; // 猜測金鑰長度
        double[] Ic; // 重合指數
        double avgIc; // 平均重合指數
        ArrayList<String> cipherGroup; // 密文分組

        while (true) {
            Ic = new double[keyLength];
            cipherGroup = new ArrayList<>();
            avgIc = 0;

            // 1 先根據金鑰長度分組
            for (int i = 0; i < keyLength; ++i) {
                StringBuilder tempGroup = new StringBuilder();
                for (int j = 0; i + j * keyLength < ciphertext.length(); ++j) {
                    tempGroup.append(ciphertext.charAt(i + j * keyLength));
                }
                cipherGroup.add(tempGroup.toString());
            }

            // 2 再計算每一組的重合指數
            for (int i = 0; i < keyLength; ++i) {
                String subCipher = cipherGroup.get(i); // 子串
                HashMap<Character, Integer> occurrenceNumber = new HashMap<>(); // 字母及其出現的次數

                // 2.1 初始化字母及其次數鍵值對
                for (int h = 0; h < 26; ++h) {
                    occurrenceNumber.put((char) (h + 65), 0);
                }

                // 2.2 統計每個字母出現的次數
                for (int j = 0; j < subCipher.length(); ++j) {
                    occurrenceNumber.put(subCipher.charAt(j), occurrenceNumber.get(subCipher.charAt(j)) + 1);
                }

                // 2.3 計算重合指數
                double denominator = Math.pow((double) subCipher.length(), 2);
                for (int k = 0; k < 26; ++k) {
                    double o = (double) occurrenceNumber.get((char) (k + 65));
                    Ic[i] += o * (o - 1);
                }
                Ic[i] /= denominator;
            }

            // 3 判斷退出條件,重合指數的平均值是否大於0.065
            for (int i = 0; i < keyLength; ++i) {
                avgIc += Ic[i];
            }
            avgIc /= (double) keyLength;
            if (avgIc >= 0.06) {
                break;
            } else {
                keyLength++;
            }
        } // while--end

        // 列印金鑰長度,分組,重合指數,平均重合指數
        System.out.println("金鑰長度為:" + String.valueOf(keyLength));
        System.out.println("\n密文分組及其重合指數為:");

        for (int i = 0; i < keyLength; ++i) {
            System.out.println(cipherGroup.get(i) + "   重合指數: " + Ic[i]);
        }
        System.out.println("\n平均重合指數為: " + String.valueOf(avgIc));

        return keyLength;
    }// Friedman--end

3.2 確定金鑰

3.2.1 擬重合指數測試法

在知道了金鑰長度n以後,就可將密文分解為n組,每一組都是一個凱撒密碼,然後對每一組用字母頻度分析進行解密,和在一起就能成功解密凱撒密碼。

首先對子密文段重各個字母的頻率進行統計(記為fi, i∈a – z),檢視字母頻率分佈統計概率表(記pi),計運算元密文段長度為n,使用公式:
擬重合指數公式
計算出M0,然後對子密文段移位25次,同樣按照上述方法求出M1 — M25的值。

根據重合指數的定義知:一個有意義的英文文字,M ≈0.065,所以找出M值接近0.065的移位數,就是祕鑰中的對應字母。

擬重合指數測試法的實現程式碼如下:

	// 再次使用重合指數法確定金鑰
    public void decryptCipher(int keyLength, String ciphertext) {
        int[] key = new int[keyLength];
        ArrayList<String> cipherGroup = new ArrayList<>();
        double[] probability = new double[]{0.082, 0.015, 0.028, 0.043, 0.127, 0.022, 0.02, 0.061, 0.07, 0.002, 0.008,
                0.04, 0.024, 0.067, 0.075, 0.019, 0.001, 0.06, 0.063, 0.091, 0.028, 0.01, 0.023, 0.001, 0.02, 0.001};

        // 1 先根據金鑰長度分組
        for (int i = 0; i < keyLength; ++i) {
            StringBuilder temporaryGroup = new StringBuilder();
            for (int j = 0; i + j * keyLength < ciphertext.length(); ++j) {
                temporaryGroup.append(ciphertext.charAt(i + j * keyLength));
            }
            cipherGroup.add(temporaryGroup.toString());
        }

        // 2 確定金鑰
        for (int i = 0; i < keyLength; ++i) {
            double MG; // 重合指數
            int flag; // 移動位置
            int g = 0; // 密文移動g個位置
            HashMap<Character, Integer> occurrenceNumber; // 字母出現次數
            String subCipher; // 子串

            while (true) {
                MG = 0;
                flag = 65 + g;
                subCipher = cipherGroup.get(i);
                occurrenceNumber = new HashMap<>();

                // 2.1 初始化字母及其次數
                for (int h = 0; h < 26; ++h) {
                    occurrenceNumber.put((char) (h + 65), 0);
                }

                // 2.2 統計字母出現次數
                for (int j = 0; j < subCipher.length(); ++j) {
                    occurrenceNumber.put(subCipher.charAt(j), occurrenceNumber.get(subCipher.charAt(j)) + 1);
                }

                // 2.3 計算重合指數
                for (int k = 0; k < 26; ++k, ++flag) {
                    double p = probability[k];
                    flag = (flag == 91) ? 65 : flag;
                    double f = (double) occurrenceNumber.get((char) flag) / subCipher.length();
                    MG += p * f;
                }

                // 2.4 判斷退出條件
                if (MG >= 0.055) {
                    key[i] = g;
                    break;
                } else {
                    ++g;
                }
            } // while--end
        } // for--end

        // 3 列印金鑰
        StringBuilder keyString = new StringBuilder();
        for (int i = 0; i < keyLength; ++i) {
            keyString.append((char) (key[i] + 65));
        }
        System.out.println("\n金鑰為: " + keyString.toString());

        // 4 解密
        StringBuilder plainBuffer = new StringBuilder();
        for (int i = 0; i < ciphertext.length(); ++i) {
            int keyFlag = i % keyLength;
            int change = (int) ciphertext.charAt(i) - 65 - key[keyFlag];
            char plainLetter = (char) ((change < 0 ? (change + 26) : change) + 65);
            plainBuffer.append(plainLetter);
        }
        System.out.println("\n明文為:\n" + plainBuffer.toString().toLowerCase());
    }

4. 演算法執行例項

密文如下:

KCCPKBGUFDPHQTYAVINRRTMVGRKDNBVFDETDGILT
XRGUDDKOTFMBPVGEGLTGCKQRACQCWDNAWCRXI
ZAKFTLEWRPTYCQKYVXCHKFTPONCQQRHJVAJUWE
TMCMSPKQDYHJVDAHCTRLSVSKCGCZQQDZXGSFRL
SWCWSJTBHAFSIASPRJAHKJRJUMVGKMITZHFPDISP
ZLVLGWTFPLKKEBDPGCEBSHCTJRWXBAFSPEZQNR
WXCVYCGAONWDDKACKAWBBIKFTIOVKCGGHJVLNHI
FFSQESVYCLACNVRWBBIREPBBVFEXOSCDYGZWPFD
TKFQIYCWHJVLNHIQIBTKHJVNPIST

執行結果如下:

金鑰長度為:6

密文分組及其重合指數為:
KGQNGVGGTGCQWAWQHNJEPJTKQFWAPJGHPWKCTAQVNCIVJFVNIVCPQJQJT   重合指數: 0.061557402277623886
CUTRRFIUFEKCCKRKKCVTKVRCDRSFRRKFZTEEJFNYWKKKVFYVRFDFIVIV   重合指數: 0.08227040816326531
CFYRKDLDMGQWRFPYFQAMQDLGZLJSJJMPLFBBRSRCDAFCLSCREEYDYLBN   重合指數: 0.04846938775510204
PDATDETDBLRDXTTVTQJCDASCXSTIAUIDVPDSWPWGDWTGNQLWPXGTCNTP   重合指數: 0.06377551020408163
KPVMNTXKPTANILYXPRUMYHVZGWBAHMTILLPHXEXAKBIGHEABBOZKWHKI   重合指數: 0.042091836734693876
BHIVBDROVGCAZECCOHWSHCSQSCHSKVZSGKGCBZCOABOHISCBBSWFHIHS   重合指數: 0.07206632653061225

平均重合指數為: 0.061705145277563156

金鑰為: CRYPTO

明文為(手動分詞):
i learned how to calculate the amount of paper needed for a 
room when i was at school you multiply the square foot age 
of the walls by the cubic contents of the floor and ceiling 
combined and double it you then allow half the total for 
openings such as windows and doors then you allow the 
other half for matching the pattern then you double the 
whole thing again to give a margin of error and then you 
order the paper