Appearance
crypto使用示例
crypto模块提供了加密功能,包含哈希、HMAC、加密、解密、签名、以及验证功能的一整套封装。
说明:文档中涉及的明文密钥举例方式,均为举例方便而为之。实际产品代码开发时,请不要将密钥,银行账号,邮箱等敏感信息直接硬编码在代码中。
哈希算法
哈希算法,是指将任意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。如果散列一段明文而且哪怕只更改该段落的一个字母,随后的哈希都将产生不同的值。要找到散列为同一个值的两个不同的输入,在计算上是不可能的,所以数据的哈希值可以检验数据的完整性。一般用于快速查找和加密算法。
通常我们对登陆密码,都是使用Hash算法进行加密,典型的哈希算法包括 md5,sha1,sha256,sha512。
ts
import * as crypto from 'crypto';
import * as buffer from 'buffer';
let hash = crypto.createHash(crypto.Hashs.SHA512);
console.log(hash.sum(buffer.from('1xxxxxxxxxxx1')).toString(buffer.Encoding.Base64));
Hmac算法
HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code),HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。HMAC可以有效防止一些类似md5的彩虹表等攻击,比如一些常见的密码直接MD5存入数据库的,可能被反向破解。
定义HMAC需要一个加密用散列函数(表示为H,可以是MD5或者SHA-1)和一个密钥K。我们用B来表示数据块的字节数。(以上所提到的散列函数的分割数据块字长B=64),用L来表示散列函数的输出数据字节数(MD5中L=16,SHA-1中L=20)。鉴别密钥的长度可以是小于等于数据块字长的任何正整数值。应用程序中使用的密钥长度若是比B大,则首先用使用散列函数H作用于它,然后用H输出的L长度字符串作为在HMAC中实际使用的密钥。一般情况下,推荐的最小密钥K长度是L个字节。
ts
import * as crypto from 'crypto';
import * as buffer from 'buffer';
let hmac = crypto.createHmac(crypto.Hashs.SHA256, buffer.from('2xxxxxxxxxxx2'));
console.log(hmac.sum(buffer.from('1xxxxxxxxxxx1')).toString(buffer.Encoding.Base64));
加密和解密算法
对于登陆密码来说,是不需要考虑解密的,通常都会用不可逆的算法,像md5,sha-1等。但是,对于有安全性要求的数据来说,我们是需要加密存储,然后解密使用的,这时需要用到可逆的加密算法。对于这种基于KEY算法,可以分为对称加密和不对称加密。
对称加密算法的原理很容易理解,通信一方用KEK加密明文,另一方收到之后用同样的KEY来解密就可以得到明文。 不对称加密算法,使用两把完全不同但又是完全匹配的一对Key:公钥和私钥。在使用不对称加密算法加密文件时,只有使用匹配的一对公钥和私钥,才能完成对明文的加密和解密过程。
对称算法
ts
import * as crypto from 'crypto';
import * as buffer from 'buffer';
let key = buffer.from('paaswordpaasword');
let iv = buffer.from('paaswordpassword');
let palinText = buffer.from('huawei');
let cipher = crypto.createCipher(crypto.Algorithm.AES_CBC, key, {
iv: iv,
padding: crypto.Padding.PKCS7,
});
let chipherText = cipher.encrypt(palinText);
console.log(chipherText.toString(buffer.Encoding.Base64));
let decipher = crypto.createDecipher(crypto.Algorithm.AES_CBC, key, {
iv: iv,
padding: crypto.Padding.PKCS7,
});
let decrypted = decipher.decrypt(chipherText);
console.log(decrypted.toString(buffer.Encoding.Utf8));
非对称算法
ts
import * as crypto from 'crypto';
import * as buffer from 'buffer';
let rsakey = crypto.generateKey(1024);
let data = buffer.from('huawei');
let cipherText = crypto.publicEncrypt(rsakey.publicKey, data);
console.log(cipherText.toString(buffer.Encoding.Base64));
let plainText = crypto.privateDecrypt(rsakey.privateKey, cipherText);
console.log(plainText.toString());
RSA分段加解密
ts
import * as crypto from 'crypto';
import * as buffer from 'buffer';
const privateKey = `-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----`;
const cipherText = `...`;
let cipherBytes = buffer.from(cipherText, buffer.Encoding.Base64);
let inputLen = cipherBytes.size();
let keyBuf = buffer.from(privateKey);
let offset = 0;
let plainText = "";
for (let i = 0; inputLen - offset > 0; offset += 128) {
let size = inputLen - offset >= 128 ? 128 : inputLen - offset;
let buf = cipherBytes.slice(offset, offset + size)
console.log("current buffer=", buf.toString(buffer.Encoding.Base64));
plainText += crypto.privateDecrypt(keyBuf, buf, crypto.RSAPadding.PKCS1).toString();
console.log(plainText);
}
console.log(plainText);
签名与验证
我们除了对数据进行加密和解密,还需要判断数据在传输过程中,是否真实际和完整,是否被篡改了。那么就需要用到签名和验证的算法,利用不对称加密算法,通过私钥进行数字签名,公钥验证数据的真实性。
数字签名的制作和验证过程,如下图所示。
我们使用Singer与Verify对象来完成签名与验证。
ts
import * as crypto from 'crypto';
import * as buffer from 'buffer';
let rsakey = crypto.generateKey(1024);
let data = buffer.from('huawei');
let sign = crypto.createSign(crypto.Hashs.SHA256);
let signed = sign.sign(rsakey.privateKey, data);
console.log('signed', signed.toString(buffer.Encoding.Base64));
let verify = crypto.createVerify(crypto.Hashs.SHA256);
console.log(verify.verify(rsakey.publicKey, signed, data));
salt算法
我们知道,如果直接对密码进行散列,那么黑客可以对通过获得这个密码散列值,然后通过查散列值字典(例如MD5密码破解网站),得到某用户的密码。 盐(Salt),在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”。
加盐后的散列值,可以极大的降低由于用户数据被盗而带来的密码泄漏风险,即使通过彩虹表寻找到了散列后的数值所对应的原始内容,但是由于经过了加盐, 插入的字符串扰乱了真正的密码,使得获得真实密码的概率大大降低。
加盐的实现过程通常是在需要散列的字段的特定位置增加特定的字符,打乱原始的字串,使其生成的散列结果产生变化。比如,用户使用了一个密码:123465, 经过MD5散列后,可以得出结果:
3d9188577cc9bfe9291ac66b5cc872b7
但是由于用户密码位数不足,短密码的散列结果很容易被彩虹表破解,因此,在用户的密码末尾添加特定字串: 123465abcdefghijklmnopqrstuvwxyz
因此,加盐后的密码位数更长了,散列的结果也发生了变化: 27e20c64ccb8cce9ad68b8ccff6252cf
ts
// salt算法
import * as crypto from 'crypto';
import * as buffer from 'buffer';
let md5 = crypto.createHash(crypto.Hashs.MD5);
let txt = buffer.from('1xxxxx5');
console.log(md5.sum(txt).toString(buffer.Encoding.Hex));
md5 = crypto.createHash(crypto.Hashs.MD5);
let salt = 'abcdefghijklmnopqrstuvwxyz';
console.log(md5.sum(buffer.from(txt + salt)).toString(buffer.Encoding.Hex));
我们可以不用自己动手加盐,而使用crypto.pbkdf2函数,默认会调用hmac算法,用散列函数,并且可以设置迭代次数和密文长度。
randomBytes
生成安全的随机字符串
ts
import * as crypto from 'crypto';
import * as buffer from 'buffer';
let rand = crypto.randomBytes(32);
console.log(rand.toString(buffer.Encoding.Base64));
pbkdf2
ts
import * as crypto from 'crypto';
import * as buffer from 'buffer';
let password = buffer.from('1xxxxxxxxxxx');
let salt = buffer.from('1xxxxxxxxxxx1');
let crypt = crypto.pbkdf2(password, salt, 1000, KEY_LEN, crypto.Hashs.SHA1);
console.log(crypt.toString(buffer.Encoding.Base64));