詳細解析DES系列加密技術(二)

melon_jj發表於2018-08-13

  我們在上一篇《詳細解析DES系列加密技術(一)》中提到說DES在1999年1月被破解,並且有分析報告提出DES演算法在理論上存在的一些漏洞,另外,2001年,DES作為一個標準已經被取代了.一旦一種加密技術被破解,那麼,被取代也就是必然的事情了,對於DES來說,取代他的又是誰呢?今天我們來討論一下DES的後輩,也就是3DES和AES.

  3DES(triple-DES)是為了增加DES的強度,將DES重複3次所得到的一種密碼演算法,也被稱為TDEA(TripleDataEncryptionAlgorithm),縮寫為3DES,其過程如下圖所示:

詳細解析DES系列加密技術(二)
  明文經過3次DES處理才能變成最後的密文,由於DES金鑰的長度實質上是56bit,因此3DES的金鑰長度就是168bit.

  雖然3DES是將DES重複了3次,但是透過上圖就能夠發現3DES並不是進行了三次DES的加密(加密—加密—加密),而是加密—解密—加密.這個是由IMB設計出來的,目的是為了相容普通的DES,比如說當3DES的三個金鑰都相同時,3DES也就相當於是普通的DES了.也就是說3DES具有向下相容性.

  在上一篇博文中我們談到DES時說,DES的加密和解密過程只是改變了子金鑰的順序,而實際上處理都是相同的.所以說,如果所有金鑰都使用相同的位元序列,那麼其結果與普通的DES就是等價的.

  我們看剛剛那幅圖,如果金鑰1和金鑰3使用相同的金鑰,而金鑰2使用不同的金鑰(也就是隻使用兩個DES金鑰),這種三重DES就成為DES-EDE2.EDE表示的是加密(Encryption)—解密(Decryption)—加密(Encryption),如下圖所示:

詳細解析DES系列加密技術(二)
  3DES的解密過程與加密過程正好相反,是以金鑰3,金鑰2,金鑰1的順序執行解密—加密—解密的操作.以下是Java實現3DES加/解密的過程:

  附帶程式碼1.javapackage org.shangzeng.cipher;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

public class ThreeDESTest {

    public static void main(String[] args) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
        String content="Hello,ThreeDES Test!";
        String key="shangzengxueyuan2018";
        byte[] encryptResult=encryThreeDES(content,key);
        String result=convertByteToHexString(encryptResult);
        System.out.println("3DES加密後為:"+result);
        byte[] decryptResult=decryptThreeDES(encryptResult,key);
        System.out.println("3DES解密後的內容為:"+new String(decryptResult));
    }


    /***
     *  將byte陣列轉換成16進位制輸出
     */
    public static String convertByteToHexString(byte[] byteArray){
        String result="";
        for (int i = 0; i < byteArray.length; i++) {
            int temp=byteArray[i]& 0xff;
            String tempHex= Integer.toHexString(temp);
            if(tempHex.length()<2){
                result+="0"+tempHex;
            }else{
                result+=tempHex;
            }
        }
        return result;
    }

    public static byte[] get3DESKey(String pass){
        byte[] key=new byte[24];//3DES裡的金鑰key為24位
        byte[] temp;
        try {
            temp=pass.getBytes("UTF-8");//將字串轉成位元組陣列
            if(key.length>temp.length){
                //如果temp不夠24位,則拷貝temp陣列整個長度的內容到key陣列中
                System.arraycopy(temp,0,key,0,temp.length);
            }else{
                //如果temp大於24位,則拷貝temp24個長度的內容到key陣列中
                System.arraycopy(temp,0,key,0,key.length);
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return key;
    }

    private static final String ALGORITHM="DESede";
    public static byte[] encryThreeDES(String message,String key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        return threeDES(message.getBytes(),key,Cipher.ENCRYPT_MODE);
    }
    public static byte[] decryptThreeDES(byte[] contentArray,String key) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
        return threeDES(contentArray,key,Cipher.DECRYPT_MODE);
    }

    private static byte[] threeDES(byte[] contentArray,String key,int mode) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        SecretKey desKey=new SecretKeySpec(get3DESKey(key), ALGORITHM);
        Cipher cipher= null;
        cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(mode,desKey);
        return cipher.doFinal(contentArray);
    }
}
複製程式碼

  3DES雖然比DES要安全一些,但是他的處理速度不高.所以除了特別重視向下相容性的情況以外,很少被用於新的用途.接下來我們來談談AES.

  AES(AdvancedEncryptionStandard)也就是高階加密標準,他是為取代DES而成為新標準的一種對稱加密演算法.談到這裡我們發現,AES和DES一樣,只是加密的標準,而不負責具體的加密演算法,DES是由Feistel實現的,而AES的實現,則由NIST(NationalInstituteofStandardsandTechnology,國家標準技術研究所)組織一個公開競選活動去競選.這個研究所選拔的密碼演算法,會成為美國的國家標準,也就是我們在上一篇文章中提到的FIPS.因為參加這個競選的條件是:被選擇AES的密碼演算法必須無條件地免費供全世界使用,所以AES雖然是美國的標準,卻也和DES一樣,成為了世界性的標準.

  AES競選的參與者還必須提交密碼演算法的詳細規格書、以ANSIC和Java編寫的實現程式碼以及抗密碼破譯強度的評估等材料.所以,參加者所提交的密碼演算法,必須在詳細設計和程式程式碼完全公開的情況下,依然保證較高的強度,這樣呢,就杜絕了隱蔽式安全性.

  AES競選的活動雖然是由NIST組織的,但密碼演算法的評審卻不是由NIST完成的,這個評審是由全世界的企業和密碼學家共同完成的.所以參加者都會努力從各個角度尋找其他密碼演算法的弱點,並向其他參與評審的人進行證明.這種方式就是通過競爭來實現標準化.

  在前面我們提到過,3DES雖然從安全性上來說優於DES,但是,他的處理速度並不高.因此,我們必須要考慮實現AES的演算法的處理速度,除此之外,演算法本身是否存在弱點、實現的容易性,金鑰準備的速度,能否在各種平臺,比如智慧卡,8位CPU等低效能平臺以及工作站等高效能平臺上有效工作,這些均是AES選拔參與者需要考慮的範圍.

  從1997年,NIST開始募集,到1999年,15個演算法有5個演算法入圍,最終2000年10月2日,Rijndael力壓群雄,被NIST選定為AES標準.Rijndael是由比利時密碼學家JoanDaemen和VincentRijmen設計的分組密碼演算法.不知道Rijndal是否和這兩位密碼學家的名字有關係.

  下面我們來詳細說明一下Rijndael的加密和解密:

  首先Rijndael的分組長度和金鑰長度可以分別以32bit為單位在128bit到256bit的範圍內進行選擇.不過,在AES的規格中,分組長度固定位128bit,金鑰長度只有128,192和256三種.

  和DES一樣,Rijndael演算法也是由多個輪構成的,只不過,DES使用Feistel網路作為其基本結構,而Rijndael則是使用了SPN接面構.其一個單位的加解密過程如下圖所示:

