加解密演算法分析

rickiyang發表於2020-06-29

日常開發中,無論你是使用什麼語言,都應該遇到過使用加解密的使用場景,比如介面資料需要加密傳給前端保證資料傳輸的安全;HTTPS使用證照的方式首先進行非對稱加密,將客戶端的私匙傳遞給服務端,然後雙方後面的通訊都使用該私匙進行對稱加密傳輸;使用MD5進行檔案一致性校驗,等等很多的場景都使用到了加解密技術。

很多時候我們對於什麼時候要使用什麼樣的加解密方式是很懵的。因為可用的加解密方案實在是太多,大家對加解密技術的型別可能不是很清楚,今天這篇文章就來梳理一下目前主流的加解密技術,本篇文件只針對演算法做科普性說明,不涉及具體演算法分析。日常使用的加解密大致可以分為以下四類:

  1. 雜湊函式(也稱資訊摘要)演算法
  2. 對稱加密演算法
  3. 非對稱加密演算法
  4. 組合加密技術

1. 雜湊函式演算法

聽名字似乎不是一種加密演算法,類似於給一個物件計算出hash值。所以這種演算法一般用於資料特徵提取。常用的雜湊函式包括:MD5、SHA1、SHA2(包括SHA128、SHA256等)雜湊函式的應用很廣,雜湊函式有個特點,它是一種單向加密演算法,只能加密、無法解密。

1.1 MD5

