互联网时代,网络上的数据量天天都在以惊人的速度增加。同时,各种网络安全问题层出不穷。在信息安全重要性日益凸显的今天,做为一名开发者,须要增强对安全的认识,并经过技术手段加强服务的安全性。javascript
crypto
模块是nodejs的核心模块之一,它提供了安全相关的功能,如摘要运算、加密、电子签名等。不少初学者对着长长的API列表,不知如何上手,所以它背后涉及了大量安全领域的知识。html
本文重点讲解API背后的理论知识,主要包括以下内容:java
本文摘录自《Nodejs学习笔记》,更多章节及更新,请访问 github主页地址。
摘要(digest):将长度不固定的消息做为输入,经过运行hash函数,生成固定长度的输出,这段输出就叫作摘要。一般用来验证消息完整、未被篡改。node
摘要运算是不可逆的。也就是说,输入固定的状况下,产生固定的输出。但知道输出的状况下,没法反推出输入。git
伪代码以下。github
digest = Hash(message)
常见的摘要算法 与 对应的输出位数以下:算法
nodejs中的例子:安全
var crypto = require('crypto'); var md5 = crypto.createHash('md5'); var message = 'hello'; var digest = md5.update(message, 'utf8').digest('hex'); console.log(digest); // 输出以下:注意这里是16进制 // 5d41402abc4b2a76b9719d911017c592
备注:在各种文章或文献中,摘要、hash、散列 这几个词常常会混用,致使很多初学者看了一脸懵逼,其实大部分时候指的都是一回事,记住上面对摘要的定义就行了。
MAC(Message Authentication Code):消息认证码,用以保证数据的完整性。运算结果取决于消息自己、秘钥。网络
MAC能够有多种不一样的实现方式,好比HMAC。ide
HMAC(Hash-based Message Authentication Code):能够粗略地理解为带秘钥的hash函数。
nodejs例子以下:
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、RC五、IDEA。
加、解密伪代码:
encryptedText = encrypt(plainText, key); // 加密
plainText = decrypt(encryptedText, key); // 解密
又称公开秘钥加密。加密、解密所用的秘钥是不一样的,即encryptKey !== decryptKey
。
加密秘钥公开,称为公钥。解密秘钥保密,称为秘钥。
常见的非对称加密算法:RSA、DSA、ElGamal。
加、解密伪代码:
encryptedText = encrypt(plainText, publicKey); // 加密
plainText = decrypt(encryptedText, priviteKey); // 解密
除了秘钥的差别,还有运算速度上的差别。一般来讲:
二者能够结合起来使用,好比HTTPS协议,能够在握手阶段,经过RSA来交换生成对称秘钥。在以后的通信阶段,可使用对称加密算法对数据进行加密,秘钥则是握手阶段生成的。
备注:对称秘钥交换不必定经过RSA,还能够经过相似DH来完成,这里不展开。
从签名大体能够猜到数字签名的用途。主要做用以下:
为了达到上述目的,须要有两个过程:
附:签名伪代码
digest = hash(message); // 计算摘要
digitalSignature = sign(digest, priviteKey); // 计算数字签名
附:签名验证伪代码
digest1 = verify(digitalSignature, publicKey); // 获取摘要
digest2 = hash(message); // 计算原始信息的摘要
digest1 === digest2 // 验证是否相等
因为RSA算法的特殊性,加密/解密、签名/验证 看上去特别像,不少同窗都很容易混淆。先记住下面结论,后面有时间再详细介绍。
常见的对称加密算法,如AES、DES都采用了分组加密模式。这其中,有三个关键的概念须要掌握:模式、填充、初始化向量。
搞清楚这三点,才会知道crypto模块对称加密API的参数表明什么含义,出了错知道如何去排查。
所谓的分组加密,就是将(较长的)明文拆分红固定长度的块,而后对拆分的块按照特定的模式进行加密。
常见的分组加密模式有:ECB(不安全)、CBC(最经常使用)、CFB、OFB、CTR等。
以最简单的ECB为例,先将消息拆分红等分的模块,而后利用秘钥进行加密。
图片来源:这里,更多关于分组加密模式的介绍能够参考 wiki。
后面假设每一个块的长度为128位
为了加强算法的安全性,部分分组加密模式(CFB、OFB、CTR)中引入了初始化向量(IV),使得加密的结果随机化。也就是说,对于同一段明文,IV不一样,加密的结果不一样。
以CBC为例,每个数据块,都与前一个加密块进行亦或运算后,再进行加密。对于第一个数据块,则是与IV进行亦或。
IV的大小跟数据块的大小有关(128位),跟秘钥的长度无关。
如图所示,图片来源 这里
分组加密模式须要对长度固定的块进行加密。分组拆分完后,最后一个数据块长度可能小于128位,此时须要进行填充以知足长度要求。
填充方式有多重。常见的填充方式有PKCS7。
假设分组长度为k字节,最后一个分组长度为k-last,能够看到:
01 -- if lth mod k = k-1 02 02 -- if lth mod k = k-2 . . . k k ... k k -- if lth mod k = 0
crypto模块涉及的安全知识较多,篇幅所限,这里没办法一一展开。为了讲解方便,部份内容可能不够严谨,若有错漏敬请指出。
有疑问或感兴趣的同窗欢迎留言交流,也可留意个人github关注最新内容更新《nodejs-learning-guide》。
Hash-based message authentication code
What is the difference between MAC and HMAC?