惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Simon Willison's Weblog
Simon Willison's Weblog
Google DeepMind News
Google DeepMind News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
P
Proofpoint News Feed
Recent Announcements
Recent Announcements
MongoDB | Blog
MongoDB | Blog
U
Unit 42
云风的 BLOG
云风的 BLOG
Recorded Future
Recorded Future
G
Google Developers Blog
I
InfoQ
Blog — PlanetScale
Blog — PlanetScale
A
About on SuperTechFans
Jina AI
Jina AI
量子位
宝玉的分享
宝玉的分享
The Cloudflare Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 聂微东
Last Week in AI
Last Week in AI
WordPress大学
WordPress大学
美团技术团队
The Hacker News
The Hacker News
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tailwind CSS Blog
博客园 - 司徒正美
博客园 - 叶小钗
Hugging Face - Blog
Hugging Face - Blog
P
Palo Alto Networks Blog
博客园_首页
阮一峰的网络日志
阮一峰的网络日志
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
The GitHub Blog
The GitHub Blog
Y
Y Combinator Blog
Vercel News
Vercel News
Martin Fowler
Martin Fowler
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Forbes - Security
Forbes - Security
Attack and Defense Labs
Attack and Defense Labs
Google DeepMind News
Google DeepMind News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Azure Blog
Microsoft Azure Blog
P
Privacy International News Feed
G
GRAHAM CLULEY
The Last Watchdog
The Last Watchdog
C
Cyber Attacks, Cyber Crime and Cyber Security
AI
AI
V2EX - 技术
V2EX - 技术

蚊子的前端博客