先來看MD5演算法,MD5演算法是廣為使用的資料特徵提取演算法,最常見的就是我們在下載一些軟體,網站都會提供MD5值給你進行校驗,你可以通過MD5值是否一致來檢查當前檔案是否被別人篡改。MD5演算法具有以下特點:

  1. 任意長度的資料得到的MD5值長度都是相等的;
  2. 對原資料進行任一點修改,得到的MD5值就會有很大的變化;
  3. 雜湊函式的不可逆性,即已知原資料,無法通過特徵值反向獲取原資料。(需要說明的是2004年的國際密碼討論年會(CRYPTO)尾聲,王小云及其研究同事展示了MD5、SHA-0及其他相關雜湊函式的雜湊衝撞。也就是說,她找出了第一個 兩個值不同,但 MD5 值相同的碰撞的例子。這個應該不能稱之為破解
1.2 MD5用途:
  1. 防篡改。上面說過用於檔案完整性校驗。
  2. 用於不想讓別人看到明文的地方。比如使用者密碼入庫,可以將使用者密碼使用MD5加密儲存,下次使用者輸入密碼登入只用將他的輸入進行MD5加密與資料庫的值判斷是否一致即可,這樣就有效防止密碼洩露的風險。
  3. 用於檔案秒傳。比如百度雲的檔案秒傳功能可以用這種方式來實現。在你點選上傳的時候,前端同學會先計算檔案的MD5值然後與服務端比對是否存在,如果有就會告訴你檔案上傳成功,即完成所謂的秒傳。

在JDK中提供了MD5的實現:java.security包中有個類MessageDigest,MessageDigest 類為應用程式提供資訊摘要演算法的功能,如 MD5 或 SHA 演算法。資訊摘要是安全的單向雜湊函式,它接收任意大小的資料,輸出固定長度的雜湊值。

MessageDigest 物件使用getInstance函式初始化,該物件通過使用 update 方法處理資料。任何時候都可以呼叫 reset 方法重置摘要。一旦所有需要更新的資料都已經被更新了,應該呼叫 digest 方法之一完成雜湊計算。

對於給定數量的更新資料,digest 方法只能被呼叫一次。digest 被呼叫後,MessageDigest 物件被重新設定成其初始狀態。

下面的例子展示了使用JDK自帶的MessageDigest類使用MD5演算法。同時也展示瞭如果使用了update方法後沒有呼叫digest方法,則會累計當前所有的update中的值在下一次呼叫digest方法的時候一併輸出:

package other;

import java.security.MessageDigest;

/**
 * @author: rickiyang
 * @date: 2019/9/13
 * @description:
 */
public class MD5Test {

    static char[] hex = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

    public static void main(String[] args) {
        try {
            //申明使用MD5演算法
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.update("a".getBytes());//
            System.out.println("md5(a)=" + byte2str(md5.digest()));
            md5.update("a".getBytes());
            md5.update("bc".getBytes());
            System.out.println("md5(abc)=" + byte2str(md5.digest()));
            //你會發現上面的md5值與下面的一樣
            md5.update("abc".getBytes());
            System.out.println("md5(abc)=" + byte2str(md5.digest()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 將位元組陣列轉換成十六進位制字串
     *
     * @param bytes
     * @return
     */
    private static String byte2str(byte[] bytes) {
        int len = bytes.length;
        StringBuffer result = new StringBuffer();
        for (int i = 0; i < len; i++) {
            byte byte0 = bytes[i];
            result.append(hex[byte0 >>> 4 & 0xf]);
            result.append(hex[byte0 & 0xf]);
        }
        return result.toString();
    }
}

輸出:

md5(a)=0CC175B9C0F1B6A831C399E269772661
md5(abc)=900150983CD24FB0D6963F7D28E17F72
md5(abc)=900150983CD24FB0D6963F7D28E17F72
2.1 SHA系列演算法

Secure Hash Algorithm,是一種與MD5同源的資料加密演算法。SHA演算法能計算出一個數位資訊所對應到的,長度固定的字串,又稱資訊摘要。而且如果輸入資訊有任何的不同,輸出的對應摘要不同的機率非常高。因此SHA演算法也是FIPS所認證的五種安全雜湊演算法之一。原因有兩點:一是由資訊摘要反推原輸入資訊,從計算理論上來說是極為困難的;二是,想要找到兩組不同的輸入資訊發生資訊摘要碰撞的機率,從計算理論上來說是非常小的。任何對輸入資訊的變動,都有很高的機率導致的資訊摘要大相徑庭。

SHA實際上是一系列演算法的統稱,分別包括:SHA-1、SHA-224、SHA-256、SHA-384以及SHA-512。後面4中統稱為SHA-2,事實上SHA-224是SHA-256的縮減版,SHA-384是SHA-512的縮減版。各中SHA演算法的資料比較如下表,其中的長度單位均為位:

類別 SHA-1 SHA-224 SHA-256 SHA-384 SHA-512
訊息摘要長度 160 224 256 384 512
訊息長度 小於264位 小於264位 小於264位 小於2128位 小於2128位
分組長度 512 512 512 1024 1024
計算字長度 32 32 32 64 64
計算步驟數 80 64 64 80 80

SHA-1演算法輸入報文的最大長度不超過264位,產生的輸出是一個160位的報文摘要。輸入是按512 位的分組進行處理的。SHA-1是不可逆的、防衝突,並具有良好的雪崩效應。

上面提到的MessageDigest類同時也支援SHA系列演算法,使用方式與MD5一樣,注意SHA不同的型別:

MessageDigest md = MessageDigest.getInstance("SHA");
MessageDigest md = MessageDigest.getInstance("SHA-224");
MessageDigest md = MessageDigest.getInstance("SHA-384");

2. 對稱加密演算法

所謂的對稱加密,意味著加密者和解密者需要同時持有一份相同的密匙,加密者用密匙加密,解密者用密匙解密即可。

常用的對稱加密演算法包括DES演算法、AES演算法等。 由於對稱加密需要一個祕鑰,而祕鑰在加密者與解密者之間傳輸又很難保證安全性,所以目前用對稱加密演算法的話主要是用在加密者解密者相同,或者加密者解密者相對固定的場景。

對稱演算法又可分為兩類:

第一種是一次只對明文中的單個位(有時對位元組)運算的演算法稱為序列演算法或序列密碼;

另一種演算法是對明文的一組位進行運算,這些位組稱為分組,相應的演算法稱為分組演算法或分組密碼。現代計算機密碼演算法的典型分組長度為64位――這個長度既考慮到分析破譯密碼的難度,又考慮到使用的方便性。

2.1 BASE64演算法

我們很熟悉的BASE64演算法就是一個沒有祕密的對稱加密演算法。因為他的加密解密演算法都是公開的,所以加密資料是沒有任何祕密可言,典型的防菜鳥不防程式設計師的演算法。

BASE64演算法作用:

  1. 用於簡單的資料加密傳輸;

  2. 用於資料傳輸過程中的轉碼,解決中文問題和特殊符號在網路傳輸中的亂碼現象。

    網路傳輸過程中如果雙方使用的編解碼字符集方式不一致,對於中文可能會出現亂碼;與此類似,網路上傳輸的字元並不全是可列印的字元,比如二進位制檔案、圖片等。Base64的出現就是為了解決此問題,它是基於64個可列印的字元來表示二進位制的資料的一種方法。

BASE64原理

BASE64的原理比較簡單,每當我們使用BASE64時都會先定義一個類似這樣的陣列:

['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']

上面就是BASE64的索引表,字元選用了"A-Z、a-z、0-9、+、/" 64個可列印字元,這是標準的BASE64協議規定。在日常使用中我們還會看到“=”或“==”號出現在BASE64的編碼結果中,“=”在此是作為填充字元出現。

JDK提供了BASE64的實現:BASE64Encoder,我們可以直接使用:

//使用base64加密
BASE64Encoder encoder = new BASE64Encoder();  
String encrypt = encoder.encode(str.getBytes());  
//使用base64解密
BASE64Decoder decoder = new BASE64Decoder();  
String decrypt = new String(decoder.decodeBuffer(encryptStr));  
2.2 DES

DES (Data Encryption Standard),在很長時間內,許多人心目中“密碼生成”與DES一直是個同義詞。

DES是一個分組加密演算法,典型的DES以64位為分組對資料加密,加密和解密用的是同一個演算法。它的金鑰長度是56位(因為每個第8 位都用作奇偶校驗),金鑰可以是任意的56位的數,而且可以任意時候改變。

DES加密過程大致如下:

  1. 首先需要從使用者處獲取一個64位長的密碼口令,然後通過等分、移位、選取和迭代形成一套16個加密金鑰,分別供每一輪運算中使用;
  2. 然後將64位的明文分組M進行操作,M經過一個初始置換IP,置換成m0。將m0明文分成左半部分和右半部分m0 = (L0,R0),各32位長。然後進行16輪完全相同的運算(迭代),這些運算被稱為函式f,在每一輪運算過程中資料與相應的金鑰結合;
  3. 在每一輪迭代中金鑰位移位,然後再從金鑰的56位中選出48位。通過一個擴充套件置換將資料的右半部分擴充套件成48位,並通過一個異或操作替代成新的48位資料,再將其壓縮置換成32位。這四步運算構成了函式f。然後,通過另一個異或運算,函式f的輸出與左半部分結合,其結果成為新的右半部分,原來的右半部分成為新的左半部分。將該操作重複16次;
  4. 經過16輪迭代後,左,右半部分合在一起經過一個末置換(資料整理),這樣就完成了加密過程。

對於DES解密的過程大家猛然一想應該是使用跟加密過程相反的演算法,事實上解密和加密使用的是一樣的演算法,有區別的地方在於加密和解密在使用密匙的時候次序是相反的。比如加密的時候是K0,K1,K2......K15,那麼解密使用密匙的次序就是倒過來的。之所以能用相同的演算法去解密,這跟DES特意設計的加密演算法有關,感興趣的同學可以深入分析。

2.3 AES

高階加密標準(AES,Advanced Encryption Standard),與DES一樣,使用AES加密函式和密匙來對明文進行加密,區別就是使用的加密函式不同。

上面說過DES的金鑰長度是56位元,因此演算法的理論安全強度是2^56。但以目前計算機硬體的製作水準和升級情況,破解DES可能只是山脈問題,最終NIST(美國國家標準技術研究所(National Institute of Standards and Technology))選擇了分組長度為128位的Rijndael演算法作為AES演算法。

AES為分組密碼,分組密碼也就是把明文分成一組一組的,每組長度相等,每次加密一組資料,直到加密完整個明文。在AES標準規範中,分組長度只能是128位,也就是說,每個分組為16個位元組(每個位元組8位)。金鑰的長度可以使用128位、192位或256位。金鑰的長度不同,推薦加密輪數也不同,如下表所示:

AES 金鑰長度(32位位元字) 分組長度(32位位元字) 加密輪數
AES-128 4 4 10
AES-192 6 4 12
AES-256 8 4 14

3. 非對稱加密

非對稱加密演算法的特點是,祕鑰一次會生成一對,其中一份祕鑰由自己儲存,不能公開出去,稱為“私鑰”,另外一份是可以公開出去的,稱為“公鑰”。

將原文用公鑰進行加密,得到的密文只有用對應私鑰才可以解密得到原文;

將原文用私鑰加密得到的密文,也只有用對應的公鑰才能解密得到原文;

因為加密和解密使用的是兩個不同的金鑰,所以這種演算法叫作非對稱加密演算法

與對稱加密演算法的對比
  • 優點:其安全性更好,對稱加密的通訊雙方使用相同的祕鑰,如果一方的祕鑰遭洩露,那麼整個通訊就會被破解。而非對稱加密使用一對祕鑰,一個用來加密,一個用來解密,而且公鑰是公開的,祕鑰是自己儲存的,不需要像對稱加密那樣在通訊之前要先同步祕鑰。
  • 缺點:非對稱加密的缺點是加密和解密花費時間長、速度慢,只適合對少量資料進行加密。

在非對稱加密中使用的主要演算法有:RSA、Elgamal、ESA、揹包演算法、Rabin、D-H、ECC(橢圓曲線加密演算法)等。不同演算法的實現機制不同。

非對稱加密工作原理

下面我們就看一下非對稱加密的工作原理。

  • 乙方生成一對金鑰(公鑰和私鑰)並將公鑰向其它方公開。
  • 得到該公鑰的甲方使用該金鑰對機密資訊進行加密後再傳送給乙方。
  • 乙方再用自己儲存的另一把專用金鑰(私鑰)對加密後的資訊進行解密。乙方只能用其專用金鑰(私鑰)解密由對應的公鑰加密後的資訊。
  • 在傳輸過程中,即使攻擊者截獲了傳輸的密文,並得到了乙的公鑰,也無法破解密文,因為只有乙的私鑰才能解密密文。同樣,如果乙要回復加密資訊給甲,那麼需要甲先公佈甲的公鑰給乙用於加密,甲自己儲存甲的私鑰用於解密。
非對稱加密鼻祖:RSA

RSA演算法基於一個十分簡單的數論事實:將兩個大質數(素數)相乘十分容易,但是想要對其乘積進行因式分解卻極其困難,因此可以將乘積公開作為加密金鑰。比如:取兩個簡單的質數:67,73,得到兩者乘積很簡單4891;但是要想對4891進行因式分解,其工作量成幾何增加。

應用場景:

HTTPS請求的SSL層。

在JDK中也提供了RSA的實現,下面給出示例:

	/**
     * 建立密匙對
     *
     * @return
     */
    private KeyPair genKeyPair() {
        //建立 RSA Key 的生產者。
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");

        //利用使用者密碼作為隨機數初始化出 1024 位元 Key 的生產者。
        //SecureRandom 是生成安全隨機數序列,password.getBytes() 是種子,只要種子相同,序列就一樣。
        keyPairGen.initialize(1024, new SecureRandom("password".getBytes()));

        //建立金鑰對
        return keyPairGen.generateKeyPair();
    }


    /**
     * 生成公匙
     *
     * @return
     */
    public PublicKey genPublicKey() {
        try {
            //建立金鑰對
            KeyPair keyPair = genKeyPair();

            //生成公鑰
            PublicKey publicKey = keyPair.getPublic();
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            publicKey = keyFactory.generatePublic(keySpec);
            return publicKey;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }

    /**
     * 生成私匙
     *
     * @return
     */
    public PrivateKey genPrivateKey() {
        try {
            //建立金鑰對
            KeyPair keyPair = genKeyPair();

            //生成私匙
            PrivateKey privateKey = keyPair.getPrivate();
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(privateKey.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePrivate(keySpec);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 公鑰加密
     *
     * @param data
     * @param publicKey
     * @return
     * @throws Exception
     */
    public static byte[] encryptByPublicKey(byte[] data, String publicKey)
            throws Exception {
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKey.getBytes());
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        Key publicK = keyFactory.generatePublic(x509KeySpec);
        // 對資料加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicK);
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 對資料分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > 117) {
                cache = cipher.doFinal(data, offSet, 117);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * 117;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;
    }

    /**
     * 私鑰解密
     *
     * @param encryptedData
     * @param privateKey
     * @return
     * @throws Exception
     */
    public static byte[] decryptByPrivateKey(byte[] encryptedData,
                                             String privateKey) throws Exception {
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey.getBytes());
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateK);
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 對資料分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > 118) {
                cache = cipher.doFinal(encryptedData, offSet, 118);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * 118;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }

    /**
     * 私鑰加密
     *
     * @param data
     * @param privateKey
     * @return
     * @throws Exception
     */
    public static byte[] encryptByPrivateKey(byte[] data, String privateKey)
            throws Exception {
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(publicKey.getBytes());
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateK);
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 對資料分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > 117) {
                cache = cipher.doFinal(data, offSet, 117);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * 117;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;
    }

	/**
     * 公鑰解密
     *
     * @param encryptedData
     * @param publicKey
     * @return
     * @throws Exception
     */
    public static byte[] decryptByPublicKey(byte[] encryptedData,
                                            String publicKey) throws Exception {
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKey.getBytes());
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        Key publicK = keyFactory.generatePublic(x509KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicK);
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 對資料分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > 118) {
                cache = cipher.doFinal(encryptedData, offSet, 118);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * 118;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }

4. 組合加密

上面介紹的3種加密技術,每一種都有自己的特點,比如雜湊技術用於特徵值提取,對稱加密速度雖快但是有私匙洩露的危險,非對稱加密雖然安全但是速度卻慢。基於這些情況,現在的加密技術更加趨向於將這些加密的方案組合起來使用,基於此來研發新的加密演算法。

MAC(Message Authentication Code,訊息認證碼演算法)是含有金鑰雜湊函式演算法,相容了MD和SHA演算法的特性,並在此基礎上加上了金鑰。因此MAC演算法也經常被稱作HMAC演算法。MAC(Message Authentication Code,訊息認證碼演算法)是含有金鑰雜湊函式演算法,HMAC加密可以理解為加鹽的雜湊演算法,此處的“鹽”就相當於HMAC演算法的祕鑰。

HMAC演算法的實現過程需要一個加密用的雜湊函式(表示為H)和一個金鑰。

經過MAC演算法得到的摘要值也可以使用十六進位制編碼表示,其摘要值得長度與實現演算法的摘要值長度相同。例如 HmacSHA演算法得到的摘要長度就是SHA1演算法得到的摘要長度,都是160位二進位制數,換算成十六進位制的編碼為40位。

MAC演算法的實現:

演算法 摘要長度 備註
HmacMD5 128 JAVA6實現
HmacSHA1 160 JAVA6實現
HmacSHA256 256 JAVA6實現
HmacSHA384 384 JAVA6實現
HmacSHA512 512 JAVA6實現
HmacMD2 128 BouncyCastle實現
HmacMD4 128 BouncyCastle實現
HmacSHA224 224 BouncyCastle實現

過程如下:

  1. 在金鑰key後面新增0來建立一個長為B(64位元組)的字串(str);
  2. 將上一步生成的字串(str) 與ipad(0x36)做異或運算,形成結果字串(istr);
  3. 將資料流data附加到第二步的結果字串(istr)的末尾;
  4. 做md5運算於第三步生成的資料流(istr);
  5. 將第一步生成的字串(str) 與opad(0x5c)做異或運算,形成結果字串(ostr),再將第四步的結果(istr) 附加到第五步的結果字串(ostr)的末尾做md5運算於第6步生成的資料流(ostr),最終輸出結果(out)

注意:如果第一步中,key的長度klen大於64位元組,則先進行md5運算,使其長度klen = 16位元組。

JDK中的實現:

public static void jdkHmacMD5() {
    try {
        // 初始化KeyGenerator
        KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacMD5");
        // 產生金鑰
        SecretKey secretKey = keyGenerator.generateKey();
        // 獲取金鑰
        byte[] key = secretKey.getEncoded();
        //            byte[] key = Hex.decodeHex(new char[]{'1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e'});
        // 還原金鑰
        SecretKey restoreSecretKey = new SecretKeySpec(key, "HmacMD5");
        // 例項化MAC
        Mac mac = Mac.getInstance(restoreSecretKey.getAlgorithm());
        // 初始化MAC
        mac.init(restoreSecretKey);
        // 執行摘要
        byte[] hmacMD5Bytes = mac.doFinal("data".getBytes());
        System.out.println("jdk hmacMD5:" + new String(hmacMD5Bytes));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

相關文章