我们在日常的业务中经常会遇到这样的场景: 等等,有各种各样的需要加密的场景。在 node 中也有原生的 crypto 模块,该模块提供了 hash、hmac、加密解密、签名、验证功能等一整套的封装。 使用 hash 算法也被称为摘要算法,该算法可以将任意长度的数据,转换为固定长度的 hash 值,这种方式具有不可逆性。你可以把一本小说转换为 hash 数据,但无法从这 hash 数据再逆转回一本小说。因此,若要获取 hash 的原数据,只能靠字典碰撞。 该算法通常在文本校验、存储密码时用的比较多。虽然摘要算法会用于密码的存储,但严格来说,摘要算法不算做是加密算法。 使用 获取到一个数组: 这么多 hash 算法,我们平时用的比较多的是 生成的结果: 不同的算法,生成的 hash 值的长度也不一样,碰撞成功的难度也越大。 同时, 既然可以接收 stream 流的格式,那么就使用 pipe 管道进行处理: hash 后传给下个管道进行处理,不过这里输出的通常会是乱码,因此这里我们自己写一个可写流: 我们先看下 hmac 算法的用法: hmac 算法与 hash 算法的调用方式很像,但 生成的结果: 由此看到,hmac 算法相当于加盐版的 hash 算法,但内部具体的实现原理,恕在下才疏学浅,实在是没看懂:github-node-crypto-hmac。 这种算法实现密码存储就非常的合适,碰撞成功的概率大大减少。在数据库中,我们可以这样存储: 即使脱库得到了这些数据,反向获取到原密码的机会也非常的低。 在 stream 流的操作上,hmac 算法和 hash 算法的用法一样。 前面的两种方法都是不可逆的 hash 加密算法,这里我们介绍下可加密和可解密的算法。常见的对称加密算法有 crypto 模块中提供了 这两个方法都接收 3 个参数: key 和 iv 两个参数都必须是 'utf8' 编码的字符串、Buffer、 TypedArray 或 DataView。 key 可以是 secret 类型的 KeyObject。 如果密码不需要初始化向量,则 iv 可以为 null。 加密的算法: 解密的算法: 使用方法: 我们刚才了解了下对称加密,即加密和解密用的都是相同的密钥。非对称加密相对来说,比对称加密更安全,用公钥加密的内容,必须通过对应的私钥才能解密。双方传输信息时,可以使用先使用对方的公钥进行加密,然后对方再使用自己的私钥解开即可。 我们先用创建一个私钥: 然后根据私钥创建对应的公钥: 这里我们就可以进行非对称的加密和解密了: 使用 在网络中传输的数据,除可使用 Cipher 类进行数据加密外,还可以对数据生成数字签名,以防止在传输过程中对数据进行修改。 签名的过程与非对称加密的过程正好相反,是使用私钥进行加密签名,然后使用公钥进行解密的签名验证。 生成签名的 sign 方法有两个参数,第一个参数为私钥,第二个参数为生成签名的格式,最后返回的 signed 为生成的签名(字符串)。 验证签名的 verify 方法有三个参数,第一个参数为公钥,第二个参数为被验证的签名,第三个参数为生成签名时的格式,返回为布尔值,即是否通过验证。 我从简单的 hash 算法,到对称加密,最后到非对称加密和签名,都有了个大致的了解。后续我们也会对 node 的其他模块进行深入的理解。
const crypto = require('crypto');
即可引入该模块。1. hash 算法 #
getHashes()
方法,可以获取到所有支持的 hash 算法:crypto.getHashes();
[
"RSA-MD4",
"RSA-MD5",
"RSA-MDC2",
"RSA-RIPEMD160",
"RSA-SHA1",
"RSA-SHA1-2",
"RSA-SHA224",
"RSA-SHA256",
"RSA-SHA3-224",
"RSA-SHA3-256",
"RSA-SHA3-384",
"RSA-SHA3-512",
"RSA-SHA384",
"RSA-SHA512",
"RSA-SHA512/224",
"RSA-SHA512/256",
"RSA-SM3",
"blake2b512",
"blake2s256",
"id-rsassa-pkcs1-v1_5-with-sha3-224",
"id-rsassa-pkcs1-v1_5-with-sha3-256",
"id-rsassa-pkcs1-v1_5-with-sha3-384",
"id-rsassa-pkcs1-v1_5-with-sha3-512",
"md4",
"md4WithRSAEncryption",
"md5",
"md5-sha1",
"md5WithRSAEncryption",
"mdc2",
"mdc2WithRSA",
"ripemd",
"ripemd160",
"ripemd160WithRSA",
"rmd160",
"sha1",
"sha1WithRSAEncryption",
"sha224",
"sha224WithRSAEncryption",
"sha256",
"sha256WithRSAEncryption",
"sha3-224",
"sha3-256",
"sha3-384",
"sha3-512",
"sha384",
"sha384WithRSAEncryption",
"sha512",
"sha512-224",
"sha512-224WithRSAEncryption",
"sha512-256",
"sha512-256WithRSAEncryption",
"sha512WithRSAEncryption",
"shake128",
"shake256",
"sm3",
"sm3WithRSAEncryption",
"ssl3-md5",
"ssl3-sha1",
"whirlpool"
]
md5
, sha1
, sha256
, sha512
。这里把同一个文本,按照不同的摘要算法来生成 hash 值:// text 要摘要的文本
// hashtype 摘要的算法
function createHash(text, hashtype) {
const hash = crypto.createHash(hashtype).update(text).digest("hex");
console.log(hashtype, hash, hash.length);
}
hashes.forEach((type) => {
createHash("蚊子", type);
});
md5 37725295ea78b626efcf77768be478cb 32
sha1 21f226b5a07ed3f74e6ae07e994f36d6a9bf6fac 40
sha256 a200ce289b67afbfb6fbc3d7dd33f7ef493daef64fb159c2e48e8534a0289a9b 64
sha512 b88bd9eac191f58e06c99c256bbcfdf2945aa94b47d5e0242be1f0739bf4adccebf4753e9f38f92603fe3f52f331121540c1dda2ed91796410abcfe49a677fba 128
update
方法不止可以接收字符串,还可以接收 stream 流:const filename = "./node-crypto.md";
const hash = crypto.createHash("sha1");
const fsStream = fs.createReadStream(filename);
fsStream.on("readable", () => {
// 哈希流只会生成一个元素。
const data = fsStream.read();
if (data) {
hash.update(data);
} else {
// 数据接收完毕后,输出hash值
console.log(`${hash.digest("hex")} ${filename}`);
}
});
const filename = "./node-crypto.md";
const hash = crypto.createHash("sha1");
const fsStream = fs.createReadStream(filename);
fsStream.pipe(hash).pipe(process.stdout);
const { Writable } = require("stream");
const write = Writable();
write._write = function (data, enc, next) {
// 将流中的数据写入底层
process.stdout.write(hash.digest("hex") + "\n");
// 写入完成时,调用`next()`方法通知流传入下一个数据
process.nextTick(next);
};
fsStream.pipe(hash).pipe(write); // 正常输出hash值
2. hmac 算法 #
const result = crypto.createHmac("sha1", "123456").update("蚊子").digest("hex");
console.log(result); // 0bdd6c1192e321e34887d965c1140be4361ada65
createHmac()
方法这里多了一个参数,这个参数相当于密钥。密钥不一样,即使要加密的文本一样,生成的结果也会不一样。function createHmac() {
const text = "蚊子";
const key = Math.random().toString().slice(-6);
const result = crypto.createHmac("sha1", key).update(text).digest("hex");
console.log(text, key, result);
}
let n = 10;
while (n--) {
createHmac();
}
蚊子 508028 486d1f539e4bb8adfd601fd6a3302fae74043bfe
蚊子 644233 dcd6501e6eee9e1462625b50c1ff91c613559b35
蚊子 479257 752945c62b87ce1edb24661103b65e612bb849b7
蚊子 445857 0c6399758a2348ea31bc778f87f503b050e036d5
蚊子 954174 a78ff9d4301bb09d249db9fa6c9a3a28c04acff7
蚊子 629736 b7fd4d3836363f029dd9009f51ad6c14280987c1
蚊子 343366 7a8cadf5dd620f8c82315f38de1f6dc60bfc5336
蚊子 168627 cc51e4531449642a5a10357cbf8f206319fb1b1f
蚊子 103054 49b1ad9dc2de5da2cd67dc892f51718aa9475a05
蚊子 477238 82615006638be235a220bcfdee0705b5cc6551fc
{
"username": "蚊子",
"password": "486d1f539e4bb8adfd601fd6a3302fae74043bfe",
"key": "508028"
}
3. 对称加密和解密算法 #
aes
和des
。createCipheriv
和createDecipheriv
来进行加密和解密的功能。之前的 createCipher 和 createDecipher 在 10.0.0 版本已经废弃了,我们这里以新的方法为例,写下加密和解密的算法。
function encode(src, key, iv) {
let sign = "";
const cipher = crypto.createCipheriv("aes-128-cbc", key, iv); // createCipher在10.0.0已被废弃
sign += cipher.update(src, "utf8", "hex");
sign += cipher.final("hex");
return sign;
}
function decode(sign, key, iv) {
let src = "";
const cipher = crypto.createDecipheriv("aes-128-cbc", key, iv);
src += cipher.update(sign, "hex", "utf8");
src += cipher.final("utf8");
return src;
}
const key = "37725295ea78b626"; // Buffer.from('37725295ea78b626', 'utf8');
const iv = "efcf77768be478cb"; // Buffer.from('efcf77768be478cb', 'utf8');
// console.log(key, iv);
const src = "hello, my name is wenzi! my password is `etu^&&*(^123)`";
const sign = encode(src, key, iv);
const _src = decode(sign, key, iv);
console.log("key: ", key, "iv: ", iv);
console.log("原文:", src);
console.log("加密后: ", sign);
console.log("解密后: ", _src);
// key: 37725295ea78b626 iv: efcf77768be478cb
// 原文: hello, my name is wenzi! my password is `etu^&&*(^123)`
// 加密后: ce6dc873bfd5a5ae6fe0b2bb3f3de46fb9fc15e0ffc75d12286871dbfa3ed185b3ebf60b8e16dd0057eb0750e897347abeddf5a2741944d5a307ceb25c181276
// 解密后: hello, my name is wenzi! my password is `etu^&&*(^123)`
4. 非对称加密算法 #
openssl genrsa -out rsa_private.key 1024
openssl rsa -in rsa_private.key -pubout -out rsa_public.key
const crypto = require("crypto");
const fs = require("fs");
const pub_key = fs.readFileSync("./rsa_public.key");
const priv_key = fs.readFileSync("./rsa_private.key");
const text = "hello, my name is 蚊子";
const secret = crypto.publicEncrypt(pub_key, Buffer.from(text));
const result = crypto.privateDecrypt(priv_key, secret);
console.log(secret); // buffer格式
console.log(result.toString()); // hello, my name is 蚊子
publicEncrypt
进行公钥的加密过程,使用privateDecrypt
进行私钥的解密过程。5. 签名 #
const crypto = require("crypto");
const fs = require("fs");
const pub_key = fs.readFileSync("./rsa_public.key");
const priv_key = fs.readFileSync("./rsa_private.key");
const text = "hello, my name is 蚊子";
// 生成签名
const sign = crypto.createSign("RSA-SHA256");
sign.update(text);
const signed = sign.sign(priv_key, "hex");
// 验证签名
const verify = crypto.createVerify("RSA-SHA256");
verify.update(text);
const verifyResult = verify.verify(pub_key, signed, "hex");
console.log("sign", signed); // ca364a6e31c1f540737ba3efb1ddf7fa2a087c5c11efe52a9e1f2c88b1fd1e0e50f12da4f22362fdfc3d77f3f538995a27a8206d250dba3572510dfcb33064f48685b96f2b2393f56de4958448cec92a4299434aa3318efe418e166b38100bc3a1d1a9310a510087021da0f66a817043ddfd2fb88db76eb2ace480c17a7f732f
console.log("verifyResult", verifyResult); // true
6. 总结 #
版权属于:
加速器之家
作品采用:
《
署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
》许可协议授权
评论