日常開發(fā)中,無論你是使用什么語言,都應該遇到過使用加解密的使用場景,比如接口數(shù)據(jù)需要加密傳給前端保證數(shù)據(jù)傳輸?shù)陌踩籋TTPS使用證書的方式首先進行非對稱加密,將客戶端的私匙傳遞給服務(wù)端,然后雙方后面的通信都使用該私匙進行對稱加密傳輸;使用MD5進行文件一致性校驗,等等很多的場景都使用到了加解密技術(shù)。
很多時候我們對于什么時候要使用什么樣的加解密方式是很懵的。因為可用的加解密方案實在是太多,大家對加解密技術(shù)的類型可能不是很清楚,今天這篇文章就來梳理一下目前主流的加解密技術(shù),本篇文檔只針對算法做科普性說明,不涉及具體算法分析。日常使用的加解密大致可以分為以下四類:
- 散列函數(shù)(也稱信息摘要)算法
- 對稱加密算法
- 非對稱加密算法
- 組合加密技術(shù)
1. 散列函數(shù)算法#
聽名字似乎不是一種加密算法,類似于給一個對象計算出hash值。所以這種算法一般用于數(shù)據(jù)特征提取。常用的散列函數(shù)包括:MD5、SHA1、SHA2(包括SHA128、SHA256等)散列函數(shù)的應用很廣,散列函數(shù)有個特點,它是一種單向加密算法,只能加密、無法解密。
1.1 MD5
先來看MD5算法,MD5算法是廣為使用的數(shù)據(jù)特征提取算法,最常見的就是我們在下載一些軟件,網(wǎng)站都會提供MD5值給你進行校驗,你可以通過MD5值是否一致來檢查當前文件是否被別人篡改。MD5算法具有以下特點:
- 任意長度的數(shù)據(jù)得到的MD5值長度都是相等的;
- 對原數(shù)據(jù)進行任一點修改,得到的MD5值就會有很大的變化;
- 散列函數(shù)的不可逆性,即已知原數(shù)據(jù),無法通過特征值反向獲取原數(shù)據(jù)。(需要說明的是2004年的國際密碼討論年會(CRYPTO)尾聲,王小云及其研究同事展示了MD5、SHA-0及其他相關(guān)雜湊函數(shù)的雜湊沖撞。也就是說,她找出了第一個 兩個值不同,但 MD5 值相同的碰撞的例子。這個應該不能稱之為破解)
1.2 MD5用途:
- 防篡改。上面說過用于文件完整性校驗。
- 用于不想讓別人看到明文的地方。比如用戶密碼入庫,可以將用戶密碼使用MD5加密存儲,下次用戶輸入密碼登錄只用將他的輸入進行MD5加密與數(shù)據(jù)庫的值判斷是否一致即可,這樣就有效防止密碼泄露的風險。
- 用于文件秒傳。比如百度云的文件秒傳功能可以用這種方式來實現(xiàn)。在你點擊上傳的時候,前端同學會先計算文件的MD5值然后與服務(wù)端比對是否存在,如果有就會告訴你文件上傳成功,即完成所謂的秒傳。
在JDK中提供了MD5的實現(xiàn):JAVA.security包中有個類MessageDigest,MessageDigest 類為應用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法。信息摘要是安全的單向哈希函數(shù),它接收任意大小的數(shù)據(jù),輸出固定長度的哈希值。
MessageDigest 對象使用getInstance函數(shù)初始化,該對象通過使用 update 方法處理數(shù)據(jù)。任何時候都可以調(diào)用 reset 方法重置摘要。一旦所有需要更新的數(shù)據(jù)都已經(jīng)被更新了,應該調(diào)用 digest 方法之一完成哈希計算。
對于給定數(shù)量的更新數(shù)據(jù),digest 方法只能被調(diào)用一次。digest 被調(diào)用后,MessageDigest 對象被重新設(shè)置成其初始狀態(tài)。
下面的例子展示了使用JDK自帶的MessageDigest類使用MD5算法。同時也展示了如果使用了update方法后沒有調(diào)用digest方法,則會累計當前所有的update中的值在下一次調(diào)用digest方法的時候一并輸出:
Copypackage 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()));
//你會發(fā)現(xiàn)上面的md5值與下面的一樣
md5.update("abc".getBytes());
System.out.println("md5(abc)=" + byte2str(md5.digest()));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 將字節(jié)數(shù)組轉(zhuǎn)換成十六進制字符串
*
* @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();
}
}
輸出:
Copymd5(a)=0CC175B9C0F1B6A831C399E269772661
md5(abc)=900150983CD24FB0D6963F7D28E17F72
md5(abc)=900150983CD24FB0D6963F7D28E17F72
2.1 SHA系列算法
Secure Hash Algorithm,是一種與MD5同源的數(shù)據(jù)加密算法。SHA算法能計算出一個數(shù)位信息所對應到的,長度固定的字串,又稱信息摘要。而且如果輸入信息有任何的不同,輸出的對應摘要不同的機率非常高。因此SHA算法也是FIPS所認證的五種安全雜湊算法之一。原因有兩點:一是由信息摘要反推原輸入信息,從計算理論上來說是極為困難的;二是,想要找到兩組不同的輸入信息發(fā)生信息摘要碰撞的幾率,從計算理論上來說是非常小的。任何對輸入信息的變動,都有很高的幾率導致的信息摘要大相徑庭。
SHA實際上是一系列算法的統(tǒng)稱,分別包括:SHA-1、SHA-224、SHA-256、SHA-384以及SHA-512。后面4中統(tǒng)稱為SHA-2,事實上SHA-224是SHA-256的縮減版,SHA-384是SHA-512的縮減版。各中SHA算法的數(shù)據(jù)比較如下表,其中的長度單位均為位:
類別
SHA-1SHA-224SHA-256SHA-384SHA-512消息摘要長度160224256384512消息長度小于264位小于264位小于264位小于2128位小于2128位分組長度51251251210241024計算字長度3232326464計算步驟數(shù)8064648080
SHA-1算法輸入報文的最大長度不超過264位,產(chǎn)生的輸出是一個160位的報文摘要。輸入是按512 位的分組進行處理的。SHA-1是不可逆的、防沖突,并具有良好的雪崩效應。
上面提到的MessageDigest類同時也支持SHA系列算法,使用方式與MD5一樣,注意SHA不同的類型:
CopyMessageDigest md = MessageDigest.getInstance("SHA");
MessageDigest md = MessageDigest.getInstance("SHA-224");
MessageDigest md = MessageDigest.getInstance("SHA-384");
2. 對稱加密算法#
所謂的對稱加密,意味著加密者和解密者需要同時持有一份相同的密匙,加密者用密匙加密,解密者用密匙解密即可。
常用的對稱加密算法包括DES算法、AES算法等。 由于對稱加密需要一個秘鑰,而秘鑰在加密者與解密者之間傳輸又很難保證安全性,所以目前用對稱加密算法的話主要是用在加密者解密者相同,或者加密者解密者相對固定的場景。
對稱算法又可分為兩類:
第一種是一次只對明文中的單個位(有時對字節(jié))運算的算法稱為序列算法或序列密碼;
另一種算法是對明文的一組位進行運算,這些位組稱為分組,相應的算法稱為分組算法或分組密碼。現(xiàn)代計算機密碼算法的典型分組長度為64位――這個長度既考慮到分析破譯密碼的難度,又考慮到使用的方便性。
2.1 BASE64算法
我們很熟悉的BASE64算法就是一個沒有秘密的對稱加密算法。因為他的加密解密算法都是公開的,所以加密數(shù)據(jù)是沒有任何秘密可言,典型的防菜鳥不防程序員的算法。
BASE64算法作用:
- 用于簡單的數(shù)據(jù)加密傳輸;
- 用于數(shù)據(jù)傳輸過程中的轉(zhuǎn)碼,解決中文問題和特殊符號在網(wǎng)絡(luò)傳輸中的亂碼現(xiàn)象。網(wǎng)絡(luò)傳輸過程中如果雙方使用的編解碼字符集方式不一致,對于中文可能會出現(xiàn)亂碼;與此類似,網(wǎng)絡(luò)上傳輸?shù)淖址⒉蝗强纱蛴〉淖址热缍M制文件、圖片等。Base64的出現(xiàn)就是為了解決此問題,它是基于64個可打印的字符來表示二進制的數(shù)據(jù)的一種方法。
BASE64原理
BASE64的原理比較簡單,每當我們使用BASE64時都會先定義一個類似這樣的數(shù)組:
Copy['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
上面就是BASE64的索引表,字符選用了"A-Z、a-z、0-9、+、/" 64個可打印字符,這是標準的BASE64協(xié)議規(guī)定。在日常使用中我們還會看到“=”或“==”號出現(xiàn)在BASE64的編碼結(jié)果中,“=”在此是作為填充字符出現(xiàn)。
JDK提供了BASE64的實現(xiàn):BASE64Encoder,我們可以直接使用:
Copy//使用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),在很長時間內(nèi),許多人心目中“密碼生成”與DES一直是個同義詞。
DES是一個分組加密算法,典型的DES以64位為分組對數(shù)據(jù)加密,加密和解密用的是同一個算法。它的密鑰長度是56位(因為每個第8 位都用作奇偶校驗),密鑰可以是任意的56位的數(shù),而且可以任意時候改變。
DES加密過程大致如下:
- 首先需要從用戶處獲取一個64位長的密碼口令,然后通過等分、移位、選取和迭代形成一套16個加密密鑰,分別供每一輪運算中使用;
- 然后將64位的明文分組M進行操作,M經(jīng)過一個初始置換IP,置換成m0。將m0明文分成左半部分和右半部分m0 = (L0,R0),各32位長。然后進行16輪完全相同的運算(迭代),這些運算被稱為函數(shù)f,在每一輪運算過程中數(shù)據(jù)與相應的密鑰結(jié)合;
- 在每一輪迭代中密鑰位移位,然后再從密鑰的56位中選出48位。通過一個擴展置換將數(shù)據(jù)的右半部分擴展成48位,并通過一個異或操作替代成新的48位數(shù)據(jù),再將其壓縮置換成32位。這四步運算構(gòu)成了函數(shù)f。然后,通過另一個異或運算,函數(shù)f的輸出與左半部分結(jié)合,其結(jié)果成為新的右半部分,原來的右半部分成為新的左半部分。將該操作重復16次;
- 經(jīng)過16輪迭代后,左,右半部分合在一起經(jīng)過一個末置換(數(shù)據(jù)整理),這樣就完成了加密過程。
對于DES解密的過程大家猛然一想應該是使用跟加密過程相反的算法,事實上解密和加密使用的是一樣的算法,有區(qū)別的地方在于加密和解密在使用密匙的時候次序是相反的。比如加密的時候是K0,K1,K2......K15,那么解密使用密匙的次序就是倒過來的。之所以能用相同的算法去解密,這跟DES特意設(shè)計的加密算法有關(guān),感興趣的同學可以深入分析。
2.3 AES
高級加密標準(AES,Advanced Encryption Standard),與DES一樣,使用AES加密函數(shù)和密匙來對明文進行加密,區(qū)別就是使用的加密函數(shù)不同。
上面說過DES的密鑰長度是56比特,因此算法的理論安全強度是2^56。但以目前計算機硬件的制作水準和升級情況,破解DES可能只是山脈問題,最終NIST(美國國家標準技術(shù)研究所(National Institute of Standards and Technology))選擇了分組長度為128位的Rijndael算法作為AES算法。
AES為分組密碼,分組密碼也就是把明文分成一組一組的,每組長度相等,每次加密一組數(shù)據(jù),直到加密完整個明文。在AES標準規(guī)范中,分組長度只能是128位,也就是說,每個分組為16個字節(jié)(每個字節(jié)8位)。密鑰的長度可以使用128位、192位或256位。密鑰的長度不同,推薦加密輪數(shù)也不同,如下表所示:
AES密鑰長度(32位比特字)分組長度(32位比特字)加密輪數(shù)
AES-1284410AES-1926412AES-2568414
3. 非對稱加密#
非對稱加密算法的特點是,秘鑰一次會生成一對,其中一份秘鑰由自己保存,不能公開出去,稱為“私鑰”,另外一份是可以公開出去的,稱為“公鑰”。
將原文用公鑰進行加密,得到的密文只有用對應私鑰才可以解密得到原文;
將原文用私鑰加密得到的密文,也只有用對應的公鑰才能解密得到原文;
因為加密和解密使用的是兩個不同的密鑰,所以這種算法叫作非對稱加密算法。
與對稱加密算法的對比
- 優(yōu)點:其安全性更好,對稱加密的通信雙方使用相同的秘鑰,如果一方的秘鑰遭泄露,那么整個通信就會被破解。而非對稱加密使用一對秘鑰,一個用來加密,一個用來解密,而且公鑰是公開的,秘鑰是自己保存的,不需要像對稱加密那樣在通信之前要先同步秘鑰。
- 缺點:非對稱加密的缺點是加密和解密花費時間長、速度慢,只適合對少量數(shù)據(jù)進行加密。
在非對稱加密中使用的主要算法有:RSA、Elgamal、ESA、背包算法、Rabin、D-H、ECC(橢圓曲線加密算法)等。不同算法的實現(xiàn)機制不同。
非對稱加密工作原理
下面我們就看一下非對稱加密的工作原理。
- 乙方生成一對密鑰(公鑰和私鑰)并將公鑰向其它方公開。
- 得到該公鑰的甲方使用該密鑰對機密信息進行加密后再發(fā)送給乙方。
- 乙方再用自己保存的另一把專用密鑰(私鑰)對加密后的信息進行解密。乙方只能用其專用密鑰(私鑰)解密由對應的公鑰加密后的信息。
- 在傳輸過程中,即使攻擊者截獲了傳輸?shù)拿芪模⒌玫搅艘业墓€,也無法破解密文,因為只有乙的私鑰才能解密密文。同樣,如果乙要回復加密信息給甲,那么需要甲先公布甲的公鑰給乙用于加密,甲自己保存甲的私鑰用于解密。
非對稱加密鼻祖:RSA
RSA算法基于一個十分簡單的數(shù)論事實:將兩個大質(zhì)數(shù)(素數(shù))相乘十分容易,但是想要對其乘積進行因式分解卻極其困難,因此可以將乘積公開作為加密密鑰。比如:取兩個簡單的質(zhì)數(shù):67,73,得到兩者乘積很簡單4891;但是要想對4891進行因式分解,其工作量成幾何增加。
應用場景:
HTTPS請求的SSL層。
在JDK中也提供了RSA的實現(xiàn),下面給出示例:
Copy /**
* 創(chuàng)建密匙對
*
* @return
*/
private KeyPair genKeyPair() {
//創(chuàng)建 RSA Key 的生產(chǎn)者。
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
//利用用戶密碼作為隨機數(shù)初始化出 1024 比特 Key 的生產(chǎn)者。
//SecureRandom 是生成安全隨機數(shù)序列,password.getBytes() 是種子,只要種子相同,序列就一樣。
keyPairGen.initialize(1024, new SecureRandom("password".getBytes()));
//創(chuàng)建密鑰對
return keyPairGen.generateKeyPair();
}
/**
* 生成公匙
*
* @return
*/
public PublicKey genPublicKey() {
try {
//創(chuàng)建密鑰對
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 {
//創(chuàng)建密鑰對
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);
// 對數(shù)據(jù)加密
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;
// 對數(shù)據(jù)分段加密
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;
// 對數(shù)據(jù)分段解密
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;
// 對數(shù)據(jù)分段加密
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;
// 對數(shù)據(jù)分段解密
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種加密技術(shù),每一種都有自己的特點,比如散列技術(shù)用于特征值提取,對稱加密速度雖快但是有私匙泄露的危險,非對稱加密雖然安全但是速度卻慢。基于這些情況,現(xiàn)在的加密技術(shù)更加趨向于將這些加密的方案組合起來使用,基于此來研發(fā)新的加密算法。
mac(Message Authentication Code,消息認證碼算法)是含有密鑰散列函數(shù)算法,兼容了MD和SHA算法的特性,并在此基礎(chǔ)上加上了密鑰。因此MAC算法也經(jīng)常被稱作HMAC算法。MAC(Message Authentication Code,消息認證碼算法)是含有密鑰散列函數(shù)算法,HMAC加密可以理解為加鹽的散列算法,此處的“鹽”就相當于HMAC算法的秘鑰。
HMAC算法的實現(xiàn)過程需要一個加密用的散列函數(shù)(表示為H)和一個密鑰。
經(jīng)過MAC算法得到的摘要值也可以使用十六進制編碼表示,其摘要值得長度與實現(xiàn)算法的摘要值長度相同。例如 HmacSHA算法得到的摘要長度就是SHA1算法得到的摘要長度,都是160位二進制數(shù),換算成十六進制的編碼為40位。
MAC算法的實現(xiàn):
算法摘要長度備注HmacMD5128JAVA6實現(xiàn)HmacSHA1160JAVA6實現(xiàn)HmacSHA256256JAVA6實現(xiàn)HmacSHA384384JAVA6實現(xiàn)HmacSHA512512JAVA6實現(xiàn)HmacMD2128BouncyCastle實現(xiàn)HmacMD4128BouncyCastle實現(xiàn)HmacSHA224224BouncyCastle實現(xiàn)
過程如下:
- 在密鑰key后面添加0來創(chuàng)建一個長為B(64字節(jié))的字符串(str);
- 將上一步生成的字符串(str) 與ipad(0x36)做異或運算,形成結(jié)果字符串(istr);
- 將數(shù)據(jù)流data附加到第二步的結(jié)果字符串(istr)的末尾;
- 做md5運算于第三步生成的數(shù)據(jù)流(istr);
- 將第一步生成的字符串(str) 與opad(0x5c)做異或運算,形成結(jié)果字符串(ostr),再將第四步的結(jié)果(istr) 附加到第五步的結(jié)果字符串(ostr)的末尾做md5運算于第6步生成的數(shù)據(jù)流(ostr),最終輸出結(jié)果(out)
注意:如果第一步中,key的長度klen大于64字節(jié),則先進行md5運算,使其長度klen = 16字節(jié)。
JDK中的實現(xiàn):
Copypublic static void jdkHmacMD5() {
try {
// 初始化KeyGenerator
KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacMD5");
// 產(chǎn)生密鑰
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);
// 執(zhí)行摘要
byte[] hmacMD5Bytes = mac.doFinal("data".getBytes());
System.out.println("jdk hmacMD5:" + new String(hmacMD5Bytes));
} catch (Exception e) {
e.printStackTrace();
}
}