crypto 加密模块
-
crypto模块提供了加密功能,包括对OpenSSL 的哈希、HMAC、加密、解密、签名、以及验证功能的以整套封装。 -
使用
require('crypto')来访问该模块 -
查看nodejs支持的加密算法,
- 使用
crypto.getCiphers(),如下所示const crypto = require('crypto') crypto.getCiphers() - 得到的是一个比较大的数组,这里列举其中的几个元素:
[ 'aes-128-cbc', 'aes-128-ccm', 'aes-128-cfb', 'aes256', 'aes256-wrap', 'aria-128-cbc', 'aria-128-ccm', 'aria-128-cfb', 'aria-128-cfb1', 'bf-cbc', 'bf-cfb', 'bf-ecb', 'camellia192', 'camellia256', 'cast', 'cast-cbc', ... 71 more items ]
- 这里重点介绍 ASE 加密
-
ASE加密是高级加密标准,为美国联邦政府采用的一种区块加密标准,高级加密标准已然成为对称密钥加密中最流行的算法之一。
-
ASE使用的密钥长度可以128位、192位或256位,所以你可以看到加密算法:aes-128/196/256,表示的都是密钥的位数。最后一段是AES的工作模式,最常用的是 ECB、CBC、CFB、和OFB四种。
-
ASE 加密时的key(原始密钥) 和 iv(初始化向量的长度)
长度 密钥长度 向量长度 128位 16 16 192位 24 16 256位 32 16
-
- 使用
-
crypto.scrypt(password,salt,keylen[,options],callback)方法:scrypt()是一个异步的密钥派生函数,被设计为在计算和内存方面的成本都非常高,目的是使它无法暴力破解。salt: 盐值,应该尽可能独特。建议盐值是随机的并且至少16个字节长。keylen: 生成的密钥的长度。callback回调函数有两个参数:err和derivedKey, 当密钥派生失败时, err 是一个异常对象,否则 err 为 null。 derivedKey 会作为 Buffer 传给回调。
-
crypto.randomBytes(size[,callback])方法:- 生成加密的强伪随机数据。size 参数是生成随机数的字节数。
- callback 回调函数有两个参数:
err和buf。如果发生错误,则err是一个Error对象,否则为null。buf参数是包含生成字节的Buffer。
###Cipher 类
Cipher类- Cipher 是密码的意思。Cipher` 类实例用来加密数据。使用方式有以下两种,任选一种即可:
- 作为一个可读可写的stream流,这样可以将原生未加密的数据写入并在可读侧生成加密的数据。
- 使用 cipher.update 和 cipher.final 方法来生成加密数据
crypto.createCipheriv(algorithm, key, iv[, options])方法:- 使用给定的
algorithm(算法)、key和初始化向量iv创建并返回一个Cipher对象。 algorithm取决于 OpenSSL,列如:'aes-128-cbc'key是algorithm使用的原始密钥,iv是初始化向量。两个参数都必须是utf8编码的字符串、Buffer、TypedArray或DataView。- 初始化向量应该是不可预测的且唯一的,理想情况下,它们在密码上是随机的。
- 使用给定的
cipher.update(data[, inputEncoding][, outputEncoding])方法:- 加密
data,生产加密数据。 如果指定了inputEncoding参数,则data参数是使用了指定的字符编码的字符串。 如果未指定inputEncoding参数,则data必须是一个Buffer、TypedArray或DataView。 如果data是一个Buffer、TypedArray或DataView,则inputEncoding会被忽略。 outputEncoding指定了加密的数据的输出格式。 如果指定了outputEncoding,则返回使用了指定的字符编码的字符串。 如果未提供outputEncoding,则返回Buffer。- 可以使用新数据多次调用
cipher.update()方法,直到cipher.final()被调用。
- 加密
cipher.final([outputEncoding])方法:- 返回任何剩余的加密内容。如果指定了
outputEncoding,则返回一个字符串。如果未提供outputEncoding,则返回Buffer。 - 一旦调用了
cipher.final()方法,则Cipher对象就不能再用于加密数据。 如果试图多次调用cipher.final(),则将会导致抛出错误。
- 返回任何剩余的加密内容。如果指定了
- 注意:加密时要将生成密钥的密码:
password,盐值:salt以及初始化向量iv一同写入密文,解密时通过密文来获取password、salt、iv
###Decipher 类
Decipher类的实例用于解密数据。 该类可以通过以下两种方式之一使用:- 作为可读写的流,其中写入加密的数据以在可读侧生成未加密的数据。
- 使用
decipher.update()和decipher.final()方法生成未加密的数据。
crypto.createDecipheriv(algorithm, key, iv[, options])方法:- 使用给定的 algorithm(算法)、 key 和初始化向量(iv)创建并返回一个 Decipher 对象。
algorithm:算法key: 是algorithm使用的原始密钥iv: 初始化向量
decipher.update(data[, inputEncoding][, outputEncoding])方法:- 解密使用
Cipher类实例加密后的密文 inputEncoding: 如果指定了 inputEncoding 参数,则 data 参数是使用了指定的字符编码的字符串。outputEncoding指定了解密的数据的输出格式
- 解密使用
decipher.final([outputEncoding])方法:- 返回任何剩余的解密内容
- 如果指定了
outputEncoding,则返回一个字符串。如果未提供outputEncoding,则返回Buffer。
- 注意:解密时的算法,密钥密码, 盐值,初始化向量必须要和加密时的一致,否则就会出现下面常见的错误:
06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
加密解密模块
加密解密模块实现:
- 加密解密模块被放在了一个叫 tools 的工具文件夹下面
- cipherCode.js
//============ 加密/解密数据模块 ===========
'use strict';
const crypto = require('crypto');
//------------------ 加密数据 -------------------
let algorithm = 'aes-128-cbc'; // algorithm 是算法的意思
/**
* @description 加密数据
* @description params: data--要加密的数据,必须是utf8格式的字符串;callback--处理结果集的回调函数
* @param data {string}
* @param callback {function(string)}
*/
let encrypt = function (data, callback) {
let password = crypto.randomBytes(16).toString('hex'); // password是用于生产密钥的密码
let salt = crypto.randomBytes(16).toString('hex'); // 生成盐值
let iv = crypto.randomBytes(8).toString('hex'); // 初始化向量
crypto.scrypt(password, salt, 16, function (err, derivedKey) {
if (err) {
throw err;
} else {
let cipher = crypto.createCipheriv(algorithm, derivedKey, iv); // 创建 cipher 实例
// 加密数据
let cipherText = cipher.update(data, 'utf8', 'hex');
cipherText += cipher.final('hex');
cipherText += (password+salt + iv);
callback(cipherText)
}
});
};
/**
* @description 解密通过 encrypt(); 加密的数据
* @description param: cipherText--通过 encrypt() 加密的数据; callback--处理结果集的回调函数。
* @param cipherText {string}
* @param callback {function(string)}
*/
let decrypt = function (cipherText, callback) {
let iv = cipherText.slice(-16); // 获取初始化向量
let salt = cipherText.slice(-48, -16); // 获取盐值
let password = cipherText.slice(-80, -48); // 获取密钥密码
let data = cipherText.slice(0, -80); //获取密文
crypto.scrypt(password, salt, 16, function (err, derivedKey) {
if (err) {
throw err;
} else {
let decipher = crypto.createDecipheriv(algorithm, derivedKey, iv); // 创建 decipher 实例
// 解密数据
let txt = decipher.update(data, 'hex', 'utf8');
txt += decipher.final('utf8');
callback(txt)
}
});
};
//----------- 导出 加密/解密数据模块 --------------
module.exports = {
encrypt,
decrypt
};
- index.js
//=========== 工具模块 ==========
const cipher = require('./cipherCode.js');
module.exports = {
cipher
};
测试
// =========== 测试加/解密模块 =============
const fs = require('fs');
const path = require('path');
const tools = require('../tools');
let str = '今年(应是2003年)是我大学毕业满10年的日子,也是我投身IT技术的第10年。';
tools.cipher.encrypt(str, function (result) {
fs.writeFile(path.join(__dirname, '1.txt'), result, function (err) {
console.log('加密数据写入成功');
});
});
fs.readFile(path.join(__dirname, '1.txt'), 'utf8', function (err, info) {
tools.cipher.decrypt(info, function (result) {
console.log(result);
console.log(result===str);
});
});
- 测试结果
加密数据写入成功 今年(应是2003年)是我大学毕业满10年的日子,也是我投身IT技术的第10年。 true