微说 | 有的人觉得只要不考XX,一定能考好!-蚊子的前端博客 微说 | 春天来了-蚊子的前端博客 明天和意外不知道哪个先来-蚊子的前端博客 微说 | 既然错过了路口,就及时止损,重新规划路线上路-蚊子的前端博客 微说 | 2025年的出生人口数是792万-蚊子的前端博客 微说 | 工作年终总结,让写对接的接口的数量?-蚊子的前端博客 微说 | 温家宝:没有政治体制改革的成功 经济体制改革不可能进行到底-蚊子的前端博客 微说 | 违法犯罪了该不该被禁言?-蚊子的前端博客 微说 | 不明白为什么在推荐去俄罗斯旅游-蚊子的前端博客 微说 | 易中天论骗子-蚊子的前端博客 微说 | 一场大风吹散了秋天-蚊子的前端博客 微说 | 既要、又要、还要、更要!-蚊子的前端博客 又是一年的国庆雨季-蚊子的前端博客 微说 | 预估下2025年的出生人口数据-蚊子的前端博客 微说 | 可惜了我那些小时候的书本-蚊子的前端博客 微说 | 牛马有的是,驴不够了!-蚊子的前端博客 微说 | 能否有一条非户口也能高考的路-蚊子的前端博客 微说 | 小聊中医-蚊子的前端博客 微说 | 将要制作的一款新产品-蚊子的前端博客 微说 | 做人要有信-蚊子的前端博客 微说 | 好一个正义联盟-蚊子的前端博客 微说 | 都是见过吃过的主儿-蚊子的前端博客 微说 | 追求8小时工作制有错吗?-蚊子的前端博客 微说 | 如果尖锐的批评完全消失-蚊子的前端博客 微说 | 很好!-蚊子的前端博客 微说 | 封禁用户可以,但要告知具体原因-蚊子的前端博客 微说 | 面朝大海,春暖花开-蚊子的前端博客 微说 | 程序员的悲哀是什么?-蚊子的前端博客 微说 | 2025年出生人口的预测-蚊子的前端博客 前端在 LiveKit 中如何获取所有的参与者-蚊子的前端博客 在 Electron 中使用 LiveKit 实现屏幕共享-蚊子的前端博客 在 Electron 应用中,如何获取 Mac 的摄像头和麦克风权限-蚊子的前端博客 微说 | 不要总让台湾艺人表态-蚊子的前端博客 微说 | 他们总说要更开放-蚊子的前端博客 自己封装 uuid 踩的一个小坑-蚊子的前端博客 CKEditor5 光标在粘贴的图片下一行-蚊子的前端博客 软考出成绩了,没过-蚊子的前端博客 微说 | 也许有一天我们这个社会会进步-蚊子的前端博客 好久没有更新文章了-蚊子的前端博客 微说 | 中共中央关于进一步全面深化改革,推进中国式现代化的决定-蚊子的前端博客 微说 | 关于惩罚性赔偿-蚊子的前端博客 微说 | 生孩子是谁的事儿!-蚊子的前端博客 微说 | 给群众一些选择电视剧的空间-蚊子的前端博客 微说 | 活着-蚊子的前端博客 微说 | 借用睡前消息里面的一段话-蚊子的前端博客 微说 | 曾经梦寐以求的,现在却没了追求-蚊子的前端博客 微说 | 我小时候的那些书本-蚊子的前端博客 给博客添加了一个微说的功能-蚊子的前端博客 今年的双 11 很平静-蚊子的前端博客 微说 | 贪就是贪-蚊子的前端博客 微说 | 总有人说穿越小说为什么不发展工业和科技!-蚊子的前端博客 微说 | 花了很多时间和精力,-蚊子的前端博客 2025 年终于是增加了 2 天的假期-蚊子的前端博客 微说 | 这是我的第一条微说,-蚊子的前端博客 如何手写 Array 的 forEach 方法-蚊子的前端博客 看到聚美优品的没落,真是唏嘘-蚊子的前端博客 如何手写 Array 的 map 方法-蚊子的前端博客 秋意正浓时-蚊子的前端博客 收藏几个常用但访问比较慢的组件的官网网址-蚊子的前端博客 从 Axios 源码分析如何支持 fetch 方法的-蚊子的前端博客 1024,给博客评论区所有的头像戴上一顶可爱的帽子-蚊子的前端博客 使用 GitHub 搭建了一个静态博客-蚊子的前端博客 在 JavaScript 如何判断变量是否为空-蚊子的前端博客 我为什么还在坚持我的个人博客-蚊子的前端博客 不要再手动拼接 URL 参数,请使用 URLSearchParams-蚊子的前端博客 使用 NodeJs 向百度资源推送链接-蚊子的前端博客 工具链极度内卷,留给开发者的性能优化手段已经不多了-蚊子的前端博客 在 React 中显示多个标签,超出省略并可以 hover 显示更多-蚊子的前端博客 蚊子的前端博客:探索与分享前端技术的奇妙之旅-蚊子的前端博客 互联网程序员前景真的一片黑暗吗?-蚊子的前端博客 Deno2.0 正式发布,向 Node.js 兼容-蚊子的前端博客 React 请求数据别再使用 useEffect 和 useState,试试 SWR 吧!-蚊子的前端博客 React 的 useEffect 的一些使用场景和技巧-蚊子的前端博客 前端趣闻之 JavaScript 语言的诞生和发展-蚊子的前端博客 如何合并同一接口的相同参数的请求-蚊子的前端博客 百度搜索中关于蚊子前端博客的奇怪的检索数据-蚊子的前端博客 使用 React 实现 todo list 的 curd 操作-蚊子的前端博客 React 模板中为什么可以用逻辑与运算符-蚊子的前端博客 评论回复在一张表里的评论系统如何进行分页-蚊子的前端博客 React 组件多次调用时如何区分不同的 div 容器-蚊子的前端博客 安全赋值运算符,再也不用写 try-catch 了-蚊子的前端博客 使用 nextjs 重构我的个人博客-蚊子的前端博客 基于 gitlab 的 webhook 向飞书发通知-蚊子的前端博客 数字转换为更高量级单位的工具方法-蚊子的前端博客 如何避免旧请求的数据覆盖掉最新请求-蚊子的前端博客 JavaScript 中 toString 的冷知识-蚊子的前端博客 React 中 useState 和 useRef 与全局变量的区别-蚊子的前端博客 从面试官的角度分析下简历中存在的问题-蚊子的前端博客 使用 React 实现 6 个输入框的短信验证码功能-蚊子的前端博客 反驳那些要实时刷新页面的前端部署方案-蚊子的前端博客 JSON stringify 的一些不常见使用-蚊子的前端博客 JavaScript 中数组 Array 的常见操作-蚊子的前端博客 前端如何提升用户的交互体验-蚊子的前端博客 没有日的日期在iOS中报 Invalid Date 的探究-蚊子的前端博客 飞书很好,但还是没抗住-蚊子的前端博客 给 Antd 的 DatePicker 组件实现带有至今的功能-蚊子的前端博客 antd 的 InputNumber 输入框添加左右的加减按钮-蚊子的前端博客 给想进国央企的同学介绍一款招聘软件「国聘」-蚊子的前端博客 基于 React 和 antd 实现的图片裁剪压缩功能-蚊子的前端博客 uniapp 中 checkbox 中的 checked 不生效的方案-蚊子的前端博客
深入理解 node 中的 crypto 加密模块-蚊子的前端博客
author · 2020-04-05 · via 蚊子的前端博客

我们在日常的业务中经常会遇到这样的场景:

  1. 对比两个文件的内容是否相同;
  2. 生成 token;
  3. 密码保护;
  4. 加密和解密数据;

等等,有各种各样的需要加密的场景。在 node 中也有原生的 crypto 模块,该模块提供了 hash、hmac、加密解密、签名、验证功能等一整套的封装。

使用const crypto = require('crypto');即可引入该模块。

1. hash 算法 #

hash 算法也被称为摘要算法,该算法可以将任意长度的数据,转换为固定长度的 hash 值,这种方式具有不可逆性。你可以把一本小说转换为 hash 数据,但无法从这 hash 数据再逆转回一本小说。因此,若要获取 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"
]

这么多 hash 算法,我们平时用的比较多的是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

不同的算法,生成的 hash 值的长度也不一样,碰撞成功的难度也越大。

同时,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}`);
    }
});

既然可以接收 stream 流的格式,那么就使用 pipe 管道进行处理:

const filename = "./node-crypto.md";
const hash = crypto.createHash("sha1");
const fsStream = fs.createReadStream(filename);

fsStream.pipe(hash).pipe(process.stdout);

hash 后传给下个管道进行处理,不过这里输出的通常会是乱码,因此这里我们自己写一个可写流:

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 算法 #

我们先看下 hmac 算法的用法:

const result = crypto.createHmac("sha1", "123456").update("蚊子").digest("hex");

console.log(result); // 0bdd6c1192e321e34887d965c1140be4361ada65

hmac 算法与 hash 算法的调用方式很像,但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

由此看到,hmac 算法相当于加盐版的 hash 算法,但内部具体的实现原理,恕在下才疏学浅,实在是没看懂:github-node-crypto-hmac

这种算法实现密码存储就非常的合适,碰撞成功的概率大大减少。在数据库中,我们可以这样存储:

{
    "username": "蚊子",
    "password": "486d1f539e4bb8adfd601fd6a3302fae74043bfe",
    "key": "508028"
}

即使脱库得到了这些数据,反向获取到原密码的机会也非常的低。

在 stream 流的操作上,hmac 算法和 hash 算法的用法一样。

3. 对称加密和解密算法 #

前面的两种方法都是不可逆的 hash 加密算法,这里我们介绍下可加密和可解密的算法。常见的对称加密算法有aesdes

crypto 模块中提供了createCipherivcreateDecipheriv来进行加密和解密的功能。之前的 createCipher 和 createDecipher 在 10.0.0 版本已经废弃了,我们这里以新的方法为例,写下加密和解密的算法。

这两个方法都接收 3 个参数:

  1. algorithm:加密解密的类型;
  2. key: 加密解密的密钥:密钥必须是 8/16/32 位,如果加密算法是 128,则对应的密钥是 16 位,如果加密算法是 256,则对应的密钥是 32 位;
  3. iv: 初始向量,规则与 key 一样

key 和 iv 两个参数都必须是 'utf8' 编码的字符串、Buffer、 TypedArray 或 DataView。 key 可以是 secret 类型的 KeyObject。 如果密码不需要初始化向量,则 iv 可以为 null。

加密的算法:

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. 签名 #

在网络中传输的数据,除可使用 Cipher 类进行数据加密外,还可以对数据生成数字签名,以防止在传输过程中对数据进行修改。

签名的过程与非对称加密的过程正好相反,是使用私钥进行加密签名,然后使用公钥进行解密的签名验证。

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

生成签名的 sign 方法有两个参数,第一个参数为私钥,第二个参数为生成签名的格式,最后返回的 signed 为生成的签名(字符串)。

验证签名的 verify 方法有三个参数,第一个参数为公钥,第二个参数为被验证的签名,第三个参数为生成签名时的格式,返回为布尔值,即是否通过验证。

6. 总结 #

我从简单的 hash 算法,到对称加密,最后到非对称加密和签名,都有了个大致的了解。后续我们也会对 node 的其他模块进行深入的理解。