数据加密,是一门历史悠久的技术,指经过加密算法和加密密钥将明文转变为密文,而解密则是经过解密算法和解密密钥将密文恢复为明文。它的核心是密码学。数据加密还是计算机系统对信息进行保护的一种最可靠的办法。它利用密码技术对信息进行加密,实现信息隐蔽,从而起到保护信息的安全的做用。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 算法“ 这篇文章。