Node.js - crypto模块笔记

简介

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数据是Bufferinput_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来派生keyiv,并使用createCipheriv()来创建加密流

Cipher加密对象是一个可读写的Stream流。
可以使用Cipher类中的update方法写入纯文本的数据,
数据输入完成后通过final方法返回加密后的数据。

Decipher解密对象是一个可读写的Stream流。
可以使用Decipher类中的update方法写入需要解密的数据,
数据输入完成后通过final方法返回解密后的数据。

  • crypto.createCipher(algorithm, password):用给定的算法和密钥,创建并返回一个Cipher加密算法的对象。参数:algorithm算法是依赖OpenSSL库支持的算法, 例如: 'aes192'算法等,password是用来派生keyiv的,它必须是一个'binary'二进制格式的字符串或者是一个Buffer

  • crypto.createCipheriv(algorithm, key, iv):用给定的算法、密钥和向量,创建并返回一个Cipher加密算法的对象。参数:algorithmcreateCipher方法相同,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_paddingfalse,那么整个输入数据的长度必须是加密器的块大小的整倍数,否则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必须是一个Bufferoutput_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数据签名对象。algorithmOpenSSL中支持的算法,比如:'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签名验证对象。algorithmOpenSSL中支持的算法,比如:'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 生成 2048RSA 密钥

  • 生成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-rsanpm 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);

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: