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