Node.js 中的 Crypto 加密模块
摘要(digest):将长度不固定的消息作为输入,通过运行 hash 函数,生成固定长度的输出,这段输出就叫做摘要。通常用来验证消息完整、未被篡改。
摘要运算是不可逆的。也就是说,输入固定的情况下,产生固定的输出。但指导输出的情况下,无法反推出输入。
digest = Hash(message)
常见的摘要算法与对应的输出位数如下:
示例:
const crypto = require('crypto');const md5 = crypto.createHash('md5');const message = 'hello';const digest = md5.update(message, 'utf-8').digest('hex');console.log(digest);// 输出如下:注意这里是 16 进制//
MAC(Message Authentication Code):消息认证码,用以保证数据的完整性。运算结果取决于消息本身、密钥。
MAC 可以有多种不同的实现方式,比如 HMAC。
HMAC(Hash-based Message Authentication Code):可以粗略地理解位带密钥的 hash 函数。
示例:
const crypto = require('crypto');// 参数一:摘要函数// 参数二:秘钥let hmac = crypto.createHmac('md5', '123456');let ret = hmac.update('hello').digest('hex');console.log(ret);// 9c699d7af73a49247a239cb0dd2f8139
加密/解密:给定明文,通过一定的算法,产生加密后的密文,这个过程叫加密。反过来就是解密。
encryptedText = encrypt( plainText ) plainText = decrypt( encryptedText )
秘钥:为了进一步增强加/解密算法的安全性,在加/解密的过程中引入了秘钥。秘钥可以视为加/解密算法的参数,在已知密文的情况下,如果不知道解密所用的秘钥,则无法将密文解开。
encryptedText = encrypt(plainText, encryptKey) plainText = decrypt(encryptedText, decryptKey)
根据加密、解密所用的秘钥是否相同,可以将加密算法分为对称加密、非对称加密。
加密、解密所用的密钥是相同的,即 encryptKey === decryptKey
。
常见的对称加密算法:DES、3DES、AES、Blowfish、RC5、IDEA。
示例: 加解密
// 加密const encryptedText = encrypt(plainText, key);// 解密const plainText = decrypt(encryptedText, key);
又称公开秘钥加密。加密、解密所用的秘钥是不同的,即 encryptKey !== decryptKey
。
加密秘钥公开,称为公钥。解密秘钥保密,称为秘钥。
常见的非对称加密算法:RSA、DSA、ElGamal。
示例:
// 加密const encryptedText = encrypt(plaintText, publicKey);// 解密const plainText = decrypt(encryptedText, priviteKey);
除了秘钥的差异,还有运算速度上的差异。通常来说:
两者可以结合起来使用,比如 HTTPS 协议,可以在握手阶段,通过 RSA 来交换生成对称秘钥。在之后的通讯阶段,可以使用对称加密算法对数据进行加密,秘钥则是握手阶段生成的。
从签名大致可以猜到数字签名的用途。主要作用如下:
为了达到上述目的,需要有两个过程:
示例:
// 计算摘要const digest = hash(message);// 计算数字签名const digitalSignature = sign(digest, priviteKey);
由于 RSA 算法的特殊性,加密/解密、签名/验证 看上去特别像,很多同学都很容易混淆。先记住下面结论,后面有时间再详细介绍。
常见的对称加密算法,如 AES、DES 都采用了分组加密模式。这其中,有三个关键的概念需要掌握:模式、填充、初始化向量。 搞清楚这三点,才会知道 crypto 模块对称加密 API 的参数代表什么含义,出了错知道如何去排查。
所谓的分组加密,就是将(较长的)明文拆分成固定长度的块,然后对拆分的快按照特定的模式进行加密。
常见的分组加密模式:ECB(不安全)、CBC(最常用)、CFB、OFB、CTR 等
以最简单的 ECB 为例,先将消息拆分成等分的模块,然后利用密钥进行加密。
为了增强算法的安全性,部分分组加密模式(CFB、OFB、CTR)中引入了初始化向量(IV),使得加密的结果随机化。也就是说,对于同一段明文,IV 不同,加密的结果不同。
以 CBC 为例,每一个数据块,都与前一个加密块进行亦或运算后,再进行加密。对于第一个数据块,则是与 IV 进行亦或。
IV 的大小跟数据块的大小有关(128 位),跟秘钥的长度无关。
分组加密模式需要对长度固定的块进行加密。分组拆分完后,最后一个数据块长度可能小于 128 位,此时需要进行填充以满足长度要求。
填充方式有多重。常见的填充方式有 PKCS7。
假设分组长度为 k 字节,最后一个分组长度为 k-last,可以看到:
01 -- if lth mod k = k-102 02 -- if lth mod k = k-2...k k ... k k -- if lth mod k = 0
概括来说