Crypto加密模块

Crypto加密模块
 最后更新于 2024年10月02日 22:59:44

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])

非对称加密 和 数字签名及验证

Loading...

基本流程:

  1. 通讯服务器拥有 2 把钥匙,私钥保留,公钥公开, 以及 CA证书服务器提供的 数字证书(Digital Certificate) 数字证书由 通讯服务器的信息,算法的信息,通讯服务器的公钥 等信息加密生成
  2. 用户向通讯服务器发起请求。
  3. 通讯服务器 先将 数据通过Hash函数生成 摘要(digest), 然后用私钥加密摘要生成 数字签名(signature) 最后将 信息数据 + 数字签名 + 数字证书 发给请求的用户
  4. 用户 浏览器中有"证书管理器",有"受信任的根证书颁发机构"列表。浏览器会根据这张列表,查看 CA 证书服务器 是否在列表之内 如果 CA证书服务器 受信任,则用 CA 的公钥 解开数字证书拿到 通讯服务器的公钥,再解密 数字签名得到摘要, 最后将 信息数据 通过 Hash函数也生成一个 摘要, 与解密得到的摘要做对比,两者相等则证明数据没有被修改

Encrypt 加密 、 Decrypt 解密

crypto.publicEncrypt(publicKey, buffer)

  • publicKey 公钥,只支持 RSA 加密算法
  • buffer 需要加密的数据,必须是 buffer、TypedArray、DataView

publicKey 如果是一个字符串,作为没有密码的密钥;如果是一个对象,则包含下面的参数:

  1. key: 加密的私钥字符串
  2. passphrase: 私钥密码字符串
  3. 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 创建密钥和证书

一个完整的利用对称加密和非对称加密的示例

实现步骤:

  1. 先用 crypto.randomBytes() 生成强随机数,作为密钥key
  2. 再将上一步生成的 key(密钥) 和对称加密算法 crypto.createCipher() 加密 data
  3. 再用非对称加密算法 crypto.publicEncrypt() 加密第一步生成的 key(密钥)
  4. 然后将 加密后的 key(密钥) 和 加密后的data 传递
  5. 服务端接收数据后。先用非对称加密算法 crypto.privateDecrypt() 解密 key(密钥)
  6. 再将上一步解密出来的 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')
  // 解密后的数据:我是需要加密的数据
})