詳細解析DES系列加密技術(二)
  現在我們對這四種處理進行詳細解析:

  通過上圖我們知道Rijndael的單位輸入分組為128bit,也就是16位元組.首先需要對這16個位元組進行SubBytes處理.所謂SubBytes處理可以簡單理解為輸入的每個位元組的值都在另一張替換表中擁有其對應的值,然後按著這種對應關係進行一一替換.如下圖所示:

詳細解析DES系列加密技術(二)
  在SubBytes之後,就是ShiftRows處理,這一步是將以4位元組為一個單位的行按著一定的規則向左平移,並且每一行平移的位元組數不相同,如下圖所示:

詳細解析DES系列加密技術(二)
  之後我們開始進行第三步處理,即MixColumns處理,這一步仍然是對以4位元組為單位的值進行位元運算,將其變為另外一個4位元組值,類似於第一步的SubBytes,如下圖所示:

詳細解析DES系列加密技術(二)
  最後我們要將MixColumns的輸出與輪金鑰進行XOR,即AddRoundKey處理,到這裡,Rijndael的一輪就結束了.

  因為在Rijndael中所有輸入的bit都會在一輪中進行加密,因此與Feistel網路相比,加密所需要的輪數就減少了.並且以上這四種方式可以並行進行.至於解密過程,除了最後一步AddRoundKey與加密時相同之外,其他三種處理都是與原步驟相對應的的逆運算.以下是AES的Java實現:

  附加程式碼2.javapackage org.shangzeng.testsignature;

import javax.crypto.*;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class AESTest {

    private static String convertByteToHexString(byte[] byteArray){
        String result="";
        for (int i = 0; i < byteArray.length; i++) {
            int temp=byteArray[i]& 0xff;
            String tempHex= Integer.toHexString(temp);
            if(tempHex.length()<2){
                result+="0"+tempHex;
            }else{
                result+=tempHex;
            }
        }
        return result;
    }

    static final String ALGORITHM="AES";
    static SecretKey secretKey=null;
    public static SecretKey generateKey() throws NoSuchAlgorithmException {
        if(null==secretKey){
            KeyGenerator keyGenerator=KeyGenerator.getInstance(ALGORITHM);
            SecureRandom secureRandom=new SecureRandom();
            keyGenerator.init(secureRandom);
            secretKey=keyGenerator.generateKey();
        }
        return secretKey;
    }

    public static byte[] encrypt(String content) throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchPaddingException {
        SecretKey secretKey=generateKey();
        return aes(Cipher.ENCRYPT_MODE,secretKey,content.getBytes(Charset.forName("UTF-8")));
    }

    public static String decrypt(byte[] contentArray) throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchPaddingException, UnsupportedEncodingException {
        SecretKey secretKey=generateKey();
        byte[] resultByte= aes(Cipher.DECRYPT_MODE,secretKey,contentArray);
        return new String(resultByte,"UTF-8");
    }


    private static byte[] aes(int mode,Key key,byte[] contentArray) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        Cipher cipher=Cipher.getInstance(ALGORITHM);
        cipher.init(mode,key);
        byte[] result=cipher.doFinal(contentArray);
        return result;
    }

    public static void main(String[] args) throws InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, NoSuchPaddingException, UnsupportedEncodingException {
        String content="你好,我是熵熷學院Jacob老師.";
        byte[] encryptResult=encrypt(content);
        String resultByHex=convertByteToHexString(encryptResult);
        String resultByUnicode=new String(encryptResult,"UTF-8");
        System.out.println("加密後的結果16進製表示為:"+resultByHex+"\n 加密後的結果轉換成字串為:"+resultByUnicode);
        String decryptResult=decrypt(encryptResult);
        System.out.println("解密後的結果為:"+decryptResult);
    }
}
複製程式碼

  對於Rijndael來說,因為它具有非常嚴謹的數學結構,也就是說從明文到密文的計算過程可以全部用公式來表達,這是之前其他演算法所不具備的性質,所以,嚴謹的數學結構這個性質也就意味著Rijndael能夠通過數學方法進行破譯,當然這目前只是一種假設.目前來看,還沒有出現針對Rijndael的有效攻擊,因此以Rijndael為實現的AES還是比較安全的.

相關文章