一增蹭、單向散列函數(shù)
1.1 概念及術(shù)語
單向散列函數(shù)(one-way hash function)有一個輸入和一個輸出更胖,其中輸入稱為消息(message)秒梳,輸出稱為散列值(hash value)梆砸。單向散列函數(shù)可以根據(jù)消息的內(nèi)容計(jì)算出散列值,而散列值就可以被用來檢查消息的完整性佩抹。這里的消息可以是文檔圖像視頻音頻等,散列函數(shù)會將它們都當(dāng)成單純的bit序列來處理取董。這樣棍苹,要確認(rèn)完整性就不需要對比消息本身,對比小的散列值即可茵汰。
注意:單向散列函數(shù)并不是一種加密枢里,因此無法通過解密將散列值還原為原來的消息。
術(shù)語:單向散列函數(shù)也稱為消息摘要函數(shù)(message digest function)蹂午、哈希函數(shù)或者雜湊函數(shù)栏豺。輸入的消息也稱為原像(pre-image),輸出的散列值也成為消息摘要(message digest)或者指紋(fingerprint)豆胸,完整性也稱為一致性奥洼。
1.2 使用場景及常見函數(shù)
單向散列函數(shù)的使用場景
- 檢測軟件是否被篡改:有些軟件會將計(jì)算出的散列值公布在自己的官方網(wǎng)站上,用戶下載軟件后晚胡,可以自行計(jì)算散列值灵奖,然后與官網(wǎng)上的散列值對比來判斷是否被篡改。當(dāng)軟件作者通過多個站點(diǎn)(鏡像站點(diǎn))來發(fā)布軟件時估盘,單向散列函數(shù)就會在檢測是否被篡改方面發(fā)揮重要作用瓷患。
- 被用于基于口令的加密(Password Based Encryption,PBE):PBE原理是將口令和鹽(salt遣妥,通過偽隨機(jī)數(shù)生成器產(chǎn)生的隨機(jī)值)混合后計(jì)算其散列值擅编,然后將這個散列值用作加密的密鑰。通過這樣的方法能夠防御針對口令的字典攻擊箫踩。
- 構(gòu)造消息認(rèn)證碼:消息認(rèn)證碼是將“發(fā)送者和接收者之間的共享密鑰”和“消息”進(jìn)行混合后計(jì)算出的散列值爱态。使用消息認(rèn)證碼可以檢測并防止通信過程中的錯誤、篡改以及偽裝班套。消息認(rèn)證碼在SSL/TLS中也得到了運(yùn)用肢藐。
- 數(shù)字簽名:數(shù)字簽名的處理過程非常耗時,因此一般不會對整個消息內(nèi)容直接施加數(shù)字簽名吱韭,而是先通過單向散列函數(shù)計(jì)算出消息的散列值吆豹,然后再對這個散列值加數(shù)字簽名鱼的。
- 偽隨機(jī)數(shù)生成器:密碼技術(shù)中所使用的隨機(jī)數(shù)需要具備“事實(shí)上不可能根據(jù)過去的隨機(jī)數(shù)列預(yù)測未來的隨機(jī)數(shù)列”這樣的性質(zhì)。為了保證不可預(yù)測性痘煤,可以利用單向散列函數(shù)的單向性凑阶。
- 一次性口令(one-time password):一次性口令經(jīng)常被用于服務(wù)器對客戶端的合法性認(rèn)證。在這種方式中衷快,通過單向散列函數(shù)可以保證口令只在通信鏈路上傳送一次宙橱,因此即使竊聽者竊取了口令也無法使用。
- 存儲密碼:加鹽操作配合單向散列函數(shù)可以存儲用戶密碼蘸拔,滿足普通項(xiàng)目需求师郑。
常見的單向散列函數(shù)
MD5:MD是消息摘要 MessageDigest 的縮寫,MD5是由Rivest于1991年設(shè)計(jì)的單向散列函數(shù)调窍,能夠產(chǎn)生128bit的散列值宝冕。MD5的強(qiáng)抗碰撞性已經(jīng)被攻破,也就是說邓萨,現(xiàn)在已經(jīng)能夠產(chǎn)生具備相同散列值的兩條不同的消息地梨,因此它已經(jīng)不安全了。
SHA-1:是由NIST(National Institute of Standards and Technology缔恳,美國國家標(biāo)準(zhǔn)技術(shù)研究所)設(shè)計(jì)的一種能夠產(chǎn)生160bit散列值的單向散列函數(shù)宝剖。SHA-1處理的消息長度存在上限蚌卤,這個值接近于264bit劫哼。該函數(shù)的強(qiáng)抗碰撞性已于2005年被攻破。
SHA-256脑奠、SHA-384纸泄、SHA-512:NIST設(shè)計(jì)的單向散列函數(shù)雅镊,它們的散列值長度分別為256bit、384bit刃滓、512bit仁烹。這些函數(shù)合起來統(tǒng)稱 **SHA-2 **,它們處理的消息長度上限為:SHA-256上限接近于264bit咧虎,SHA-384和SHA-512上限接近2128bit卓缰。目前 SHA-2 的強(qiáng)抗碰撞性還未被攻破。
單向散列函數(shù)無法辨別“偽裝”
假設(shè)主動攻擊者M(jìn)偽裝成了A砰诵,向B同時發(fā)送了消息和散列值征唬,這時B能夠用函數(shù)檢查消息完整性,但是這只是對M發(fā)出的消息進(jìn)行檢查茁彭,沒辦法試別出是其他人偽裝成了A总寒。
也就是說單向散列函數(shù)無法直接用于認(rèn)證。需要結(jié)合消息認(rèn)證碼和數(shù)字簽名技術(shù)理肺,保證消息完整性和進(jìn)行身份認(rèn)證摄闸。
二善镰、對稱密碼
根據(jù)密鑰的使用方法,可以將密碼分為對稱密碼和公鑰密碼兩種年枕。
對稱密碼是指在加密和解密時使用同一密鑰的方式炫欺。
非對稱密碼又叫公鑰密碼,指在加密和解密時使用不同密鑰的方式熏兄。
用相同的密鑰進(jìn)行加密和解密就是對稱加密品洛。
常見對稱加密算法:
DES(Data Encryption Standard)
DES是1977年美國聯(lián)邦信息處理標(biāo)準(zhǔn)(FIPS)中所采用的一種對稱密碼,一直以來被美國以及其他國家的政府和銀行等廣泛使用摩桶。然而隨著計(jì)算機(jī)的進(jìn)步桥状,現(xiàn)在DES已經(jīng)能夠被暴力破解,強(qiáng)度大不如前了硝清。
DES是一種將64bit的明文加密成64bit的密文的對稱密碼算法岛宦,它的密鑰長度是56bit。盡管從規(guī)格上來說耍缴,DES的密鑰長度是64bit,但由于每隔7bit會設(shè)置一個用于錯誤檢查的bit挽霉,因此實(shí)質(zhì)上長度是56bit防嗡。
DES是以64bit的明文(bit序列)為單位來進(jìn)行加密的,這個64bit的單位稱為分組侠坎。一般來說蚁趁,以分組為單位進(jìn)行處理的密碼算法稱為分組密碼(block cipher),DES就是分組密碼的一種实胸。
DES每次只能加密64bit的數(shù)據(jù)他嫡,如果要加密的明文比較長,就需要對DES加密進(jìn)行迭代(反復(fù))庐完,而迭代的具體方式就稱為模式(mode)钢属。
三重DES
三重DES(triple-DES)是為了增加DES的強(qiáng)度,將DES重復(fù)3次得到的一種密碼算法门躯,通诚常縮寫為3DES。
明文經(jīng)過三次DES處理才能變成最后的密文讶凉,由于DES密鑰的長度實(shí)質(zhì)上是56bit染乌,因此三重DES的密鑰長度就是56*3=168bit。加密中加入解密懂讯,這個方法是IBM公司設(shè)計(jì)的荷憋,目的是為了3DES能夠兼容普通的DES,當(dāng)三個密鑰都相同時褐望,就可以向下兼容DES了勒庄。
盡管3DES目前還被銀行等機(jī)構(gòu)使用串前,但其處理速度不高,而且在安全性方面也逐漸顯現(xiàn)出了一些問題锅铅。
對稱密碼的新標(biāo)準(zhǔn)——AES
AES(Advanced Encryption Standard)是取代舊標(biāo)準(zhǔn)DES而成為新標(biāo)準(zhǔn)的一種對稱密碼算法酪呻。全世界的企業(yè)和密碼學(xué)家提交了多個對稱密碼算法作為AES的候選,最終在2000年從這些候選算法中選出了一種名為Rijndael的對稱密碼算法盐须,并將其確定為了AES玩荠。
應(yīng)該使用哪種對稱密碼?
現(xiàn)在DES由于安全性問題最好是不要使用了贼邓。出于兼容性因素3DES還會使用一段時間阶冈,但是會逐漸被AES所取代。
一般來說現(xiàn)在不應(yīng)該使用使用任何自制的算法塑径,而是應(yīng)該使用AES女坑,因?yàn)锳ES在其選定過程中,經(jīng)過了全世界密碼學(xué)家所進(jìn)行的高品質(zhì)的檢驗(yàn)工作统舀,而對于自制密碼算法則很難進(jìn)行這樣的驗(yàn)證匆骗。
三、非對稱密碼
在對稱密碼中誉简,加密密鑰和解密密鑰是相同的碉就,但非對稱密碼,也叫公鑰密碼中闷串,加密密鑰和解密密鑰卻是不同的瓮钥。
公鑰密碼(public-key cryptography)中,密鑰分為加密密鑰和解密密鑰兩種烹吵。發(fā)送者用加密密鑰(公鑰)對消息進(jìn)行加密
碉熄,接收者用解密密鑰(私鑰)對密文進(jìn)行解密。常見公鑰密碼有:RSA肋拔、EIGamal方式锈津、Rabin方式、橢圓曲線密碼凉蜂。
不難發(fā)現(xiàn)一姿,公鑰和私鑰有如下特點(diǎn):
- 發(fā)送者只需要公鑰
- 接收者只需要私鑰
- 私鑰由接收者持有,不能被竊聽者獲取
- 公鑰被竊聽者獲取也沒問題
得出結(jié)論:公鑰密碼解決了使用對稱密碼時的密鑰配送問題跃惫。
公鑰加解密流程圖示如下:
3.1 公鑰密碼無法解決的問題
公鑰密碼解決了密鑰配送問題叮叹,但這并不意味著它能夠解決所有問題,因?yàn)槲覀冃枰袛嗨玫降墓€是否正確合法爆存,這個問題被稱為公鑰認(rèn)證問題蛉顽;
還有個問題是,它的處理速度很慢先较,只有對稱密碼的幾百分之一携冤。
四悼粮、混合密碼——用對稱密碼提高速度,用公鑰密碼保護(hù)會話密鑰
通過使用對稱密碼曾棕,能夠在通信中確保機(jī)密性扣猫。然而在實(shí)際中運(yùn)用時,就必須解決密鑰配送問題翘地。
而使用公鑰密碼就可以避免解密密鑰(私鑰)配送申尤,從而也就解決了對稱密碼所存在的密鑰配送問題。但是衙耕,公鑰密碼還有兩個很大的問題:
- 公鑰密碼處理速度遠(yuǎn)遠(yuǎn)低于對稱密碼
- 公鑰密碼難以抵御中間人攻擊
混合密碼系統(tǒng)可以解決問題1昧穿,而要解決問題2,則需要對公鑰進(jìn)行認(rèn)證橙喘。
混合密碼系統(tǒng)(hybrid cryptosystem)是將對稱密碼和公鑰密碼的優(yōu)勢相結(jié)合的方法时鸵。具體加解密流程如下:
4.1 加密
會話密鑰(session key)是指為本次通信而生成的的臨時密鑰,它一般是通過偽隨機(jī)數(shù)生成器產(chǎn)生的厅瞎。會話密鑰也會作為對稱密碼的密鑰使用饰潜。
簡言之,會話密鑰是對稱密碼的密鑰和簸,同時也是公鑰密碼的明文彭雾。
4.2 解密
分離:雙方要事先約定好密文結(jié)構(gòu),這樣對密文的分離操作就很容易完成比搭。
著名的密碼軟件PGP、以及網(wǎng)絡(luò)上的密碼通信所使用的SSL/TLS都運(yùn)用了混合密碼系統(tǒng)南誊。
五身诺、數(shù)字簽名——消息到底是誰寫的
數(shù)字簽名是根據(jù)消息內(nèi)容生成的一串“只有自己才能計(jì)算出來的數(shù)值”,因此數(shù)字簽名的內(nèi)容是隨消息的改變而改變的抄囚。
數(shù)字簽名跟公鑰密碼有著非常緊密的聯(lián)系霉赡。簡言之,數(shù)字簽名就是通過將公鑰密碼“反過來用”而實(shí)現(xiàn)的幔托。如下圖所示:
下面再來對比一下公鑰密碼和私鑰密碼:
公鑰密碼包括一個由公鑰和私鑰組成的密鑰對穴亏,其中公鑰加密,私鑰解密重挑。如圖:
數(shù)字簽名中也同樣會使用公鑰密碼和私鑰密碼組成的密鑰對嗓化,不過這兩個密鑰的用法和公鑰密碼是相反的,即用私鑰加密相當(dāng)于生成簽名谬哀,
而用公鑰解密則相當(dāng)于驗(yàn)證簽名(嚴(yán)格來說刺覆,RSA算法中公鑰加密和數(shù)字簽名正好是完全相反的關(guān)系,但在其他算法中有可能不是這樣完全相反的關(guān)系)史煎。
<meta name="source" content="lake">
公鑰密碼中谦屑,任何人都能夠進(jìn)行加密驳糯;
數(shù)字簽名中,任何人都能夠驗(yàn)證簽名氢橙。
5.1 數(shù)字簽名的方法
一般有兩種生成和驗(yàn)證數(shù)字簽名的方法:
- 直接對消息簽名的方法
- 對消息的散列值簽名的方法
直接對消息簽名的方法比較容易理解酝枢,但實(shí)際上并不會使用;對消息的散列值簽名的方法稍微復(fù)雜一點(diǎn)悍手,但實(shí)際中一般都使用這種方法帘睦。
直接對消息簽名很簡單,就是用私鑰對消息簽名就行谓苟,接收者接收到消息和簽名后官脓,用對應(yīng)公鑰解簽,再對比消息涝焙,一致則驗(yàn)簽成功卑笨,否則失敗。
5.1.1 對消息的散列值簽名
上面說過仑撞,公鑰密碼算法是很慢的赤兴,那么對長消息加密也很慢。那么隧哮,能不能生成一條很短的數(shù)據(jù)來代替消息本身呢桶良?
回想一下,上面說過的單向散列函數(shù)就非常契合沮翔。長消息的散列值永遠(yuǎn)都很短陨帆,對其加簽是很輕松的。
對散列值簽名的流程圖如下:
5.2 數(shù)字簽名無法解決的問題
數(shù)字簽名既可以識別出篡改和偽裝采蚀,還可以防止否認(rèn)疲牵。也就是說,同時實(shí)現(xiàn)了確認(rèn)消息的完整性榆鼠、進(jìn)行認(rèn)證以及否認(rèn)防止纲爸。
然而要正確使用數(shù)字簽名,有一個大前提妆够,那就是用于驗(yàn)證簽名的公鑰必須屬于真正的發(fā)送者识啦。即便數(shù)字簽名算法再強(qiáng)大,如果你得到的公鑰是偽造的神妹,那么數(shù)字簽名也會完全失效颓哮。
為了能夠確認(rèn)自己得到的公鑰是否合法,我們需要使用證書鸵荠。所謂證書题翻,就是將公鑰當(dāng)作一條消息,由一個可信的第三方對其簽名后所得到的公鑰。
六嵌赠、證書——為公鑰加上數(shù)字簽名
公鑰證書(Public-Key Certificate,PKC)其實(shí)和駕照很相似塑荒,里面記有姓名、組織姜挺、郵箱地址等個人信息齿税,以及屬于此人的公鑰,并由認(rèn)證機(jī)構(gòu)(Certification Authority炊豪、Certifying Authority,CA)施加數(shù)字簽名凌箕。只要看到公鑰證書,我們就可以知道認(rèn)證機(jī)構(gòu)認(rèn)定該公鑰的確屬于此人词渤。公鑰證書也簡稱為證書(certificate)牵舱。
認(rèn)證機(jī)構(gòu)就是能夠認(rèn)定“公鑰確實(shí)屬于此人”并能夠生成數(shù)字簽名的個人或者組織。世界上最有名的認(rèn)證機(jī)構(gòu)當(dāng)屬VeriSign(verisign.com)公司缺虐。
6.1 證書應(yīng)用場景
認(rèn)證機(jī)構(gòu)必須是可信的芜壁,對于“可信的第三方”,這里用 Trent 這個名字代表高氮,那么場景示例如下圖所示
七慧妄、RSA
7.1 RSA加密
RSA是現(xiàn)在使用最廣泛的公鑰密碼算法 。它的名字是由它的三位開發(fā)者剪芍,即Ron Rivest塞淹、Adi Shamir 和 Leonard Adleman 的姓氏的首字母組成。
RSA可以被用于公鑰密碼和數(shù)字簽名罪裹。1983年饱普,RSA公司為RSA算法在美國取得了專利,但現(xiàn)在專利已過期状共。
RSA加密過程:
在RSA中套耕,明文、密鑰和密文都是數(shù)字口芍。
RSA的加密解密公式如下:
7.2 RSA數(shù)字簽名
RSA的簽名生成和驗(yàn)證公式如下:
7.3 對RSA的攻擊
RSA的加密是求“E次方的 mod N”箍铲,解密是求“D次方的 mod N”雇卷,原理很簡單鬓椭,但它是否容易被破譯呢?
通過密文直接破譯原文的方法有如下幾種(詳細(xì)說明見參考書目對應(yīng)章節(jié)):
- 通過密文求得明文
- 暴力破解找出 D
- 通過 E 和 N 求出 D
- 對 N 進(jìn)行質(zhì)因數(shù)分解攻擊
- 通過推測 p 和 q 進(jìn)行攻擊
- 其他攻擊关划。只要對 N 進(jìn)行質(zhì)因數(shù)分解并求出 p 和 q小染,就能夠求出 D
除了直接對密文破譯以外,還有一種攻擊方式贮折,叫中間人攻擊(main-in-the-middle attack)裤翩。這種方法雖然不能破譯 RSA,但卻是一種針對機(jī)密性的有效攻擊。中間人攻擊方式如下圖:
上圖的過程可以被重復(fù)多次踊赠,Bob 向 Alice 發(fā)送加密郵件時也可能受到同樣的攻擊呵扛,因此 Bob 即便要發(fā)郵件給 Alice 以詢問她真正的想法,也會被 Mallory 隨意篡改筐带。
這種攻擊不僅針對 RSA今穿,而是可以針對任何公鑰密碼。在這個過程中伦籍,公鑰密碼并沒有被破譯蓝晒,保證了信息的機(jī)密性。然而帖鸦,所謂的機(jī)密性并非在 Alice 和 Bob 之間芝薇,而是在 Alice 和 Mallory 之間,以及 Mallory 和 Bob 之間成立的作儿。僅靠公鑰密碼本身洛二,是無法防御中間人攻擊的。
要防御中間人攻擊立倍,還需要一種手段來確認(rèn)所收到的公鑰是否真的屬于 Bob灭红,這種手段稱為認(rèn)證,這里就用到了證書口注。
7.4 整個加密簽名变擒,解密驗(yàn)簽過程
根據(jù)個人理解,整合上述的所有流程寝志,在不考慮中間人攻擊的情況下娇斑,在開發(fā)中的使用流程如下:
接收者Bob收到消息后的處理流程如下:
7.4.1 代碼模擬
根據(jù)上面流程的代碼實(shí)現(xiàn)如下:
依賴
Hutool、JDK8材部、Spring Boot
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.5</version>
</dependency>
明文DP(消息發(fā)送方進(jìn)行加密簽名):
package com.jiangxb.rsa.dp;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
/**
* 明文DP
*
* @author: jiangxiangbo
* @date: 2021/9/1
*/
@Data
@Slf4j
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PlainText {
private final static String ENCRYPT_DATA_SEPARATOR = "----SEPARATE----";
private final static String publicKey_B = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv8GjRufWGPI7Xe6caZ5h5PbnRIQVzD4P1gDjKZaibcxcApGEaqFkT3Am2U6iKv6paELuwxy+dUL1Jvbs09QljuHgDB9SV0VxSM5LscpCmWJ5P1V6Y/QiholCQHCFR6ok6oE2HWGRw/bPQWr/gHfa2zNPu+CB64cbOxLHIQYIRji47tyywAL5ABhF1msZY2vW8xaFKHGq74sxNpf8s0NUnRnVRANjHtuDa/zvrHim45gqBWg+3gPVSQyPU3ydMoj0AiORJQmqprHaZDB7BufpTEZA6I2WElsKJcsGMdwfSd1s0B1iCzrkMmT30n/XXxyw8qQGsvJvQ2V90QiAV9bV+wIDAQAB";
private final static String privateKey_A = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCyd6fTUJ1BzbOH8eKEI01gjq7sAVd/82tn/1q4+8WwlwrVxaPkQUFTeWu2zY5O+uZhW6uEAGDaD9hruCXNS3oPBSiMc5a2nAXFIfW/sVQky/EOxt3gZcpF1aoySqRwIxBgOkamyFsvcgBZgjDqmCcFwqSxvOQOtkJkwDIdrVsHfOHm4Sb2ZytGUFQaKts81W377fO0h91zhWnmFvD751o0wWv9UYOiExWDcOWThmS0OAdzmdqReQTyFKvpQNPDjkml9lnR0/1dOL8JJVB2hgP52wkhny6V62VusStF9kQk3H+TTDzw/iR8a140EaGXPNWjjm0yQdPfc7xAHonEjjFVAgMBAAECggEAec/qIPXZIF0CuTuEXKSr38gD5NpVmuPO38EPb0uJ96pgnuCzqMxRhmRN/Qv4ojfmn3UucH7BnJVMJtoeEy39NdtTfeo3aJS963vufNTQlf0NoARk1RElKt1XudPwwQlt2ABu0M/YTV4GlxGhyb3ohKoCN76x+si0MIhurIryovyabZCtlhGD2fg3V1t8RBlCEuz68FtB9fSh4zk7u6RhAL5LCOGNbVAiY4hx/NhrDiBfvQBhJZmPG+3gWjjZFgZEH5B0tGByuG2M+dj2qT5LFepkhGyI/upJwOhJrjiRrvR7LmSYCz0lI8/2fVCF8jN/TJav/1xVR82d/165Movm0QKBgQDZv/H1bhPoCoeh8z3ww8Um5FjFb1MMjmh4oB1d2+0QlYTbSVx6mOxBoh0yk+jKztotJyWs61nnHekOhdHFx9Ij1L8oMxybBK+heTuzl2WIs9/2CRBV4XfKMwNiYxJYkaXxUgeHx/2IVXTuFMKMrWhf7kxk2iFrK+Gv63oY0dmvhwKBgQDR0TWXRXC2qEqH/NV/6d24UHl4i/+UP1aKE9jA8xArJYBlKtTWCgM7g3/wxr0IRB6RocVupop/kZJ9RUFjprfaykDOj+A0oC+IDwUmGIjGbR4P921qjWEVQGIFSJvnIwHwGfEAPxvw0uW2tqz9C2GUZ9OB17lecfIdeJQX2Hb3QwKBgA9bWCclAkZlJ7emPgIS7H6XsCMMfODv0jJfqHKMJiX7RYlpnRoQWukuE70TbWGQQRbaIfAWERsZouwhR/AY7ZsVT/33zNap9/D9adZ6oPCJLwxdC0fjRN1/x4dS0WJpszhXvqw20Iyi6kI4OJhPSoMpfT3HnH/AcoRDqTLC6gVVAoGBAI3f9GfseZHZbERV75wF7HoEWI7tw41f4smNMAUQln9GZXKDKtXsgVEN00ZhbFMZlL4O8GyoyoAGVFLGsLeMdUfJeVbzrLyJEHrlBStEbcAW6rwLJ/5jySDQnzdJaLo7TsUnFXKAOgl24gPRtFmLB5mNN1TWJS86x2esMB+LrK33AoGADEDHIUtulc4zclLH9MJj7JcZPkgVz5llJ1jQj3fOu4iPc9TNvV2gV2kWU446gyMmRrQ2We1awnrjaeSzeFnf0OhL+yTzNUmRLMYWZhja/KMhr7b9vVRCCrysZJod+MWodEH+HIJlu9RGIxv7fNNy1S4yRU92OQU43XQ1S93eaEE=";
/**
* 消息明文json
*/
private String msg;
/**
* 加密簽名
*/
public Map<String, Object> encrypt() {
assert StringUtils.isNotBlank(msg);
Map<String, Object> map = Maps.newHashMap();
try {
// 一毫缆、 AES密鑰加密
// 1. 隨機(jī)生成AES密鑰
byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue()).getEncoded();
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key);
// 2. 明文加密
String encryptMsg = aes.encryptBase64(msg);
// 3. 用B公鑰加密AES密鑰
RSA rsa = new RSA(null, publicKey_B);
String encryptKey = rsa.encryptBase64(key, KeyType.PublicKey);
// 4. 組合
String content = encryptKey + ENCRYPT_DATA_SEPARATOR + encryptMsg;
log.info("A發(fā)送的content為:{}", content);
// 二、簽名
// 1. 計(jì)算消息散列值
String msgHash = DigestUtil.sha256Hex(msg, CharsetUtil.UTF_8);
// 2. 對消息散列值用自己私鑰簽名
Sign sign = SecureUtil.sign(SignAlgorithm.SHA1withRSA, privateKey_A, null);
String signed = Base64.encodeBase64String(sign.sign(msgHash));
log.info("簽名為:{}", signed);
map.put("content", content);
map.put("signed", signed);
log.info("發(fā)送的消息:{}", map);
} catch (Exception e) {
log.error("消息明文加密失敗", e);
}
return map;
}
}
密文DP(消息接收方進(jìn)行解密驗(yàn)簽):
package com.jiangxb.rsa.dp;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import javax.validation.constraints.NotBlank;
/**
* 密文DP
*
* @author: jiangxiangbo
* @date: 2021/8/4
*/
@Data
@Slf4j
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CipherText {
private final static String ENCRYPT_DATA_SEPARATOR = "----SEPARATE----";
private final static String publicKey_A = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsnen01CdQc2zh/HihCNNYI6u7AFXf/NrZ/9auPvFsJcK1cWj5EFBU3lrts2OTvrmYVurhABg2g/Ya7glzUt6DwUojHOWtpwFxSH1v7FUJMvxDsbd4GXKRdWqMkqkcCMQYDpGpshbL3IAWYIw6pgnBcKksbzkDrZCZMAyHa1bB3zh5uEm9mcrRlBUGirbPNVt++3ztIfdc4Vp5hbw++daNMFr/VGDohMVg3Dlk4ZktDgHc5nakXkE8hSr6UDTw45JpfZZ0dP9XTi/CSVQdoYD+dsJIZ8uletlbrErRfZEJNx/k0w88P4kfGteNBGhlzzVo45tMkHT33O8QB6JxI4xVQIDAQAB";
private final static String privateKey_B = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC/waNG59YY8jtd7pxpnmHk9udEhBXMPg/WAOMplqJtzFwCkYRqoWRPcCbZTqIq/qloQu7DHL51QvUm9uzT1CWO4eAMH1JXRXFIzkuxykKZYnk/VXpj9CKGiUJAcIVHqiTqgTYdYZHD9s9Bav+Ad9rbM0+74IHrhxs7EschBghGOLju3LLAAvkAGEXWaxlja9bzFoUocarvizE2l/yzQ1SdGdVEA2Me24Nr/O+seKbjmCoFaD7eA9VJDI9TfJ0yiPQCI5ElCaqmsdpkMHsG5+lMRkDojZYSWwolywYx3B9J3WzQHWILOuQyZPfSf9dfHLDypAay8m9DZX3RCIBX1tX7AgMBAAECggEBAIkRYyMGCTYfwGvuagPdYOCH1NxXBjXOjwdL7xUFRenyUDrNxbdq0gcuhbaDzMuq6XFLltwFKecsC4zkqHjqhkZSExLXOMaFLur5+4WErIJzr3OkKC5Wjm9YofDp/XsyldzCq+nomodXXuLGFwi/o8NYNEB5xKSVGNPrIkfqxfNazdR63738zq0ZPQmMjxEb/AK5uc+fdF9qosDrNI0SqQng00mhfpilvwHZbOYPfKNfh26lpqTEAGk0gaFGfr/QnhUDAnxfaoLhr9zELr4utrkwpaCzX958MrRB5naeScocYSl1h4Bi6htjjpdLWDKkk/vQ8Keno6GtF9Iha8MdvnECgYEA9p5UzaP3qYek4pe96milqmPiYkQeqSAUWSp1TpC5ppIoyPGMYl7Ia8SOsIYBw+9WL9iIR6jqZOpd/E68j8YsW5PyJuFYQTVjXZrVD76D435mskl4gsKz1izEWz5jzU/oE4mGfuaobfaOuw5ixun7dd8FrXknIVHbne0zyEZ+FY0CgYEAxw0Jq3aJbrJHX8ahPsZAodbWKYH8Ojkt2GkXKdrB9HJ6EGKockjPj7R/+ForXw2XWoGdoL4QPalhKuJX+3bsSQIgt7mDRiDEPK7XkbKd5mS/HTXWXsTIkGaDhYlq6yOkbmRsR3QgCfnhLYaaYkZ6kIKDsGgUJF8oIqxKrlDWo6cCgYBUyulzbu3nLwklE3Er2GElbYRXrv4vviTg53U/1wjN2bEGLe7Ln7UfQIyi6uBOgsrKVpO8t7onimFYL6YrdMKplfuLHK2gdf+9HlAlQqbMIBilMheqNdFpUSkOCix8Wf38QauplBrS/BPlArQ5mhdoVo74LxCiJyfwa68DLCGLvQKBgEGg4tdNtfJxhWbmrrNr2lOB6gq1eNwZjiwUOjbqkZhvRh+w56kGqKjQ8oCH+lTUvlpw8e/VurUZ65egGTIn+6/2q6Ln34h3tTvsydaX9cfI39pZrdyBNT+nDSYyMLZmggiDw8+rUgT4Bm5kOvK8Gh0bax/2sO1tEmacN+NRc/NxAoGALzEnmKI9B46NVNOWi0VLtTdiloSI6bxgli0Rm8T+6wD6y9JNnWsibYydGx9pDn6w2qihuP1QcKruHUiZ7V8aahhD4o4e/a+IgRzNYcQh4CngUzL+XymlqQVbDSaiEiVi/qSNv+i9mgeF/mSYbYcZjhFPjzdOy3hvtO3GtjDSMQw=";
@NotBlank(message = "密文不能為空")
private String content;
@NotBlank(message = "簽名不能為空")
private String signed;
/**
* 解密驗(yàn)簽
*/
public String decrypt() {
try {
// 一乐导、分解密文
log.info("接收到的密文:{}", content);
String[] split = content.split(ENCRYPT_DATA_SEPARATOR);
assert split.length == 2;
String encryptKey = split[0];
String encryptMsg = split[1];
// 獲取對稱密鑰
RSA rsa = new RSA(privateKey_B, null);
byte[] key = rsa.decrypt(Base64.decodeBase64(encryptKey), KeyType.PrivateKey);
log.info("獲取對稱密鑰成功, 解析到的對稱密鑰為:{}", key);
// 用對稱密鑰解密苦丁,提取消息明文
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key);
String msg = aes.decryptStr(Base64.decodeBase64(encryptMsg));
assert msg != null;
// 計(jì)算明文散列值
String msgHash = DigestUtil.sha256Hex(msg, CharsetUtil.UTF_8);
// 二、解簽
log.info("接收到的簽名:{}", signed);
assert StringUtils.isNotBlank(signed);
Sign sign = SecureUtil.sign(SignAlgorithm.SHA1withRSA, null, publicKey_A);
boolean isVerified = sign.verify(msgHash.getBytes(CharsetUtil.UTF_8), Base64.decodeBase64(signed));
if (!isVerified) {
log.info("驗(yàn)簽失敗");
} else {
log.info("驗(yàn)簽成功, 收到的消息為:{}", msg);
return msg;
}
} catch (Exception e) {
log.error("解密或驗(yàn)簽失敗", e);
}
return null;
}
}
demo測試:
package com.jiangxb.rsa;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.jiangxb.model.Student;
import com.jiangxb.rsa.dp.CipherText;
import com.jiangxb.rsa.dp.PlainText;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* @author: jiangxiangbo
* @date: 2021/7/30
*/
@Validated
@RestController
@RequestMapping("/rsa")
public class RsaController {
/**
* 接收消息
*/
@PostMapping("/post")
public String testPost(@Validated CipherText cipherText) {
return cipherText.decrypt();
}
/**
* 發(fā)送消息
*/
public static void main(String[] args) {
String msg = JSONObject.toJSONString(Student.builder().name("張三").age(20).build());
Map<String, Object> encrypt = PlainText.builder().msg(msg).build().encrypt();
String resopnse = HttpUtil.post("127.0.0.1:8080/rsa/post", encrypt);
System.out.println(resopnse);
}
}
Student實(shí)體:
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private String name;
private Integer age;
}
測試結(jié)果:
[INFO ] [http-nio-8080-exec-4] [com.jiangxb.rsa.dp.CipherText ]
接收到的密文:rVcet45asWcB2KZwQv6HmOktquLyPPqixOjPpIc3eIbULnUcuorl2KIxK4XDipvreT7tATlW4VFzbC7gSFW0DfiWLdqknLyPzmC2nXBeaP8o89bMk4pe4sQ/NVct5JTRpJqx+0+8MGA4QrMUlQZ+4REhZmdLg+JyJijRSccRvQMZhBh1uOZCRWeo8gmR5oWMuquo0r91Y91ewaMoVHjWfMdtu2Y1H8vXAeb7b1aDc26SEtstZb8q/TqZlpo+hh2pVoDGIhXJUQH5VlizF9sZnZBPuLUCnar7NU+wi2oEWlrNHscaP0dKCRKsikAcf0kplM3UhXCXghC6xkZsk1uptA==----SEPARATE----Wo89FnRcZ3e8PXL40tgieUYQssiUlYIzbzP8gMXB04w=
[INFO ] [http-nio-8080-exec-4] [com.jiangxb.rsa.dp.CipherText ] 獲取對稱密鑰成功, 解析到的對稱密鑰為:[-28, 4, -90, 9, 120, 77, 28, 73, 119, 18, -112, 90, 65, -59, 89, -113]
[INFO ] [http-nio-8080-exec-4] [com.jiangxb.rsa.dp.CipherText ] 接收到的簽名:kh7HtOnSPQi8t/vDrle31tRWUeSGc3cyc8S5heHKThq5HtH7dCeZEY8vq+UFtlMr/eqxF19y/VonXlaSaFsRxzKLM3fVJ8AGWivdH7MVpBI3/x3cHbqDfgnSRmhVQDBnTBjrgTgO00KwZx/hxJn6x5NzfXawxpoFyIXauJuqOpW+tts8ysNJMhAeR7/LdXuISEFLK7lZf/zWESwiH+f4davQv3XiVuXzTEH6U05NGwDq2LqWMiCBh/MgQFJfLaOBUO9kCNoMcuNiS3KOG0JBLr718rPX4lUKI37C0Mc3rAzgvnlZNMv68YtPxFiHe0VhpW/ssOHk245zUPWNjIsdPA==
[INFO ] [http-nio-8080-exec-4] [com.jiangxb.rsa.dp.CipherText ] 驗(yàn)簽成功, 收到的消息為:{"age":20,"name":"張三"}
參考
《圖解密碼技術(shù)》