简介
crypto
模块提供了加密功能,实现了包括对 OpenSSL
的哈希、HMAC
、加密、解密、签名、以及验证功能的一整套封装。
Hash(计算数据的哈希值)
Hash
类是用于创建数据哈希值的工具类。
主要用来生成数据的哈希值
Hash
类是一个可读写的Stream
;
可以使用hash.update()
将计算数据以流的方式写入;
流输入结束,使用hash.digest()
方法计算数据的hash
值。
方法 | 作用 | 参数 |
---|---|---|
crypto.getHashes() |
查看 crypto 模块支持的 hash 函数 |
|
crypto.createHash(algorithm) |
创建一个Hash 类 |
参数algorithm 取决与平台上所安装的 OpenSSL 版本所支持的算法,如:'sha1' 、'md5' 、'sha256' 、'sha512' 等 |
hash.update(data[, input_encoding]) |
将计算数据以流的方式写入 | data :数据;input_encoding :编码方式,有'utf8' 、'ascii' 、'binary' ,未设置时,默认编码方式是'binary' 。如果接收的data 数据是Buffer ,input_encoding 将会被忽略 |
hash.digest([encoding]) |
计算传入的数据的hash 值 |
encoding 可以是'hex' 、'binary' 、'base64' ,如果没有指定encoding ,将返回 buffer |
注意:
- 调用
digest()
后不能再用hash
对象
// const crypto = require('crypto')
// // 创建哈希函数 sha256
// const hash = crypto.createHash('sha256')
// const str = hash.update('some data to hash').digest('hex').toUpperCase()
// console.log(str)
// 加盐salt
const crypto = require('crypto')
// 创建哈希函数 md5
const hash = crypto.createHash('md5')
const password = '123456';
const salt = 'hhug6dcKyCNBQ5sUC0i6hja5dCTqdSzV'; // 盐值
const str = hash.update(password + salt).digest('hex').toUpperCase()
console.log(str)
- 可以使用
crypto
模块为哈希函数生成随机的盐值
const crypto = require('crypto');
// 1. 异步方法
crypto.randomBytes(32, (err, salt) => {
if (err) throw err
// 记录盐值可读的字符串版本
console.log('salt: ' + salt.toString('hex'))
// 后续步骤:使用盐值
});
// 2. 同步方法
const buf = crypto.randomBytes(256)
Hmac(计算哈希密钥)
Hmac
类是用于创建加密 Hmac
摘要的工具。可以把Hmac
理解为用随机数“增强”的哈希算法
Hmac
是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code
)
Hmac
运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出
方法 | 作用 | 参数 |
---|---|---|
crypto.createHmac(algorithm, key) |
创建一个Hmac 类 |
参数algorithm 取决与平台上所安装的 OpenSSL 版本所支持的算法,如:'sha1' 、'md5' 、'sha256' 、'sha512' 等 |
hmac.update(data) |
将计算数据以流的方式写入 | data 数据,由于其本身也是个流对象,所以这个方法可以被多次调用 |
hmac.digest([encoding]) |
计算传入的数据的hmac 值 |
encoding 可以是'hex' 、'binary' 、'base64' ,如果没有指定encoding ,将返回 buffer |
注意:
- 调用
digest()
后不能再用hmac
对象
// const crypto = require('crypto')
// const key = 'BDvDYUmfdykkBLgX'
// const hmac = crypto.createHmac('sha1', key.toString('ascii'))
// hmac.update('data to crypt')
// console.log(hmac.digest('hex'))
const crypto = require('crypto')
const secret = 'abcdefg'
const hash = crypto.createHmac('sha256', secret).update('I love cupcakes').digest('hex')
console.log(hash)
Cipher(加密数据)、Decipher(解密数据)
Cipher
类不是计算数据哈希值及密钥,而对数据本身进行加密的
是一种常用的对称加密算法,加解密都用同一个密钥
创建Cipher
类可以crypto.createCipher()
和crypto.createCipheriv()
两个方法。
创建Decipher
类也有两个对应的镜像方法分别是:crypto.createDecipher()
和crypto.createDecipheriv()
。
OpenSSL
推荐使用pbkdf2来替换EVP_BytesToKey
,因此在创建Cipher
类时,建议使用crypto.pbkdf2
来派生key
和iv
,并使用createCipheriv()
来创建加密流
Cipher
加密对象是一个可读写的Stream
流。
可以使用Cipher
类中的update
方法写入纯文本的数据,
数据输入完成后通过final
方法返回加密后的数据。
Decipher
解密对象是一个可读写的Stream
流。
可以使用Decipher
类中的update
方法写入需要解密的数据,
数据输入完成后通过final
方法返回解密后的数据。
crypto.createCipher(algorithm, password)
:用给定的算法和密钥,创建并返回一个Cipher
加密算法的对象。参数:algorithm
算法是依赖OpenSSL
库支持的算法, 例如:'aes192'
算法等,password
是用来派生key
和iv
的,它必须是一个'binary'
二进制格式的字符串或者是一个Buffer
。
crypto.createCipheriv(algorithm, key, iv)
:用给定的算法、密钥和向量,创建并返回一个Cipher
加密算法的对象。参数:algorithm
与createCipher
方法相同,key
密钥是一个被算法使用的原始密钥,iv
是一个初始化向量。key
密钥和iv
向量必须是'binary'
二进制格式的字符串或者是一个Buffer
。
cipher.update(data, [input_encoding], [output_encoding])
:更新Cipher
类的加密数据。data
:要更新的Cipher
加密对象的数据,编码input_encoding
可以是:'utf8'
、'ascii'
、'binary'
。如果没有编码参数,那么data
必须是一个Buffer
。参数output_encoding
指定加密数据的输出编码,可以是:'binary'
、'base64'
或'hex'
,如果未设置这个参数,将会返回一个Buffer
。
cipher.final([output_encoding])
:返回加密后的内容,output_encoding
为:'binary'
、'base64'
或'hex'
。 如果没有提供编码格式,如果未设置这个参数,将会返回一个Buffer
。
cipher.setAutoPadding(auto_padding=true)
:设置输入数据自动填充到块大小功能,这个函数必须在cipher.final
之前调用。如果auto_padding
是false
,那么整个输入数据的长度必须是加密器的块大小的整倍数,否则final
会失败。这对非标准的填充很有用,例如:使用0x0
而不是PKCS
的填充。
cipher.getAuthTag()
:加密认证模式(目前支持:GCM
),这个方法返回经过计算的认证标志Buffer
。必须在使用final
方法完成加密后调用。
crypto.createDecipher(algorithm, password)
:根据给定的算法和密钥,创建并返回一个Decipher
解密对象。
crypto.createDecipheriv(algorithm, key, iv)
:根据给定的算法,密钥和初始化向量,创建并返回一个Decipher
解密对象。
decipher.update(data, [input_encoding], [output_encoding])
:更新Decipher
类解密数据。data
:要更新的Decipher
解密对象的数据,编码input_encoding
可以是:'binary'
、'base64'
或'hex'
。如果没有编码参数,那么data
必须是一个Buffer
。output_encoding
指定解密数据的输出编码,可以是:'utf8'
、'ascii'
、'binary'
,如果未设置这个参数,将会返回一个Buffer
。
decipher.final([output_encoding])
:返回解密后的内容,output_encoding
为:'utf8'
、'ascii'
、'binary'
。 如果没有提供编码格式,如果未设置这个参数,将会返回一个Buffer
。
decipher.setAutoPadding(auto_padding=true)
:如果数据以非标准的块填充方式被加密,那么你可以禁用自动填充来防止decipher.final
对数据进行检查和移除。这只有在输入数据的长度是加密器块大小的整倍数时才有效。这个方法必须在数据流传给decipher.update
之前调用。
decipher.setAuthTag(tag)
:对于加密认证模式(目前支持:GCM
),必须用这个方法来传递接收到的认证标志。如果没有提供标志或者密文被篡改,将会抛出final
标志,认证失败,密文会被抛弃。
注意:
- 调用
final()
后不能再用Cipher
对象- 调用
digest()
后不能再用Decipher
对象
// aes-256-gcm 示例
// 使用GCM,CCM和OCB时需要AuthTag
const crypto = require('crypto');
const options = {
key: crypto.randomBytes(32), // 32位的共享密钥
iv: crypto.randomBytes(16), // 初始向量,16 字节
algorithm: 'aes-256-gcm', // 加密算法和操作模式 aes-256-ecb等
clearEncoding: 'utf8',
cipherEncoding: 'base64'
}
let tag
const encryption = data => {
// 初始化加密算法
const cipher = crypto.createCipheriv(options.algorithm, options.key, options.iv)
cipher.setAutoPadding(true)
let encrypted = cipher.update(data, options.clearEncoding, options.cipherEncoding)
encrypted += cipher.final(options.cipherEncoding)
// 使用GCM,CCM和OCB时需要AuthTag
tag = cipher.getAuthTag()
return encrypted
}
const decryption = data => {
// 初始化解密算法
const decipher = crypto.createDecipheriv(options.algorithm, options.key, options.iv)
// 传入验证标签,验证密文的来源
decipher.setAuthTag(tag)
decipher.setAutoPadding(true)
let decrypted = decipher.update(data, options.cipherEncoding, options.clearEncoding)
decrypted += decipher.final(options.clearEncoding)
return decrypted
}
// 要加密的数据
const text = 'Encryption Testing AES GCM mode'
const str = encryption(text)
console.log(str)
console.log(decryption(str))
// aes-256-gcm 示例二
const crypto = require('crypto');
const aes256gcm = key => {
// 加密算法和操作模式
const ALGO = 'aes-256-gcm';
// encrypt returns base64-encoded ciphertext
const encrypt = str => {
// Hint: the 'iv' should be unique (but not necessarily random).
// 'randomBytes' here are (relatively) slow but convenient for demonstration
const iv = new Buffer.from(crypto.randomBytes(16), 'utf8'); // 初始向量,16 字节
const cipher = crypto.createCipheriv(ALGO, key, iv); // 初始化加密算法
// Hint: Larger inputs (it's GCM, after all!) should use the stream API
let enc = cipher.update(str, 'utf8', 'base64');
enc += cipher.final('base64');
return [ enc, iv, cipher.getAuthTag() ];
};
// decrypt decodes base64-encoded ciphertext into a utf8-encoded string
const decrpt = (enc, iv, authTag) => {
const decipher = crypto.createDecipheriv(ALGO, key, iv);
decipher.setAuthTag(authTag);
let str = decipher.update(enc, 'base64', 'utf8');
str += decipher.final('utf8');
return str;
};
return { encrypt, decrpt };
};
// 加密密钥
// Note: a key size of 16 bytes will use AES-128, 24 => AES-192, 32 => AES-256
const KEY = new Buffer.from(crypto.randomBytes(32), 'utf8');
const aesCipher = aes256gcm(KEY);
// 加密
const [ encrypted, iv, authTag ] = aesCipher.encrypt('hello world');
// 解密
const decrypted = aesCipher.decrpt(encrypted, iv, authTag);
console.log(decrypted); // hello world
// aes-256-ecb 示例
// iv: ''
const crypto = require('crypto');
const options = {
key: crypto.randomBytes(32), // 32位的共享密钥
iv: '', // 初始向量
algorithm: 'aes-256-ecb', // 加密算法和操作模式
clearEncoding: 'utf8',
cipherEncoding: 'base64'
}
const encryption = data => {
// 初始化加密算法
const cipher = crypto.createCipheriv(options.algorithm, options.key, options.iv)
cipher.setAutoPadding(true)
let encrypted = cipher.update(data, options.clearEncoding, options.cipherEncoding)
encrypted += cipher.final(options.cipherEncoding)
return encrypted
}
const decryption = data => {
// 初始化解密算法
const decipher = crypto.createDecipheriv(options.algorithm, options.key, options.iv)
decipher.setAutoPadding(true)
let decrypted = decipher.update(data, options.cipherEncoding, options.clearEncoding)
decrypted += decipher.final(options.clearEncoding)
return decrypted
}
// 要加密的数据
const text = 'Encryption Testing AES GCM mode'
const str = encryption(text)
console.log(str)
console.log(decryption(str))
Sign(生成数字签名)、Verify(验证数字签名)
在网络中传输的数据,除可使用Cipher
类进行数据加密外,还可以对数据生成数字签名,以防止在传输过程中对数据进行修改。
Sign
签名对象是一个可读写的Stream
流。
可以使用Sign
类中的update
方法写入需要签名的数据,
数据输入完成后通过sign
方法返回数据的数字签名。
Verify
验证对象也是一个可读写的Stream
流。
被写入的数据将会被用来验证提供的数字签名,
在所有的数据被写入后,如果提供的数字签名有效,verify
函数会返回真。
crypto.createSign(algorithm)
:根据传入的算法创建并返回一个Sign
数据签名对象。algorithm
为OpenSSL
中支持的算法,比如:'RSA-SHA256'
。
sign.update(data)
:更新Sign
类的签名数据,data
为要更新的数据,由于Sign
是一个可读写的流,此方法可以被多次调用。
sign.sign(private_key, [output_format])
:根据传送给Sign
的数据来计算数字签名,private_key
可以是一个对象或是字符串。如果为字符串时,则是一个包含了签名私钥的字符串,该私钥用的是PEM
编码的。如是为对象时,对象如下:{key
: ‘包含 PEM 编码的私钥’,passphrase
: ‘私钥的密码’ };output_format
:返回值的编码格式, 格式可以是'binary'
、'hex'
、'base64'
。如果没有设置 ,将返回Buffer
。
crypto.createVerify(algorithm)
:根据传入的算法创建并返回一个Verify
签名验证对象。algorithm
为OpenSSL
中支持的算法,比如:'RSA-SHA256'
。createVerify()
方法为createSign()
方法的镜像方法。
verify.update(data)
:更新验证对象的数据,参数data
为验证对象的更新数据。因为其本身是一个流,所以可以被多次调用。
verifier.verify(object, signature, [signature_format])
:验证被签名的数据是否正确。参数object
是包含了PEM
编码对象的字符串,它可以是:RSA
公钥、DSA
公钥、X.509
证书。signature
是之前计算出来的数字签名,signature_format
可以是'binary'
、'hex'
、'base64'
,如果没有指定编码方式则默认是Buffer
对象。
注意:
- 调用
sign()
后不能再使用sign
对象verifier
对象不能在verify()
方法之后调用
rsa_public_key.pem
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGp/2IzCUcjbZg6Y1rJzthI48r7EyeRonRS3E10WH1mU6mA0jS6eoam0KYSyibYChNzTdBFb5Rm/QJtQiy5kqEwxcvbIBplyUWStoptrh4A4gku9N65NbEBluzrLOW3ttM01Km5wXVRBRyXZcynWkJ+3vhBEpJg/ycYz6AAVEwXQIDAQAB
-----END PUBLIC KEY-----
rsa_private_key.pem
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMan/YjMJRyNtmDpjWsnO2EjjyvsTJ5GidFLcTXRYfWZTqYDSNLp6hqbQphLKJtgKE3NN0EVvlGb9Am1CLLmSoTDFy9sgGmXJRZK2im2uHgDiCS703rk1sQGW7Oss5be20zTUqbnBdVEFHJdlzKdaQn7e+EESkmD/JxjPoABUTBdAgMBAAECgYB7UlYF0hVHwIFzcAkmd9hY2SZL8gkuSEPN9bN14WGagW1dibRvml6F3dRdjmrK6cqbYcXnVYQsTVAVppib1nJzF5PsodMSQUaIIP5GUsINZfjRxZHLko6rRHAqqBMJwzA2GLssyc4Ox+0ljzSroEStpKx3TFKZHorti0vNe4SsgQJBAPL4Rnj7haq3M6A3cWjpTysoxYsIzOuv5GfFZ3KkkudFcRF0uscU6jDAtstd1B79Ol8WoaacFsTFNjsl4pCmdWUCQQDRT1W/mJj90QUGqm0XNsgkuSDUmSN85o2/loetBdEpLqes6vNeESG0yl9yv4FZCth8vnlTmcbQARUfJwhDn/uZAkEAhzcUQQ/4+2CpImi4fKIapPIzvYRQRnnEqtt5Dpv4BSzoF8bWiyRgkHEvSU4WVoimi3SU0ZvcL/VwkMospEN+4QJAVCzHm0nPHSQWFVwsiw1o5/vbjCQZ9XzyvH3ZCmgweZNds1i5jrbtCzvnrsn9RsXp0iD3wfsxzSziRaj41dlc4QJAGbpFc24Db1jLE0F4hA3ol9ULqbZhG7S0tcxu+KbQz1OmWR0aLMKAXnt+AiIdJzxCaObVBzzLm2PHRjwWHflsVg==
-----END PRIVATE KEY-----
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const options = {
algorithm: 'RSA-SHA256',
encoding: 'hex',
privateKey: fs.readFileSync(path.join(__dirname, './rsa_private_key.pem')).toString('ascii'),
publicKey: fs.readFileSync(path.join(__dirname, './rsa_public_key.pem')).toString('ascii')
// privateKey: fs.readFileSync('./rsa/ca.key').toString(),
// publicKey: fs.readFileSync('./rsa/ca.crt').toString()
}
const getSign = data => {
// 创建签名算法
const sign = crypto.createSign(options.algorithm)
sign.update(data)
return sign.sign(options.privateKey, options.encoding)
}
const verifySign = (data, result) => {
// 创建验证算法
const verify = crypto.createVerify('RSA-SHA256')
verify.update(data)
return verify.verify(options.publicKey, result, options.encoding)
}
const data = 'data to sign'
const result = getSign(data)
console.log(result)
console.log(verifySign(data, result))
使用
OpenSSL
生成2048
位RSA
密钥
- 生成
RSA
私钥:openssl genrsa -out mykey.pem 2048
- 从密钥对中分离出公钥:
openssl rsa -in mykey.pem -pubout -out mypubkey.pem
使用
OpenSSL
生成的RSA
私钥和公钥
- 生成
RSA
私钥:openssl genrsa -out ca.key
- 使用私钥创建根证书,即:公钥。
openssl req -new -x509 -days 36500 -key ca.key -out ca.crt -subj "/C=CN/ST=BEIJING/L=BEIJING/O=Your Company Name/OU=Your Root CA"
RSA 加密/解密
RSA
算法是一种非对称加密算法,即由一个私钥和一个公钥构成的密钥对,通过私钥加密,公钥解密,或者通过公钥加密,私钥解密。
其中,公钥可以公开,私钥必须保密。
node-rsa
- 纯粹的
JavaScript
- 不需要
OpenSSL
- 生成密钥
- 支持加密/解密的长消息
- 签署和验证
-
下载
node-rsa
:npm i node-rsa --save
-
创建实例
var NodeRSA = require(' node-rsa ');
var key = new NodeRSA([ keyData, [ format ] ], [ options ])
// keyData - {string|buffer|object}用于生成密钥或以支持的格式之一生成密钥的参数。
// format - {string}导入密钥的格式。查看有关导出/导入部分格式的更多详细信息。
// options - {object}- 其他设置。
// 创建“空”键
var key = new NodeRSA();
// 生成新的512位长度密钥
var key = new NodeRSA({ b: 512 });
- 导入/导出密钥
/*实例*/
var publicDer = key.exportKey('public');
var privateDer = key.exportKey('private');
console.log('公钥:',publicDer);
console.log('私钥:',privateDer);
key.importKey(result[1], 'private');
/*语法*/
key.importKey(keyData, [format]);
key.exportKey([format]);
// keyData - {string|buffer}- 可能是:
// 键入PEM字符串
// 包含PEM字符串的缓冲区
// 包含DER编码数据的缓冲区
// 对象包含关键组件
// format - {string}- 用于导出/导入的格式ID。
- 加密/解密
/*加密*/
key.encrypt(buffer, [encoding], [source_encoding]);
key.encryptPrivate(buffer, [encoding], [source_encoding]); // 使用私钥进行加密
/*解密*/
key.decrypt(buffer, [encoding]);
key.decryptPublic(buffer, [encoding]); // 使用公钥解密
/*实例*/
var encryData = key.encryptPrivate(req.body.user_pwd, 'base64','utf8');
console.log('加密后的数据',encryData);
var decryptData = key.decryptPublic(result[0],'utf8');
console.log('解密后的数据',decryptData);
发表评论