在我们的日常开发中,不免会使用到数据加解密,哈希等功能,如果用纯 JS 代码来实现这些算法,速度会变慢很多,Node 使用 C/C++ 实现了这些算法,暴露为 javascript 接口的 crypto 模块,包含对 OpenSSL 的哈希、HMAC、加密、解密、签名、以及验证功能的一整套封装。
本文记录一些常用算法介绍和代码实现。
Hash算法
hash算法将任意长度的输入通过散列算法变换成固定长度的输出,该输出就是hash值,常见的有md5,sha1,sha256等。
其特点有:
- 不可逆,从hash值不可以反向推导出原始的数据。
- 相同输入输出相同的hash值。
crypto
模块可以创建 Hash 类
, 通过 createHash(algorithm[, options])
创建并返回一个 Hash
对象,该对象可用于生成哈希摘要(使用给定的 algorithm
)。
实现方式
crypto.createHash(algorithm)
创建并返回一个 hash 对象,它是一个指定算法的加密 hash,用于生成 hash 摘要。 不能使用 new
关键字直接地创建 Hash
对象。
参数 algorithm 可选择系统上安装的 OpenSSL 版本所支持的算法。例如:sha1、md5、sha256、sha512
等。在近期发行的版本中,openssl list-message-digest-algorithms
会显示这些可用的摘要算法。
hash.update(data[, inputEncoding])
更新 hash 的内容为指定的 data。当使用流数据时可能会多次调用该方法。
hash.digest([encoding])
计算所有传入数据的 hash 摘要。参数 encoding(字符编码)可以为 hex、binary、base64
。
关于字符编码,传送门:http://nodejs.cn/api/buffer.html#buffer_buffers_and_character_encodings
下面以 MD5 和 SHA1 为例
const crypto = require("crypto");
function md5(str) {
return crypto.createHash("md5").update(str, "utf8").digest("hex");
}
function sha1(str) {
return crypto.createHash("sha1").update(str, "utf8").digest("hex");
}
console.log(md5("hello md5")); // 741fc6b1878e208346359af502dd11c5
console.log(sha1("hello sha1")); // 64faca92dec81be17500f67d521fbd32bb3a6968
Hmac算法
HMAC算法将Hash算法与一个密钥结合在一起,以阻止对签名完整性的破坏,密钥发生了变化,输出结果也会发生变化。(Hmac可以理解为用随机数“增强”的哈希算法)
使用方法也与 生成 Hash 类似,使用crypto.createHmac(algorithm, key[, options])
创建并返回一个 Hmac
对象。
const crypto = require("crypto");
const key = "abcmouse";
function hmacMd5(str, key) {
return crypto.createHmac("md5", key).update(str, "utf8").digest("hex");
}
console.log(hmacMd5("hello hmacMd5", key)); // 92effcb2e4b99cc77006e26520539401
对称加密
对称加密算法,加解密都用同一个密钥。
Node 的 crypto
模块,提供了 Cipher
类用于加密,Decipher
类用于解密,分别使用 crypto.createCipheriv(algorithm, key, iv[, options])
方法和 crypto.createDecipheriv(algorithm, key, iv[, options])
来创建并返回相应的对象。
key
是 algorithm
使用的原始密钥, iv
是初始化向量。 两个参数都必须是 'utf8'
编码的字符串、Buffer
、 TypedArray
或 DataView
。
Cipher
类的实例用于加密数据。 该类可以通过以下两种方式之一使用:
- 作为可读写的流,其中写入未加密的数据以在可读侧生成加密的数据。
- 使用
cipher.update()
和cipher.final()
方法生成加密的数据
Decipher
类的实例用于解密数据。 该类可以通过以下两种方式之一使用:
- 作为可读写的流,其中写入加密的数据以在可读侧生成未加密的数据。
- 使用
decipher.update()
和decipher.final()
方法生成未加密的数据。
const crypto = require("crypto");
const algorithm = "aes-256-cbc";
// key: 密钥
// 密钥长度取决于算法。
// 在此示例中,对于 aes256,它是 32 个字节(256 位)。
const AESSecret = "abcmouse".repeat(4);
// 初始化向量(iv)
const iv = Buffer.alloc(16, 0);
// data:需要加解密的内容,
function aesEncrypt(data) {
// 给定的算法,密钥和初始化向量(iv)创建并返回Cipher对象
const cipher = crypto.createCipheriv(algorithm, AESSecret, iv);
let encrypted = cipher.update(data, "utf8", "hex");
encrypted += cipher.final("hex");
return crypted;
}
function aesDecrypt(data) {
// 给定的算法,密钥和初始化向量(iv)创建并返回Cipher对象
const decipher = crypto.createDecipheriv(algorithm, AESSecret, iv);
let decrypted = decipher.update(data, "hex", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
}
const data = "hello abcmouse";
const encryptData = aesEncrypt(data);
const decryptData = aesDecrypt(encryptData);
console.log(encryptData); // 60b80608202e1951ef9e3634dc35f92e
console.log(decryptData); // hello abcmouse
补充:
AES是一种常用的对称加密算法。加密的分组模式有ECB/CBC/CFB/OFB
分组密码又称为秘密钥密码或对称密码。利用分组密码对明文进行加密时,首先需要对明文进行分组,每组的长度都相同,然后对每组明文分别加密得到等长的密文,分组密码的特点是加密密钥与解密密钥相同。 分组密码的安全性应该主要依赖于密钥,而不依赖于对加密算法和解密算法的保密。因此,分组密码的加密和解密算法可以公开。
总结
之前对于这个领域的东西还是比较少接触,这次通过实战学习总结,总算是理清楚了常用的加密算法以及常见用途。除了以上功能外,crypto模块还常用的有非对称加密算法,以及签名和验证算法,感兴趣的同学可以自行查阅相关资料,后续有学习使用,也会补充到这里来~