詳細(xì)分析JAVA加解密算法
加解密算法分析
日常開發(fā)中,無論你是使用什么語言,都應(yīng)該遇到過使用加解密的使用場景,比如接口數(shù)據(jù)需要加密傳給前端保證數(shù)據(jù)傳輸?shù)陌踩?;HTTPS使用證書的方式首先進(jìn)行非對稱加密,將客戶端的私匙傳遞給服務(wù)端,然后雙方后面的通信都使用該私匙進(jìn)行對稱加密傳輸;使用MD5進(jìn)行文件一致性校驗,等等很多的場景都使用到了加解密技術(shù)。
很多時候我們對于什么時候要使用什么樣的加解密方式是很懵的。因為可用的加解密方案實在是太多,大家對加解密技術(shù)的類型可能不是很清楚,今天這篇文章就來梳理一下目前主流的加解密技術(shù),本篇文檔只針對算法做科普性說明,不涉及具體算法分析。日常使用的加解密大致可以分為以下四類:
- 散列函數(shù)(也稱信息摘要)算法
- 對稱加密算法
- 非對稱加密算法
- 組合加密技術(shù)
1. 散列函數(shù)算法
聽名字似乎不是一種加密算法,類似于給一個對象計算出hash值。所以這種算法一般用于數(shù)據(jù)特征提取。常用的散列函數(shù)包括:MD5、SHA1、SHA2(包括SHA128、SHA256等)散列函數(shù)的應(yīng)用很廣,散列函數(shù)有個特點,它是一種單向加密算法,只能加密、無法解密。
1.1 MD5
先來看MD5算法,MD5算法是廣為使用的數(shù)據(jù)特征提取算法,最常見的就是我們在下載一些軟件,網(wǎng)站都會提供MD5值給你進(jìn)行校驗,你可以通過MD5值是否一致來檢查當(dāng)前文件是否被別人篡改。MD5算法具有以下特點:
- 任意長度的數(shù)據(jù)得到的MD5值長度都是相等的;
- 對原數(shù)據(jù)進(jìn)行任一點修改,得到的MD5值就會有很大的變化;
- 散列函數(shù)的不可逆性,即已知原數(shù)據(jù),無法通過特征值反向獲取原數(shù)據(jù)。(需要說明的是2004年的國際密碼討論年會(CRYPTO)尾聲,王小云及其研究同事展示了MD5、SHA-0及其他相關(guān)雜湊函數(shù)的雜湊沖撞。也就是說,她找出了第一個 兩個值不同,但 MD5 值相同的碰撞的例子。這個應(yīng)該不能稱之為破解)
1.2 MD5用途:
- 防篡改。上面說過用于文件完整性校驗。
- 用于不想讓別人看到明文的地方。比如用戶密碼入庫,可以將用戶密碼使用MD5加密存儲,下次用戶輸入密碼登錄只用將他的輸入進(jìn)行MD5加密與數(shù)據(jù)庫的值判斷是否一致即可,這樣就有效防止密碼泄露的風(fēng)險。
- 用于文件秒傳。比如百度云的文件秒傳功能可以用這種方式來實現(xiàn)。在你點擊上傳的時候,前端同學(xué)會先計算文件的MD5值然后與服務(wù)端比對是否存在,如果有就會告訴你文件上傳成功,即完成所謂的秒傳。
在JDK中提供了MD5的實現(xiàn):java.security包中有個類MessageDigest,MessageDigest 類為應(yīng)用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法。信息摘要是安全的單向哈希函數(shù),它接收任意大小的數(shù)據(jù),輸出固定長度的哈希值。
MessageDigest 對象使用getInstance函數(shù)初始化,該對象通過使用 update 方法處理數(shù)據(jù)。任何時候都可以調(diào)用 reset 方法重置摘要。一旦所有需要更新的數(shù)據(jù)都已經(jīng)被更新了,應(yīng)該調(diào)用 digest 方法之一完成哈希計算。
對于給定數(shù)量的更新數(shù)據(jù),digest 方法只能被調(diào)用一次。digest 被調(diào)用后,MessageDigest 對象被重新設(shè)置成其初始狀態(tài)。
下面的例子展示了使用JDK自帶的MessageDigest類使用MD5算法。同時也展示了如果使用了update方法后沒有調(diào)用digest方法,則會累計當(dāng)前所有的update中的值在下一次調(diào)用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())); //你會發(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)換成十六進(jì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(); } }
輸出:
md5(a)=0CC175B9C0F1B6A831C399E269772661
md5(abc)=900150983CD24FB0D6963F7D28E17F72
md5(abc)=900150983CD24FB0D6963F7D28E17F72
1.3 SHA系列算法
Secure Hash Algorithm,是一種與MD5同源的數(shù)據(jù)加密算法。SHA算法能計算出一個數(shù)位信息所對應(yīng)到的,長度固定的字串,又稱信息摘要。而且如果輸入信息有任何的不同,輸出的對應(yīng)摘要不同的機率非常高。因此SHA算法也是FIPS所認(rèn)證的五種安全雜湊算法之一。原因有兩點:一是由信息摘要反推原輸入信息,從計算理論上來說是極為困難的;二是,想要找到兩組不同的輸入信息發(fā)生信息摘要碰撞的幾率,從計算理論上來說是非常小的。任何對輸入信息的變動,都有很高的幾率導(dǎo)致的信息摘要大相徑庭。
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-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 |
計算步驟數(shù) | 80 | 64 | 64 | 80 | 80 |
SHA-1算法輸入報文的最大長度不超過264位,產(chǎn)生的輸出是一個160位的報文摘要。輸入是按512 位的分組進(jìn)行處理的。SHA-1是不可逆的、防沖突,并具有良好的雪崩效應(yīng)。
上面提到的MessageDigest類同時也支持SHA系列算法,使用方式與MD5一樣,注意SHA不同的類型:
MessageDigest md = MessageDigest.getInstance("SHA");
MessageDigest md = MessageDigest.getInstance("SHA-224");
MessageDigest md = MessageDigest.getInstance("SHA-384");
2. 對稱加密算法
所謂的對稱加密,意味著加密者和解密者需要同時持有一份相同的密匙,加密者用密匙加密,解密者用密匙解密即可。
常用的對稱加密算法包括DES算法、AES算法等。 由于對稱加密需要一個秘鑰,而秘鑰在加密者與解密者之間傳輸又很難保證安全性,所以目前用對稱加密算法的話主要是用在加密者解密者相同,或者加密者解密者相對固定的場景。
對稱算法又可分為兩類:
第一種是一次只對明文中的單個位(有時對字節(jié))運算的算法稱為序列算法或序列密碼;
另一種算法是對明文的一組位進(jìn)行運算,這些位組稱為分組,相應(yīng)的算法稱為分組算法或分組密碼。現(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ù)淖址⒉蝗强纱蛴〉淖址?,比如二進(jìn)制文件、圖片等。Base64的出現(xiàn)就是為了解決此問題,它是基于64個可打印的字符來表示二進(jìn)制的數(shù)據(jù)的一種方法。
BASE64原理
BASE64的原理比較簡單,每當(dāng)我們使用BASE64時都會先定義一個類似這樣的數(shù)組:
['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
上面就是BASE64的索引表,字符選用了"A-Z、a-z、0-9、+、/" 64個可打印字符,這是標(biāo)準(zhǔn)的BASE64協(xié)議規(guī)定。在日常使用中我們還會看到“=”或“==”號出現(xiàn)在BASE64的編碼結(jié)果中,“=”在此是作為填充字符出現(xiàn)。
JDK提供了BASE64的實現(xiàn):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),在很長時間內(nèi),許多人心目中“密碼生成”與DES一直是個同義詞。
DES是一個分組加密算法,典型的DES以64位為分組對數(shù)據(jù)加密,加密和解密用的是同一個算法。它的密鑰長度是56位(因為每個第8 位都用作奇偶校驗),密鑰可以是任意的56位的數(shù),而且可以任意時候改變。
DES加密過程大致如下:
- 首先需要從用戶處獲取一個64位長的密碼口令,然后通過等分、移位、選取和迭代形成一套16個加密密鑰,分別供每一輪運算中使用;
- 然后將64位的明文分組M進(jìn)行操作,M經(jīng)過一個初始置換IP,置換成m0。將m0明文分成左半部分和右半部分m0 = (L0,R0),各32位長。然后進(jìn)行16輪完全相同的運算(迭代),這些運算被稱為函數(shù)f,在每一輪運算過程中數(shù)據(jù)與相應(yīng)的密鑰結(jié)合;
- 在每一輪迭代中密鑰位移位,然后再從密鑰的56位中選出48位。通過一個擴展置換將數(shù)據(jù)的右半部分?jǐn)U展成48位,并通過一個異或操作替代成新的48位數(shù)據(jù),再將其壓縮置換成32位。這四步運算構(gòu)成了函數(shù)f。然后,通過另一個異或運算,函數(shù)f的輸出與左半部分結(jié)合,其結(jié)果成為新的右半部分,原來的右半部分成為新的左半部分。將該操作重復(fù)16次;
- 經(jīng)過16輪迭代后,左,右半部分合在一起經(jīng)過一個末置換(數(shù)據(jù)整理),這樣就完成了加密過程。
對于DES解密的過程大家猛然一想應(yīng)該是使用跟加密過程相反的算法,事實上解密和加密使用的是一樣的算法,有區(qū)別的地方在于加密和解密在使用密匙的時候次序是相反的。比如加密的時候是K0,K1,K2......K15,那么解密使用密匙的次序就是倒過來的。之所以能用相同的算法去解密,這跟DES特意設(shè)計的加密算法有關(guān),感興趣的同學(xué)可以深入分析。
2.3 AES
高級加密標(biāo)準(zhǔn)(AES,Advanced Encryption Standard),與DES一樣,使用AES加密函數(shù)和密匙來對明文進(jìn)行加密,區(qū)別就是使用的加密函數(shù)不同。
上面說過DES的密鑰長度是56比特,因此算法的理論安全強度是2^56。但以目前計算機硬件的制作水準(zhǔn)和升級情況,破解DES可能只是山脈問題,最終NIST(美國國家標(biāo)準(zhǔn)技術(shù)研究所(National Institute of Standards and Technology))選擇了分組長度為128位的Rijndael算法作為AES算法。
AES為分組密碼,分組密碼也就是把明文分成一組一組的,每組長度相等,每次加密一組數(shù)據(jù),直到加密完整個明文。在AES標(biāo)準(zhǔn)規(guī)范中,分組長度只能是128位,也就是說,每個分組為16個字節(jié)(每個字節(jié)8位)。密鑰的長度可以使用128位、192位或256位。密鑰的長度不同,推薦加密輪數(shù)也不同,如下表所示:
AES | 密鑰長度(32位比特字) | 分組長度(32位比特字) | 加密輪數(shù) |
AES-128 | 4 | 4 | 10 |
AES-192 | 6 | 4 | 12 |
AES-256 | 8 | 4 | 14 |
3. 非對稱加密
非對稱加密算法的特點是,秘鑰一次會生成一對,其中一份秘鑰由自己保存,不能公開出去,稱為“私鑰”,另外一份是可以公開出去的,稱為“公鑰”。
將原文用公鑰進(jìn)行加密,得到的密文只有用對應(yīng)私鑰才可以解密得到原文;
將原文用私鑰加密得到的密文,也只有用對應(yīng)的公鑰才能解密得到原文;
因為加密和解密使用的是兩個不同的密鑰,所以這種算法叫作非對稱加密算法。
與對稱加密算法的對比
- 優(yōu)點:其安全性更好,對稱加密的通信雙方使用相同的秘鑰,如果一方的秘鑰遭泄露,那么整個通信就會被破解。而非對稱加密使用一對秘鑰,一個用來加密,一個用來解密,而且公鑰是公開的,秘鑰是自己保存的,不需要像對稱加密那樣在通信之前要先同步秘鑰。
- 缺點:非對稱加密的缺點是加密和解密花費時間長、速度慢,只適合對少量數(shù)據(jù)進(jìn)行加密。
在非對稱加密中使用的主要算法有:RSA、Elgamal、ESA、背包算法、Rabin、D-H、ECC(橢圓曲線加密算法)等。不同算法的實現(xiàn)機制不同。
非對稱加密工作原理
下面我們就看一下非對稱加密的工作原理。
- 乙方生成一對密鑰(公鑰和私鑰)并將公鑰向其它方公開。
- 得到該公鑰的甲方使用該密鑰對機密信息進(jìn)行加密后再發(fā)送給乙方。
- 乙方再用自己保存的另一把專用密鑰(私鑰)對加密后的信息進(jìn)行解密。乙方只能用其專用密鑰(私鑰)解密由對應(yīng)的公鑰加密后的信息。
- 在傳輸過程中,即使攻擊者截獲了傳輸?shù)拿芪?,并得到了乙的公鑰,也無法破解密文,因為只有乙的私鑰才能解密密文。同樣,如果乙要回復(fù)加密信息給甲,那么需要甲先公布甲的公鑰給乙用于加密,甲自己保存甲的私鑰用于解密。
非對稱加密鼻祖:RSA
RSA算法基于一個十分簡單的數(shù)論事實:將兩個大質(zhì)數(shù)(素數(shù))相乘十分容易,但是想要對其乘積進(jìn)行因式分解卻極其困難,因此可以將乘積公開作為加密密鑰。比如:取兩個簡單的質(zhì)數(shù):67,73,得到兩者乘積很簡單4891;但是要想對4891進(jìn)行因式分解,其工作量成幾何增加。
應(yīng)用場景:
HTTPS請求的SSL層。
在JDK中也提供了RSA的實現(xiàn),下面給出示例:
/** * 創(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,消息認(rèn)證碼算法)是含有密鑰散列函數(shù)算法,兼容了MD和SHA算法的特性,并在此基礎(chǔ)上加上了密鑰。因此MAC算法也經(jīng)常被稱作HMAC算法。MAC(Message Authentication Code,消息認(rèn)證碼算法)是含有密鑰散列函數(shù)算法,HMAC加密可以理解為加鹽的散列算法,此處的“鹽”就相當(dāng)于HMAC算法的秘鑰。
HMAC算法的實現(xiàn)過程需要一個加密用的散列函數(shù)(表示為H)和一個密鑰。
經(jīng)過MAC算法得到的摘要值也可以使用十六進(jìn)制編碼表示,其摘要值得長度與實現(xiàn)算法的摘要值長度相同。例如 HmacSHA算法得到的摘要長度就是SHA1算法得到的摘要長度,都是160位二進(jìn)制數(shù),換算成十六進(jìn)制的編碼為40位。
MAC算法的實現(xiàn):
算法 | 摘要長度 | 備注 |
HmacMD5 | 128 | JAVA6實現(xiàn) |
HmacSHA1 | 160 | JAVA6實現(xiàn) |
HmacSHA256 | 256 | JAVA6實現(xiàn) |
HmacSHA384 | 384 | JAVA6實現(xiàn) |
HmacSHA512 | 512 | JAVA6實現(xiàn) |
HmacMD2 | 128 | BouncyCastle實現(xiàn) |
HmacMD4 | 128 | BouncyCastle實現(xiàn) |
HmacSHA224 | 224 | BouncyCastle實現(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é),則先進(jìn)行md5運算,使其長度klen = 16字節(jié)。
JDK中的實現(xiàn):
public 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(); } }
以上就是詳細(xì)分析JAVA加解密算法的詳細(xì)內(nèi)容,更多關(guān)于JAVA加解密算法的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring Boot實現(xiàn)通用的接口參數(shù)校驗
本文介紹基于 Spring Boot 和 JDK8 編寫一個 AOP ,結(jié)合自定義注解實現(xiàn)通用的接口參數(shù)校驗。具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-05-05JAVA基于Redis實現(xiàn)計數(shù)器限流的使用示例
計數(shù)器法是限流算法里最簡單也是最容易實現(xiàn)的一種算法,本文主要介紹了JAVA基于Redis實現(xiàn)計數(shù)器限流的使用示例,具有一定的參考價值,感興趣的可以了解一下2023-09-09idea亂碼修改bin目錄下的idea.exe.vmoptions無效問題
這篇文章主要介紹了idea亂碼修改bin目錄下的idea.exe.vmoptions無效問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04