數據加密,是一門歷史悠久的技術,指經過加密算法和加密密鑰將明文轉變爲密文,而解密則是經過解密算法和解密密鑰將密文恢復爲明文。它的核心是密碼學。數據加密還是計算機系統對信息進行保護的一種最可靠的辦法。它利用密碼技術對信息進行加密,實現信息隱蔽,從而起到保護信息的安全的做用。javascript
本文阿寶哥將介紹如何對數據進行混合加密,即便用對稱加密算法與非對稱加密算法對數據進行加密,從而進一步保證數據的安全性。閱讀完本文,你將瞭解如下內容:html
在最後的 阿寶哥有話說 環節,阿寶哥還將簡單介紹一下什麼是消息摘要算法和什麼是 MD5 算法及其用途與缺陷。好的,如今讓咱們步入正題。爲了讓剛接觸混合加密的小夥伴更好地瞭解並掌握混合加密,阿寶哥將乘坐 「時光機」 帶你們來到某個發版的夜晚...java
那一晚咱們團隊的小夥伴正在等服務端數據升級,爲了讓你們 「忘記」 這個漫漫的升級過程,阿寶哥就立馬組織了一場關於混合加密的技術分享會。在阿寶哥 「威逼利誘」 之下,團隊的小夥伴們很快就到齊了,以後阿寶哥以如下對話拉開了分享會的序幕:git
幾分鐘事後,小哥講完了,基本關鍵點都有回答上來,但還遺漏了一些內容。爲了讓小夥伴們更好地理解對稱加密,阿寶哥對小哥表述的內容進行了從新梳理,下面讓咱們來一塊兒認識一下對稱加密。github
對稱密鑰算法(英語:Symmetric-key algorithm)又稱爲對稱加密、私鑰加密、共享密鑰加密,是密碼學中的一類加密算法。這類算法在加密和解密時使用相同的密鑰,或是使用兩個能夠簡單地相互推算的密鑰。ajax
算法公開、計算量小、加密速度快、加密效率高,適合對大量數據進行加密的場景。 好比 HLS(HTTP Live Streaming)普通加密場景中,通常會使用 AES-128 對稱加密算法對 TS 切片進行加密,以保證多媒體資源安全。算法
發送方使用密鑰將明文數據加密成密文,而後發送出去,接收方收到密文後,使用同一個密鑰將密文解密成明文讀取。shell
常見的對稱加密算法有 AES、ChaCha20、3DES、Salsa20、DES、Blowfish、IDEA、RC五、RC六、Camellia。這裏咱們以常見的 AES 算法爲例,來介紹一下 AES(Advanced Encryption Standard)對稱加密與解密的過程。segmentfault
下面阿寶哥將使用 crypto-js 這個庫來介紹 AES 算法的加密與解密,該庫提供了 CryptoJS.AES.encrypt()
方法用於實現 AES 加密,而 AES 解密對應的方法是 CryptoJS.AES.decrypt()
。安全
基於上述兩個方法阿寶哥進一步封裝了 aesEncrypt()
和 aesDecrypt()
這兩個方法,它們分別用於 AES 加密與解密,其具體實現以下所示:
// AES加密 function aesEncrypt(content) { let text = CryptoJS.enc.Utf8.parse(JSON.stringify(content)); let encrypted = CryptoJS.AES.encrypt(text, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); return encrypted.toString(); }
// AES解密 function aesDecrypt(content) { let decrypt = CryptoJS.AES.decrypt(content, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); return decrypt.toString(CryptoJS.enc.Utf8); }
在以上示例中,咱們在頁面上建立了 3 個 textarea,分別用於存放明文、加密後的密文和解密後的明文。當用戶點擊 加密 按鈕時,會對用戶輸入的明文進行 AES 加密,完成加密後,會把密文顯示在密文對應的 textarea 中,當用戶點擊 解密 按鈕時,會對密文進行 AES 解密,完成解密後,會把解密後的明文顯示在對應的 textarea 中。
以上示例對應的完整代碼以下所示:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>AES 對稱加密與解密示例</title> <style> .block { flex: 1; } </style> </head> <body> <h3>阿寶哥:AES 對稱加密與解密示例(CBC 模式)</h3> <div style="display: flex;"> <div class="block"> <p>①明文加密 => <button onclick="encrypt()">加密</button></p> <textarea id="plaintext" rows="5" cols="15"></textarea> </div> <div class="block"> <p>②密文解密 => <button onclick="decrypt()">解密</button></p> <textarea id="ciphertext" rows="5" cols="15"></textarea> </div> <div class="block"> <p>③解密後的明文</p> <textarea id="decryptedCiphertext" rows="5" cols="15"></textarea> </div> </div> <!-- 引入 CDN Crypto.js AES加密 --> <script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/core.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/enc-base64.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/cipher-core.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/aes.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/pad-pkcs7.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/enc-utf8.min.js"></script> <!-- 引入 CDN Crypto.js 結束 --> <script> const key = CryptoJS.enc.Utf8.parse("0123456789abcdef"); // 密鑰 const iv = CryptoJS.enc.Utf8.parse("abcdef0123456789"); // 初始向量 const plaintextEle = document.querySelector("#plaintext"); const ciphertextEle = document.querySelector("#ciphertext"); const decryptedCiphertextEle = document.querySelector( "#decryptedCiphertext" ); function encrypt() { let plaintext = plaintextEle.value; ciphertextEle.value = aesEncrypt(plaintext); } function decrypt() { let ciphertext = ciphertextEle.value; decryptedCiphertextEle.value = aesDecrypt(ciphertext).replace(/\"/g,''); } // AES加密 function aesEncrypt(content) { let text = CryptoJS.enc.Utf8.parse(JSON.stringify(content)); let encrypted = CryptoJS.AES.encrypt(text, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); return encrypted.toString(); } // AES解密 function aesDecrypt(content) { let decrypt = CryptoJS.AES.decrypt(content, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); return decrypt.toString(CryptoJS.enc.Utf8); } </script> </body> </html>
在上面的示例中,咱們經過 AES 對稱加密算法,對 「我是阿寶哥」 明文進行加密,從而實現信息隱蔽。
那麼使用對稱加密算法就能夠解決咱們前面的問題麼?答案是否認,這是由於對稱加密存在一些的缺點。
經過使用對稱加密算法,咱們已經把明文加密成密文。雖然這解決了數據的安全性,但同時也帶來了另外一個新的問題。由於對稱加密算法,加密和解密時使用的是同一個密鑰,因此對稱加密的安全性就不只僅取決於加密算法自己的強度,更取決於密鑰是否被安全的傳輸或保管。
另外對於實際應用場景,爲了不單一的密鑰被攻破,從而致使全部的加密數據被破解,對於不一樣的數據,咱們通常會使用不一樣的密鑰進行加密,這樣雖然提升了安全性,但也增長了密鑰管理的難度。
因爲對稱加密存在以上的問題,所以它並非一種好的解決方案。爲了找到更好的方案,阿寶哥開始了另外一輪新的對話。
非對稱加密算法須要兩個密鑰:公開密鑰(publickey:簡稱公鑰)和私有密鑰(privatekey:簡稱私鑰)。公鑰與私鑰是一對,若是用公鑰對數據進行加密,只有用對應的私鑰才能解密。 由於加密和解密使用的是兩個不一樣的密鑰,因此這種算法叫做非對稱加密算法。
安全性更高,公鑰是公開的,私鑰是本身保存的,不須要將私鑰提供給別人。
常見的非對稱加密算法有 RSA、Elgamal、揹包算法、Rabin、D-H、ECC(橢圓曲線加密算法)。這裏咱們以常見的 RSA 算法爲例,來介紹一下 RSA 非對稱加密與解密的過程。
RSA 是 1977 年由羅納德·李維斯特(Ron Rivest)、阿迪·薩莫爾(Adi Shamir)和倫納德·阿德曼(Leonard Adleman)一塊兒提出的。當時他們三人都在麻省理工學院工做。RSA 就是他們三人姓氏開頭字母拼在一塊兒組成的。
下面阿寶哥將使用 jsencrypt 這個庫來介紹 RSA 算法的加密與解密,該庫提供了 encrypt()
方法用於實現 RSA 加密,而 RSA 解密對應的方法是 decrypt()
。
使用 jsencrypt 這個庫以前,咱們須要先生成公鑰和私鑰。接下來阿寶哥以 macOS 系統爲例,來介紹一下如何生成公私鑰。
首先咱們先來生成私鑰,在命令行輸入如下命令:
$ openssl genrsa -out rsa_1024_priv.pem 1024
在該命令成功運行以後,在當前目錄下會生成一個 rsa_1024_priv.pem 文件,該文件的內容以下:
-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDocWYwnJ4DYur0BjxFjJkLv4QRJpTJnwjiwxkuJZe1HTIIuLbu /yHyHLhc2MAHKL0Ob+8tcKXKsL1oxs467+q0jA+glOUtBXFcUnutWBbnf9qIDkKP ... bKkRJNJ2PpfWA45Vdq6u+izrn9e2TabKjWIfTfT/ZQ== -----END RSA PRIVATE KEY-----
而後咱們來生成公鑰,一樣在命令行輸入如下命令:
$ openssl rsa -pubout -in rsa_1024_priv.pem -out rsa_1024_pub.pem
在該命令成功運行以後,在當前目錄下會生成一個 rsa_1024_pub.pem 文件,該文件的內容以下:
-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDocWYwnJ4DYur0BjxFjJkLv4QR JpTJnwjiwxkuJZe1HTIIuLbu/yHyHLhc2MAHKL0Ob+8tcKXKsL1oxs467+q0jA+g lOUtBXFcUnutWBbnf9qIDkKP2uoDdZ//LUeW7jibVrVJbXU2hxB8bQpBkltZf/xs cyhRIeiXxs13vlSHVwIDAQAB -----END PUBLIC KEY-----
建立完公私鑰以後,咱們就能夠進一步建立 RSA 加密器和解密器,具體代碼以下:
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDocWYwnJ4DYur0BjxFjJkLv4QR ... cyhRIeiXxs13vlSHVwIDAQAB -----END PUBLIC KEY-----`; const PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDocWYwnJ4DYur0BjxFjJkLv4QRJpTJnwjiwxkuJZe1HTIIuLbu ... bKkRJNJ2PpfWA45Vdq6u+izrn9e2TabKjWIfTfT/ZQ== -----END RSA PRIVATE KEY-----`; const encryptor = new JSEncrypt(); // RSA加密器 encryptor.setPublicKey(PUBLIC_KEY); const decryptor = new JSEncrypt(); // RSA解密器 decryptor.setPrivateKey(PRIVATE_KEY);
在以上示例中,咱們在頁面上建立了 3 個 textarea,分別用於存放明文、加密後的密文和解密後的明文。當用戶點擊 加密 按鈕時,會對用戶輸入的明文進行 RSA 加密,完成加密後,會把密文顯示在密文對應的 textarea 中,當用戶點擊 解密 按鈕時,會對密文進行 RSA 解密,完成解密後,會把解密後的明文顯示在對應的 textarea 中。
以上示例對應的完整代碼以下所示:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>RSA 對稱加密與解密示例</title> <style> .block { flex: 1; } </style> </head> <body> <h3>阿寶哥:RSA 對稱加密與解密示例</h3> <div style="display: flex;"> <div class="block"> <p>①明文加密 => <button onclick="encrypt()">加密</button></p> <textarea id="plaintext" rows="5" cols="15"></textarea> </div> <div class="block"> <p>②密文解密 => <button onclick="decrypt()">解密</button></p> <textarea id="ciphertext" rows="5" cols="15"></textarea> </div> <div class="block"> <p>③解密後的明文</p> <textarea id="decryptedCiphertext" rows="5" cols="15"></textarea> </div> </div> <script src="https://cdn.bootcdn.net/ajax/libs/jsencrypt/2.3.1/jsencrypt.min.js"></script> <script> const PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDocWYwnJ4DYur0BjxFjJkLv4QR JpTJnwjiwxkuJZe1HTIIuLbu/yHyHLhc2MAHKL0Ob+8tcKXKsL1oxs467+q0jA+g lOUtBXFcUnutWBbnf9qIDkKP2uoDdZ//LUeW7jibVrVJbXU2hxB8bQpBkltZf/xs cyhRIeiXxs13vlSHVwIDAQAB -----END PUBLIC KEY-----`; const PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDocWYwnJ4DYur0BjxFjJkLv4QRJpTJnwjiwxkuJZe1HTIIuLbu /yHyHLhc2MAHKL0Ob+8tcKXKsL1oxs467+q0jA+glOUtBXFcUnutWBbnf9qIDkKP 2uoDdZ//LUeW7jibVrVJbXU2hxB8bQpBkltZf/xscyhRIeiXxs13vlSHVwIDAQAB AoGAKOarYKpuc5IYXdArEuHmnFaa2pm7XK8LVTuXVrNuuoPkpfw61Fs4ke3T0yKg x6G3gq7Xm1tTEROAgMtaxqwo1D5n1H0nkyDFggLB0K9Ws0frp7HENtSQwdNSry1A iD8TLxkhoWo7BS0VViLT1gKOfnw4YeMJP+CcOQ+DQjCsUMECQQD0Nc0vKBTlK6GT 28gcIMVoQy2KicjiH222A9/TLCNAQ9DEeZDYEptuTfrlbggfWdgQ3nc6CyvGf6c5 6uBPi/+5AkEA86oqqZPi7ekkUVHx0VSkp0mTlD1tAPhDE8cLX8vyImGExS+tTznz ROyzm3T1M1PisdQIU8Wd5rqvHP6dB0enjwJAauhKpMQ1MYYCPApQ9g9anCQcgbOD 34nGq5HSoE2IOQ/3Cqv1PsIWjRlSJrIemCrqrafWJfDR/xnPCUnLXMd68QJAPNwG 1d4zMvslcA5ImOFMUuBEtST2geSAVINFqwK0krPKxrmWzxAJW/DHF5AJ4m0UVRhB kDLusn90V4iczgGurwJAZUz6w01OeoLhsOuWNvkbTq+IV0NQ5GAEGA721Ck5zp86 bKkRJNJ2PpfWA45Vdq6u+izrn9e2TabKjWIfTfT/ZQ== -----END RSA PRIVATE KEY-----`; const encryptor = new JSEncrypt(); // RSA加密器 encryptor.setPublicKey(PUBLIC_KEY); const decryptor = new JSEncrypt(); // RSA解密器 decryptor.setPrivateKey(PRIVATE_KEY); const plaintextEle = document.querySelector("#plaintext"); const ciphertextEle = document.querySelector("#ciphertext"); const decryptedCiphertextEle = document.querySelector( "#decryptedCiphertext" ); function encrypt() { let plaintext = plaintextEle.value; ciphertextEle.value = encryptor.encrypt(plaintext); } function decrypt() { let ciphertext = ciphertextEle.value; decryptedCiphertextEle.value = decryptor.decrypt(ciphertext); } </script> </body> </html>
在上面的示例中,咱們經過 RSA 非對稱加密算法,對 「我是阿寶哥」 明文進行加密,從而實現信息隱蔽。
那麼使用非對稱加密算法就能夠解決咱們前面的問題麼?答案是否認,這是由於非對稱加密也存在一些的缺點。
非對稱加密算法加密和解密花費時間長、速度慢,只適合對少許數據進行加密。由於咱們要提供的是通用的解決方案,即要同時考慮到少許數據和大量數據的狀況,因此非對稱加密也不是一個好的解決方案。爲了解決問題,阿寶哥又從新開啓了一輪新的對話。
混合加密是結合 對稱加密 和 非對稱加密 各自優勢的一種加密方式。其具體的實現思路是先使用 對稱加密算法 對數據進行加密,而後使用非對稱加密算法對 對稱加密的密鑰 進行非對稱加密,以後再把加密後的密鑰和加密後的數據發送給接收方。
爲了讓小夥伴們更加直觀理解上述的過程,阿寶哥花了點心思畫了一張圖,用來進一步說明混合加密的過程,下面咱們就一塊兒來看圖吧。
瞭解完 「混合加密數據傳輸流程」,阿寶哥跟小夥伴一塊兒來實現一下上述的混合加密流程。這裏咱們會基於前面介紹過的對稱加密和非對稱加密的示例進行開發,即如下示例會直接利用前面非對稱加密示例中用到的公私鑰。
function getRandomAESKey() { return ( Math.random().toString(36).substring(2, 10) + Math.random().toString(36).substring(2, 10) ); }
// AES加密 function aesEncrypt(key, iv, content) { let text = CryptoJS.enc.Utf8.parse(JSON.stringify(content)); let encrypted = CryptoJS.AES.encrypt(text, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); return encrypted.toString(); } // AES解密 function aesDecrypt(key, iv, content) { let decrypt = CryptoJS.AES.decrypt(content, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); return decrypt.toString(CryptoJS.enc.Utf8); }
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDocWYwnJ4DYur0BjxFjJkLv4QR ... cyhRIeiXxs13vlSHVwIDAQAB -----END PUBLIC KEY-----`; const PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDocWYwnJ4DYur0BjxFjJkLv4QRJpTJnwjiwxkuJZe1HTIIuLbu ... bKkRJNJ2PpfWA45Vdq6u+izrn9e2TabKjWIfTfT/ZQ== -----END RSA PRIVATE KEY-----`; const rsaEncryptor = new JSEncrypt(); // RSA加密器 rsaEncryptor.setPublicKey(PUBLIC_KEY); const rsaDecryptor = new JSEncrypt(); // RSA解密器 rsaDecryptor.setPrivateKey(PRIVATE_KEY);
function hybirdEncrypt(data) { const iv = getRandomAESKey(); const key = getRandomAESKey(); const encryptedData = aesEncrypt(key, iv, data); const encryptedIv = rsaEncryptor.encrypt(iv); const encryptedKey = rsaEncryptor.encrypt(key); return { iv: encryptedIv, key: encryptedKey, data: encryptedData, }; } function hybirdDecrypt(encryptedResult) { const iv = rsaDecryptor.decrypt(encryptedResult.iv); const key = rsaDecryptor.decrypt(encryptedResult.key); const data = encryptedResult.data; return aesDecrypt(key, iv, data); }
以上步驟完成以後,咱們基本已經完成了混合加密的功能,在看完整代碼以前,咱們先來看一下實際的運行效果:
備註:密文解密下方對應的 textarea 文本框中,除了加密的數據以外,還會包含使用 RSA 加密過的 AES CBC 模式中的 iv 和 key。
在以上示例中,咱們在頁面上建立了 3 個 textarea,分別用於存放明文、加密後的數據和解密後的明文。當用戶點擊 加密 按鈕時,會對用戶輸入的明文進行混合加密,完成加密後,會把加密的數據顯示在密文對應的 textarea 中,當用戶點擊 解密 按鈕時,會對密文進行 混合解密,即先使用 RSA 私鑰解密 AES 的 key 和 iv,而後再使用它們對 AES 加密過的密文進行 AES 解密,完成解密後,會把解密後的明文顯示在對應的 textarea 中。
以上示例對應的完整代碼以下所示:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>混合加密與解密示例</title> <style> .block { flex: 1; } </style> </head> <body> <h3>阿寶哥:混合加密與解密示例</h3> <div style="display: flex;"> <div class="block"> <p>①明文加密 => <button onclick="encrypt()">加密</button></p> <textarea id="plaintext" rows="5" cols="15"></textarea> </div> <div class="block"> <p>②密文解密 => <button onclick="decrypt()">解密</button></p> <textarea id="ciphertext" rows="5" cols="15"></textarea> </div> <div class="block"> <p>③解密後的明文</p> <textarea id="decryptedCiphertext" rows="5" cols="15"></textarea> </div> </div> <!-- 引入 CDN Crypto.js AES加密 --> <script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/core.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/enc-base64.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/md5.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/evpkdf.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/cipher-core.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/aes.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/pad-pkcs7.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/enc-utf8.min.js"></script> <!-- 引入 CDN Crypto.js 結束 --> <script src="https://cdn.bootcdn.net/ajax/libs/jsencrypt/2.3.1/jsencrypt.min.js"></script> <script> const PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDocWYwnJ4DYur0BjxFjJkLv4QR JpTJnwjiwxkuJZe1HTIIuLbu/yHyHLhc2MAHKL0Ob+8tcKXKsL1oxs467+q0jA+g lOUtBXFcUnutWBbnf9qIDkKP2uoDdZ//LUeW7jibVrVJbXU2hxB8bQpBkltZf/xs cyhRIeiXxs13vlSHVwIDAQAB -----END PUBLIC KEY-----`; const PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDocWYwnJ4DYur0BjxFjJkLv4QRJpTJnwjiwxkuJZe1HTIIuLbu /yHyHLhc2MAHKL0Ob+8tcKXKsL1oxs467+q0jA+glOUtBXFcUnutWBbnf9qIDkKP 2uoDdZ//LUeW7jibVrVJbXU2hxB8bQpBkltZf/xscyhRIeiXxs13vlSHVwIDAQAB AoGAKOarYKpuc5IYXdArEuHmnFaa2pm7XK8LVTuXVrNuuoPkpfw61Fs4ke3T0yKg x6G3gq7Xm1tTEROAgMtaxqwo1D5n1H0nkyDFggLB0K9Ws0frp7HENtSQwdNSry1A iD8TLxkhoWo7BS0VViLT1gKOfnw4YeMJP+CcOQ+DQjCsUMECQQD0Nc0vKBTlK6GT 28gcIMVoQy2KicjiH222A9/TLCNAQ9DEeZDYEptuTfrlbggfWdgQ3nc6CyvGf6c5 6uBPi/+5AkEA86oqqZPi7ekkUVHx0VSkp0mTlD1tAPhDE8cLX8vyImGExS+tTznz ROyzm3T1M1PisdQIU8Wd5rqvHP6dB0enjwJAauhKpMQ1MYYCPApQ9g9anCQcgbOD 34nGq5HSoE2IOQ/3Cqv1PsIWjRlSJrIemCrqrafWJfDR/xnPCUnLXMd68QJAPNwG 1d4zMvslcA5ImOFMUuBEtST2geSAVINFqwK0krPKxrmWzxAJW/DHF5AJ4m0UVRhB kDLusn90V4iczgGurwJAZUz6w01OeoLhsOuWNvkbTq+IV0NQ5GAEGA721Ck5zp86 bKkRJNJ2PpfWA45Vdq6u+izrn9e2TabKjWIfTfT/ZQ== -----END RSA PRIVATE KEY-----`; const rsaEncryptor = new JSEncrypt(); // RSA加密器 rsaEncryptor.setPublicKey(PUBLIC_KEY); const rsaDecryptor = new JSEncrypt(); // RSA解密器 rsaDecryptor.setPrivateKey(PRIVATE_KEY); const plaintextEle = document.querySelector("#plaintext"); const ciphertextEle = document.querySelector("#ciphertext"); const decryptedCiphertextEle = document.querySelector( "#decryptedCiphertext" ); function getRandomAESKey() { return ( Math.random().toString(36).substring(2, 10) + Math.random().toString(36).substring(2, 10) ); } // AES加密 function aesEncrypt(key, iv, content) { let text = CryptoJS.enc.Utf8.parse(JSON.stringify(content)); let encrypted = CryptoJS.AES.encrypt(text, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); return encrypted.toString(); } // AES解密 function aesDecrypt(key, iv, content) { let decrypt = CryptoJS.AES.decrypt(content, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); return decrypt.toString(CryptoJS.enc.Utf8); } function hybirdEncrypt(data) { const iv = getRandomAESKey(); const key = getRandomAESKey(); const encryptedData = aesEncrypt(key, iv, data); const encryptedIv = rsaEncryptor.encrypt(iv); const encryptedKey = rsaEncryptor.encrypt(key); return { iv: encryptedIv, key: encryptedKey, data: encryptedData, }; } function hybirdDecrypt(encryptedResult) { const iv = rsaDecryptor.decrypt(encryptedResult.iv); const key = rsaDecryptor.decrypt(encryptedResult.key); const data = encryptedResult.data; return aesDecrypt(key, iv, data); } function encrypt() { let plaintext = plaintextEle.value; const encryptedResult = hybirdEncrypt(plaintext); ciphertextEle.value = JSON.stringify(encryptedResult); } function decrypt() { let ciphertext = ciphertextEle.value; const encryptedResult = JSON.parse(ciphertext); decryptedCiphertextEle.value = hybirdDecrypt(encryptedResult).replace(/\"/g,''); } </script> </body> </html>
經過這個示例,相信你們對混合加密已經有了必定的瞭解。但在實際 Web 項目中,咱們通常不會在客戶端進行數據解密,而是會把數據提交到服務端,而後由服務端進行數據解密和數據處理。
HTTP 協議對大多數 Web 開發者來講,都不會陌生。HTTP 協議是基於請求和響應,具體以下圖所示:
在對數據安全要求較高的場景或傳輸敏感數據時,咱們就能夠考慮利用前面的混合加密方案對提交到服務端的數據進行混合加密,當服務端接收到對應的加密數據時,再使用對應的解密算法對加密的數據進行解密,從而進一步進行數據處理。
可是若是服務端也要返回敏感數據時,應該怎麼辦呢?這裏阿寶哥給你們介紹一種方案,該方案只需使用一對公私鑰。固然該方案僅供你們參考,若是你有好的方案,歡迎給阿寶哥留言或跟阿寶哥交流喲。
下面咱們來看一下該方案的具體操做流程:
① 生成一個惟一的 reqId(請求 ID),用於標識當前請求;
② 分別生成一個隨機的 AES Key 和 AES IV(採用 AES CBC 模式);
③ 採用 RSA 非對稱加密算法,分別對 AES Key 和 AES IV 進行 RSA 非對稱加密;
④ 採用隨機生成的 AES Key 和 AES IV 對敏感數據進行 AES 對稱加密;
⑤ 把 reqId 做爲 key,AES Key 和 AES IV 組成的對象做爲 value 保存到 Map 或 {} 對象中;
⑥ 把 reqId、加密後的 AES Key、AES IV 和加密後的數據保存到對象中提交到服務端;
⑦ 當服務端接收到數據後,對接收的數據進行解密,而後使用客戶端傳過來的解密後的 AES Key 和 AES IV 對響應數據進行 AES 對稱加密;
⑧ 服務端在完成數據加密後,把 reqId 和加密後的數據包裝成響應對象,返回給客戶端;
⑨ 當客戶端成功接收服務端的響應後,先獲取 reqId,進而從保存 AES Key 和 IV 的 Map 獲取該 reqId 對應的 AES 加密信息;
⑩ 客戶端使用當前 reqId 對應的加密信息,對服務端返回的數據進行解密,當完成解密以後,從 Map 或 {} 對象中刪除已有記錄。
如今咱們來對上述流程作個簡單分析,首先 AES 加密信息都是隨機生成的且根據每一個請求獨立地保存到內存中,把 AES 加密信息中的 Key 和 IV 提交到服務端的時候都會使用 RSA 非對稱加密算法進行加密。
在服務端返回數據的時候,會使用當前請求對應的 AES 加密信息對返回的結果進行加密,同時返回當前請求對應的 reqId(請求 ID)。即服務端不須要再生成新的 AES 加密信息,來對響應數據進行加密,這樣就不須要在響應對象中傳遞 AES 加密信息。
該方案看似挺完美的,因爲咱們加密的信息仍是存在內存中,若是使用開發者工具對 Web 應用進行調試時,那麼仍是能夠看到每一個請求對應的加密信息。那麼這個問題該如何解決呢?能不能防止使用開發者工具對咱們的 Web 應用進行調試,答案是有的。
不過這裏阿寶哥就不繼續展開了,後面可能會單獨寫一篇文章來介紹如何防止使用開發者工具調試 Web 應用,感興趣的小夥伴能夠給我留言喲。
其實在平常工做中,除了對稱加密和非對稱加密算法以外。還有一種用得比較廣的消息摘要算法。消息摘要算法是密碼學算法中很是重要的一個分支,它經過對全部數據提取指紋信息以實現數據簽名、數據完整性校驗等功能,因爲其不可逆性,有時候會被用作敏感信息的加密。消息摘要算法也被稱爲哈希(Hash)算法或散列算法。
任何消息通過散列函數處理後,都會得到惟一的散列值,這一過程稱爲 「消息摘要」,其散列值稱爲 「數字指紋」,其算法天然就是 「消息摘要算法」了。 換句話說,若是其數字指紋一致,就說明其消息是一致的。
(圖片來源 —— https://zh.wikipedia.org/wiki...)
消息摘要算法的主要特徵是加密過程不須要密鑰,而且通過加密的數據沒法被解密,目前能夠解密逆向的只有 CRC32 算法,只有輸入相同的明文數據通過相同的消息摘要算法才能獲得相同的密文。 消息摘要算法不存在密鑰的管理與分發問題,適合於分佈式網絡上使用。消息摘要算法主要應用在 「數字簽名」 領域,做爲對明文的摘要算法。著名的摘要算法有 RSA 公司的 MD5 算法和 SHA-1 算法及其大量的變體。
消息摘要算法擁有如下特色:
MD5(Message Digest Algorithm 5,消息摘要算法版本5),它由 MD二、MD三、MD4 發展而來,由 Ron Rivest(RSA 公司)在 1992 年提出,目前被普遍應用於數據完整性校驗、數據(消息)摘要、數據簽名等。MD二、MD四、MD5 都產生 16 字節(128 位)的校驗值,通常用 32 位十六進制數表示。MD2 的算法較慢但相對安全,MD4 速度很快,但安全性降低,MD5 比 MD4 更安全、速度更快。
隨着計算機技術的發展和計算水平的不斷提升,MD5 算法暴露出來的漏洞也愈來愈多。1996 年後被證明存在弱點,能夠被加以破解,對於須要高度安全性的數據,專家通常建議改用其餘算法,如 SHA-2。2004 年,證明 MD5 算法沒法防止碰撞(collision),所以不適用於安全性認證,如 SSL 公開密鑰認證或是數字簽名等用途。
128 位的 MD5 散列在大多數狀況下會被表示爲 32 位十六進制數字。如下是一個 43 位長的僅 ASCII 字母列的MD5 散列:
MD5("The quick brown fox jumps over the lazy dog") = 9e107d9d372bb6826bd81d3542a419d6
即便在原文中做一個小變化(好比把 dog 改成 cog,只改變一個字符)其散列也會發生巨大的變化:
MD5("The quick brown fox jumps over the lazy cog") = 1055d3e698d289f2af8663725127bd4b
接着咱們再來舉幾個 MD5 散列的例子:
MD5("") -> d41d8cd98f00b204e9800998ecf8427e MD5("semlinker") -> 688881f1c8aa6ffd3fcec471e0391e4d MD5("kakuqo") -> e18c3c4dd05aef020946e6afbf9e04ef
文件分發防篡改
在互聯網上分發軟件安裝包時,出於安全性考慮,爲了防止軟件被篡改,好比在軟件安裝程序中添加木馬程序。軟件開發者一般會使用消息摘要算法,好比 MD5 算法產生一個與文件匹配的數字指紋,這樣接收者在接收到文件後,就能夠利用一些現成的工具來檢查文件完整性。
消息傳輸防篡改
假設在網絡上你須要發送電子文檔給你的朋友,在文件發送前,先對文檔的內容進行 MD5 運算,得出該電子文檔的 「數字指紋」,並把該 「數字指紋」 隨電子文檔一同發送給對方。當對方接收到電子文檔以後,也使用 MD5 算法對文檔的內容進行哈希運算,在運算完成後也會獲得一個對應 「數字指紋」,當該指紋與你所發送文檔的 「數字指紋」 一致時,表示文檔在傳輸過程當中未被篡改。
哈希碰撞是指不一樣的輸入卻產生了相同的輸出,好的哈希算法,應該沒有人能從中找到 「碰撞」 或者說極度難找到,雖然 「碰撞」 是確定存在的。
2005 年山東大學的王小云教授發佈算法能夠輕易構造 MD5 碰撞實例,此後 2007 年,有國外學者在王小云教授算法的基礎上,提出了更進一步的 MD5 前綴碰撞構造算法 「chosen prefix collision」,此後還有專家陸續提供了MD5 碰撞構造的開源的庫。
2009 年,中國科學院的謝濤和馮登國僅用了 220.96 的碰撞算法複雜度,破解了 MD5 的碰撞抵抗,該攻擊在普通計算機上運行只須要數秒鐘。
MD5 碰撞很容易構造,基於 MD5 來驗證數據完整性已不可靠,考慮到近期谷歌已成功構造了 SHA-1(英語:Secure Hash Algorithm 1,中文名:安全散列算法1)的碰撞實例,對於數據完整性,應使用 SHA256 或更強的算法代替。
其實 MD5 的相關知識還有挺多,好比 MD5 密文反向查詢、密碼加鹽和實現內容資源防盜鏈等。這裏阿寶哥就不繼續展開了,感興趣的小夥伴能夠閱讀阿寶哥以前寫的 」一文讀懂 MD5 算法「 這篇文章。