Crypto介绍
Crypto库是随Nodejs内核一起打包发布的,主要提供了加密、解密、签名、验证等功能。Crypto利用OpenSSL库来实现它的加密技术,它提供OpenSSL中的一系列哈希方法,包括hmac、cipher、decipher、签名和验证等方法的封装。 【官方地址】http://nodejs.cn/api/crypto.html
Hash算法
哈希算法,是指将任意长度的二进制值映射为较短的固定长度的二进制值,这个值称为哈希值
crypto.createHash(algorithm)
创建并返回一个哈希对象。支持的算法可以通过 crypto.getHashes()
查看
hash支持的算法和 AES对称加密 支持的算法是不相同的
hash.update(data[, inputEncoding])
使用 data 更新哈希内容
hash.digest([encoding])
返回 hash 后的数据
实例:
const crypto = require('crypto');
const hash = crypto.createHash('sha256');
hash.update('加密的数据');
console.log(hash.digest('hex'))
// 08688a294e043521d71c7caf0cd1e3689c8cc42e363d875c768f66d1e09c6ca4
Hmac算法
Hmac算法也是一种哈希算法,与Hash算法不同的是,Hmac还需要一个密钥
crypto.createHmac(algorithm, key)
创建并返回一个对象,使用 算法 和 密钥
hmac.update(data[, inputEncoding])
使用 data 更新哈希内容
hmac.digest([encoding])
返回 hmac 后的值
实例:
const crypto = require('crypto');
const hmac = crypto.createHmac('sha256', '秘钥');
hmac.update('加密的数据');
console.log(hmac.digest('hex'))
// f998de608a5f38195d21db861104e5630717f5de13bb5db5a06c659db9fd9725
Salt 加盐加密
在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”
普通的加盐加密
var crypto = require('crypto');
var txt = '原始数据';
var md5 = crypto.createHash('md5');
var salt = 'salt';
md5.update(txt + salt);
console.log(md5.digest('hex')) // fb8e6eb8db9a20e14f21c2287f3e564a
加盐迭代
crypto.pbkdf2(password, salt, iterations, keylen, digest, callback)
异步
crypto.pbkdf2Sync(password, salt, iterations, keylen, digest)
同步
- password 需要加密的原始数据
- salt 盐
- iteratuibs 迭代次数
- ketlen 密文长度,实际产生的密文长度是 ketlen 的2倍
- digest 算法
- callback 回调函数, 有 (err, derivedKey) 两个参数
从给定的password,salt,iterations派生出密钥(key)。
实例(多因素分析性能):
const crypto = require('crypto');
const options = [
{ iterations: 100, keylen: 100, digest: 'sha1'},
{ iterations: 100, keylen: 300, digest: 'sha1'},
{ iterations: 100, keylen: 500, digest: 'sha1'},
{ iterations: 500, keylen: 500, digest: 'sha1'},
{ iterations: 1000, keylen: 500, digest: 'sha1'},
{ iterations: 1500, keylen: 500, digest: 'sha1'},
{ iterations: 100, keylen: 100, digest: 'sha256'},
{ iterations: 100, keylen: 100, digest: 'sha512'}
]
options.forEach((item) => {
var s1 = new Date();
let key = crypto.pbkdf2Sync('加密的数据', 'salt', item.iterations, item.keylen, item.digest).toString('hex');
var s2 = new Date();
console.log('算法:' + item.digest + ', 迭代:' + item.iterations + ', 耗时:' + (s2 - s1) + 'ms, 密文长度:' + key.length + ', 密文:' + key.substr(0, 10) + '...' + key.substr(-10, 10))
})
/*
算法:sha1, 迭代:100, 耗时:1ms, 密文长度:200, 密文:08221a986a...2baeac3331
算法:sha1, 迭代:100, 耗时:0ms, 密文长度:600, 密文:08221a986a...7a92e291ea
算法:sha1, 迭代:100, 耗时:1ms, 密文长度:1000, 密文:08221a986a...d310bba029
算法:sha1, 迭代:500, 耗时:7ms, 密文长度:1000, 密文:7cb0ce5d97...38ba173640
算法:sha1, 迭代:1000, 耗时:14ms, 密文长度:1000, 密文:6b41df18c1...090bae9a3c
算法:sha1, 迭代:1500, 耗时:24ms, 密文长度:1000, 密文:160484efcd...19bac51be5
算法:sha256, 迭代:100, 耗时:0ms, 密文长度:200, 密文:6b22ffdf40...3a72356efe
算法:sha512, 迭代:100, 耗时:0ms, 密文长度:200, 密文:438a26fbf3...57f42f73f0
*/
crypto.randomBytes(size[, callback])
- size 密文长度
- callback 回调函数, 有 (err, buf) 两个参数, buf 为 salt
生成加密强伪随机数据作为 salt 进行加密。将 生成的 salt 和 密文 同时保存到数据库
实例:
const crypto = require('crypto');
for (var i = 0; i < 3; i++) {
crypto.randomBytes(128, function (err, salt) {
if (err) { throw err;}
salt = salt.toString('hex');
console.log('salt:' + salt);
var key = crypto.pbkdf2Sync('加密的数据', salt, 100, 30, 'sha1');
key = key.toString('hex');
console.log('hash:' + key + '\n');
})
}
/*
salt:f5b7e6b88b7f37897b323d81ac814e6062905b7713b08e6fcbe6f5485fc4145338a5de43e1eb608b1ffa08e7b0dc1c05b64d1410bfe41c703e6ca18c62601516ce8ff0e346a779f2b4d524057f5e7f6cae68f59546ff957fc3d7b383
2304df70574e4bdd01a4754ce22ebc13349b172a2e8f54a4b8595e3b102b9a2b7ba8e8ce
hash:849a2e91e36b62c206cdabed526c2fea76fbd344e0e4bdcf2e48cb4ed6b7
salt:5245fcbbbd80bdd5ff3771ab3a8cf226c4f9fdf795f85c24c865c35a57974574f5ca6ca3382db7744fe1b3f5bb3736f4881159ac25734e731c63d773084e4377cef7b2142ce87469418f73e1f1e29f8596245e2237443108b28d511f
56ecfcdbaddf66ba7fb842b4806707307bd06d70226df14e4a15ade0cd94af18cb274787
hash:5834ed9d218ff81a6e28170f8edaff675b2229ce8f0017a6f027cc25b454
salt:52a23d1f12b0156591d683eea15db642503b67c0f934735a089e7be2bfb1894118b0a9d0696314c19b93895b1973a339012340840dcba88eb32ae55e3e3c257dedff61f63c6a39b82647c9c50018ea4f898a3a15ee5ac6cad6c24e9c
6d9179b8ae0b583e65c3179b09a82a0d4296ef93107e9b64f08d44c2a756f6da49c6f360
hash:9983ac42d54802ae2bcd031f49f9fe73f5ce33358d1241217de7091351e7
*/
AES对称加密算法
加密
AES 对称加密算法,加解密都用同一个密钥
crypto.createCipher(algorithm, password)
- algorithm String
- password String | Buffer | TypedArray | DataView
使用 算法 和 密钥🔸创建并返回一个加密对象
算法依赖于OpenSSL, 可以通过 crypto.getCiphers()
查看支持的算法
crypto.createCipheriv(algorithm, key, iv)
- algorithm String
- key String | Buffer | TypedArray | DataView
- iv String | Buffer | TypedArray | DataView
使用 算法、密钥 和 **初始化向量(iv)**创建并返回一个加密对象
不同的IV,用相同的密钥加密相同的数据得到的加密结果也是不同的
cipher.update(data[, inputEncoding][, outputEncoding])
- data 需要加密的数据,有 string、Buffer、TypedArray、DataView
- inputEncoding data 的编码格式,有 utf8、ASCII、latin1等
- outputEncoding 加密数据的输出格式,有 latin1、Base64、hex。注意有些编码会有 Cannot change encoding 报错
用 data 更新加密
cipher.final([outputEncoding])
- outputEncoding 加密数据的输出格式, 需要同
cipher.update()
的 outputEncoding 保持一致
返回完整的加密数据
实例:
const crypto = require('crypto')
const cipher = crypto.createCipher('aes192', '秘钥')
let encrypted = cipher.update('加密的数据', 'utf8', 'hex')
encrypted += cipher.final('hex')
console.log(encrypted) // cb21a071359147ddb0cc6f15c8e5d8b4
解密
crypto.createDecipher(algorithm, password)
使用 算法 和 秘钥🔸创建并返回解密对象
crypto.createDecipheriv(algorithm, key, iv)
- algorithm String
- key String | Buffer | TypedArray | DataView
- iv String | Buffer | TypedArray | DataView
使用 算法、密钥 和 **初始化向量(iv)**创建并返回一个解密对象
decipher.update(data[, inputEncoding][, outputEncoding])
- data 需要解密的数据,有 string、Buffer、TypedArray、DataView
- inputEncoding 加密数据 data 的编码格式。注意需要和
cipher.update()
的 outputEncoding 协同 - outputEncoding 解密后数据的输出格式
用data 更新解密
decipher.final([outputEncoding])
- outputEncoding 解密数据的输出格式
返回原始数据
示例:
const decipher = crypto.createDecipher('aes192', '秘钥')
let decrypted = decipher.update(encrypted, 'hex', 'utf8')
decrypted += decipher.final('utf8')
console.log(decrypted) // 加密的数据
DiffieHellman 密钥交换算法
DH算法是一种密钥交换协议。只能用于密钥的交换,而不能进行消息的加密和解密。双方确定密钥后,要使用其他对称密钥操作加密算法实际加密和解密消息
crypto.createDiffieHellman(prime_length)
通过一个素数的 bit 长度创建DiffieHellman实例
crypto.createDiffieHellman(prime, [encoding])
另外一个素数来生成DiffieHellman实例
diffieHellman.generateKeys([encoding])
根据指定的编码生成私有key和公有key
diffieHellman.getPrime([encoding])
获得实例的素数
diffieHellman.getGenerator([encoding])
非对称加密 和 数字签名及验证
基本流程:
- 通讯服务器拥有 2 把钥匙,私钥保留,公钥公开, 以及 CA证书服务器提供的 数字证书(Digital Certificate) 数字证书由 通讯服务器的信息,算法的信息,通讯服务器的公钥 等信息加密生成
- 用户向通讯服务器发起请求。
- 通讯服务器 先将 数据通过Hash函数生成 摘要(digest), 然后用私钥加密摘要生成 数字签名(signature) 最后将 信息数据 + 数字签名 + 数字证书 发给请求的用户
- 用户 浏览器中有"证书管理器",有"受信任的根证书颁发机构"列表。浏览器会根据这张列表,查看 CA 证书服务器 是否在列表之内 如果 CA证书服务器 受信任,则用 CA 的公钥 解开数字证书拿到 通讯服务器的公钥,再解密 数字签名得到摘要, 最后将 信息数据 通过 Hash函数也生成一个 摘要, 与解密得到的摘要做对比,两者相等则证明数据没有被修改
Encrypt 加密 、 Decrypt 解密
crypto.publicEncrypt(publicKey, buffer)
- publicKey 公钥,只支持 RSA 加密算法
- buffer 需要加密的数据,必须是 buffer、TypedArray、DataView
publicKey 如果是一个字符串,作为没有密码的密钥;如果是一个对象,则包含下面的参数:
- key: 加密的私钥字符串
- passphrase: 私钥密码字符串
- padding: 可选的填充值,以下值之一: constants.RSA_NO_PADDING constants.RSA_PKCS1_PADDING constants.RSA_PKCS1_OAEP_PADDING (默认填充值)
公钥加密
crypto.privateEncrypt(privateKey, buffer)
私钥加密。同 crypto.publicEncrypt()
的API
crypto.publicDecrypt(publicKey, buffer)
公钥解密。同 crypto.publicEncrypt()
的API,默认填充值是RSA_PKCS1_PADDING
crypto.privateDecrypt(privateKey, buffer)
私钥解密。同 crypto.publicEncrypt()
的API,默认填充值是RSA_PKCS1_OAEP_PADDING
注意: 使用不同的加解密方法时,必须使用对应的 公钥或者私钥,不能出现 公钥加密使用的是私钥,否则报错
实例:
var crypto = require('crypto');
var fs = require('fs');
var data = '我是需要加密的数据';
data = Buffer.from(data, 'utf8');
var privatePem = fs.readFileSync('private.pem');
var publicPem = fs.readFileSync('public.pem');
var encrypt = crypto.publicEncrypt(publicPem, data);
var decrypt = crypto.privateDecrypt(privatePem, encrypt);
console.log('加密后的数据:' + '\n' + encrypt.toString('hex') + '\n');
console.log('解密后的数据:' + '\n' + decrypt.toString('utf8') + '\n');
/*
加密后的数据:
0e0dedfee2eca220d9d20142f992e28fa9f2e58bb27d721a86b0c08e4887cddee86a7226c40357997dde5478090b37c2302281948a3387cc8055736c1178be14611487cfbbf65f20ab43a84f6f0f79d6f42f94e34b9381695ceec2f7618ea8ccc3a41cbf39a13caaf9cb847d822890f1f66e35b3315560b6cd2709cb880e6507
解密后的数据:
我是需要加密的数据
*/
Sign 签名
crypto.createSign(algorithm)
- algorithm 算法
使用指定的算法生成数字签名对象(sign)
sign.update(data[, inputEncoding])
- data 需要加密的数据,可以是 string、Buffer、TypedArray、DataView 等数据格式
- inputEncoding 数据编码
用 data 更新sign对象。可以多次使用
sign.sign(privateKey[, outputFormat])
- privateKey 可以是一个对象或一个字符串,如果是一个字符串,那么它被当做没有密码的密钥;如果是对象,则是: key: 私钥 passphrase: 私钥密码
- outputFormat 密钥编码
根据所有通过update方法传入的数据计算数字签名
Verify 验证
crypto.createVerify(algorithm)
使用指定的算法生成验证数字签名的对象(verify)
verify.update(data[, inputEncoding])
- data 需要加密的数据,可以是 string、Buffer、TypedArray、DataView 等数据格式
- inputEncoding 数据编码
用 data 更新verify对象。可以多次使用
verify.verify(object, signature[, signatureFormat])
- object PEM编码对象的字符串,可以是RSA公钥,DSA公钥或X.509证书
- signature 计算出来的数字签名
- signature_format 'binary', 'hex'或 'base64'。如果没有指定编码,那么需要是 buffer
通过object和signature验证被签名的数据和公钥的有效性
实例:
var crypto = require('crypto');
var fs = require('fs');
function signer (algorithm, key, data) {
var sign = crypto.createSign(algorithm);
sign.update(data);
sig = sign.sign(key, 'hex');
return sig;
}
function verify (algorithm, pub, sig, data) {
var verify = crypto.createVerify(algorithm);
verify.update(data);
return verify.verify(pubkey, sig, 'hex');
}
// 需要加密的数据
var data = 'abcdef';
// 算法
var algorithm = 'RSA-SHA256';
// 私钥
var privatePem = fs.readFileSync('private.pem');
var key = privatePem.toString();
// 公钥
var publicPem = fs.readFileSync('public.pem');
var pubkey = publicPem.toString();
// 数字签名
var _sign = signer(algorithm, key, data);
// 验证
var _verify1 = verify(algorithm, pubkey, sig, data);
// 更改原始数据验证
var _verify2 = verify(algorithm, pubkey, sig, data + '1234');
console.log(_sign); //81e51d0eb359......a1adc22f
console.log(_verify1, _verify2); // true, false
如何生成公钥私钥,详见 operassl 创建密钥和证书
一个完整的利用对称加密和非对称加密的示例
实现步骤:
- 先用
crypto.randomBytes()
生成强随机数,作为密钥key - 再将上一步生成的 key(密钥) 和对称加密算法
crypto.createCipher()
加密 data - 再用非对称加密算法
crypto.publicEncrypt()
加密第一步生成的 key(密钥) - 然后将 加密后的 key(密钥) 和 加密后的data 传递
- 服务端接收数据后。先用非对称加密算法
crypto.privateDecrypt()
解密 key(密钥) - 再将上一步解密出来的 key(密钥)通过对称加密算法
crypto.createDecipher()
解密出 data
实例:
var crypto = require('crypto');
var fs = require('fs');
var data = '我是需要加密的数据';
var privatePem = fs.readFileSync('private.pem');
var publicPem = fs.readFileSync('public.pem');
crypto.randomBytes(50, (err, key) => {
if (err) throw err
key = key.toString('hex')
// 原始密钥:0b1aa37aba8ddb0a0e61e5bb4548a32eade97a794fa5da0b9ccdc877adbb76121e73d712fc4eda2b57c5045d371464123956
const cipher = crypto.createCipher('aes192', key)
let encrypted = cipher.update(data, 'utf8', 'hex')
encrypted += cipher.final('hex')
// 加密后的数据:03caa794db58137d431f594ad48f95f610b9dced613845c749009958651d53b4
key = Buffer.from(key, 'hex')
var encrypt = crypto.publicEncrypt(publicPem, key)
// 加密后的密钥:06884dbd48b58e98c29052f88c7abb6143561b9dd1c6f8c7f6548129d116abe720bb5abd520bf6a75ded76e6dfdebc6ff7eaca76bf10b7dc10446a3277a23c463651cc6339ab693d19203c7e081988b3bceba33f2d6f86ff32b3fa6b656a4d3a30e65127e337fc9a7ec2312774962d335214f16b80c402d74f9f533d2c48f743
encrypt = Buffer.from(encrypt, 'hex')
var decrypt = crypto.privateDecrypt(privatePem, encrypt)
decrypt = decrypt.toString('hex')
// 解密后的密钥:0b1aa37aba8ddb0a0e61e5bb4548a32eade97a794fa5da0b9ccdc877adbb76121e73d712fc4eda2b57c5045d371464123956
const decipher = crypto.createDecipher('aes192', decrypt)
let decrypted = decipher.update(encrypted, 'hex', 'utf8')
decrypted += decipher.final('utf8')
// 解密后的数据:我是需要加密的数据
})