专栏首页YuanXinNodeJS模块研究 - crypto

NodeJS模块研究 - crypto

这次研究下 nodejs 的 crypto 模块,它提供了各种各样加密算法的 API。这篇文章记录了常用加密算法的种类、特点、用途和代码实现。其中涉及算法较多,应用面较广,每类算法都有自己适用的场景。为了使行文流畅,列出了本文记录的几类常用算法:

  • 内容摘要:散列(Hash)算法
  • 内容摘要:HMac 算法
  • 内容加解密:对称加密(AES)与非对称加密解密(RSA)
  • 内容签名:签名和验证算法

散列(Hash)算法

散列函数(英语:Hash function)又称散列算法、哈希函数,是一种从任何一种数据中创建小的数字“指纹”的方法。基本原理是将任意长度数据输入,最后输出固定长度的结果。

hash 算法具有以下特点:

  • 不能从 hash 值倒推原数据
  • 不同的输入,会有不同的输出
  • 好的 hash 算法冲突概率更低

正因为 hash 算法的这些特点,因此 hash 算法主要用于:加密、数据检验、版本标识、负载均衡、分布式(一致性 hash)。

下面实现了一个获取文件标识的函数:

const crypto = require("crypto");
const fs = require("fs");

function getFileHash(file, algorithm) {
    if (!crypto.getHashes().includes(algorithm)) {
        throw new Error("不支持此哈希函数");
    }

    return new Promise(resolve => {
        const hash = crypto.createHash(algorithm);

        const rs = fs.createReadStream(file);
        rs.on("readable", () => {
            const data = rs.read();
            if (data) {
                hash.update(data);
            }
        });
        rs.on("end", () => {
            resolve(hash.digest("hex"));
        });
    });
}

// 用法:获取文件md5
getFileHash("./db.json", "md5").then(val => {
    console.log(val);
});

HMac 算法

攻击者可以借助“彩虹表”来破解哈希表。应对彩虹表的方法,是给密码加盐值(salt),将 pwd 和 salt 一起计算 hash 值。其中,salt 是随机生成的,越长越好,并且需要和用户名、密码对应保存在数据表中。

虽然通过加盐,实现了哈希长度扩展,但是攻击者通过提交密码和哈希值也可以破解攻击。服务器会把提交的密码和 salt 构成字符串,然后和提交的哈希值对比。如果系统不能提交哈希值,不会受到此类攻击。

显然,没有绝对安全的方法。但是不推荐使用密码加盐,而是 HMac 算法。它可以使用任意的 Hash 函数,例如 md5 => HmacMD5、sha1 => HmacSHA1。

下面是利用 Hmac 实现加密数据的函数:

const crypto = require("crypto");

function encryptData(data, key, algorithm) {
    if (!crypto.getHashes().includes(algorithm)) {
        throw new Error("不支持此哈希函数");
    }

    const hmac = crypto.createHmac(algorithm, key);
    hmac.update(data);
    return hmac.digest("hex");
}

// output: 30267bcf2a476abaa9b9a87dd39a1f8d6906d1180451abdcb8145b384b9f76a5
console.log(encryptData("root", "7(23y*&745^%I", "sha256"));

对称加密(AES)与非对称加密解密(RSA)

有很多数据需要加密存储,并且需要解密后进行使用。这和前面不可逆的哈希函数不同。此类算法一共分为两类:

  • 对称加密(AES):加密和解密使用同一个密钥
  • 非对称加密解密(RSA):公钥加密,私钥解密

对称加密(AES)

查看 nodejs 支持的所有加密算法:

crypto.getCiphers();

Nodejs 提供了 Cipher 类和 Decipher 类,分别用于加密和解密。两者都继承 Transfrom Stream,API 的使用方法和哈希函数的 API 使用方法类似。

下面是用 aes-256-cbc 算法对明文进行加密:

const crypto = require("crypto");

const secret = crypto.randomBytes(32); // 密钥
const content = "hello world!"; // 要加密的明文

const cipher = crypto.createCipheriv(
    "aes-256-cbc",
    secret,
    Buffer.alloc(16, 0)
);
cipher.update(content, "utf8");
// 加密后的结果:e2a927165757acc609a89c093d8e3af5
console.log(cipher.final("hex"));

注意:在使用加密算法的时候,给定的密钥长度是有要求的,否则会爆出this[kHandle].initiv(cipher, credential, iv, authTagLength); Error: Invalid key length...的错误。以 aes-256-cbc 算法为例,需要 256 bits = 32 bytes 大小的密钥。同样地,AES 的 IV 也是有要求的,需要 128bits。(请参考“参考链接”部分)

使用 32 个连续I作为密钥,用 aes-256-cbc 加密后的结果是 a061e67f5643d948418fdb150745f24d。下面是逆向解密的过程:

const secret = "I".repeat(32);
const decipher = crypto.createDecipheriv(
    "aes-256-cbc",
    secret,
    Buffer.alloc(16, 0)
);
decipher.update("a061e67f5643d948418fdb150745f24d", "hex");
console.log(decipher.final("utf8")); // 解密后的结果:hello world!

非对称加密解密(RSA)

借助 openssl 生成私钥和公钥:

# 生成私钥
openssl genrsa -out privatekey.pem 1024
# 生成公钥
openssl rsa -in privatekey.pem -pubout -out publickey.pem

hello world! 加密和解密的代码如下:

const crypto = require("crypto");
const fs = require("fs");

const privateKey = fs.readFileSync("./privatekey.pem");
const publicKey = fs.readFileSync("./publickey.pem");

const content = "hello world!"; // 待加密的明文内容

// 公钥加密
const encodeData = crypto.publicEncrypt(publicKey, Buffer.from(content));
console.log(encodeData.toString("base64"));
// 私钥解密
const decodeData = crypto.privateDecrypt(privateKey, encodeData);
console.log(decodeData.toString("utf8"));

签名和验证算法

除了不可逆的哈希算法、数据加密算法,还有专门用于签名和验证的算法。这里也需要用 openssl 生成公钥和私钥。

代码示范如下:

const crypto = require("crypto");
const fs = require("fs");
const assert = require("assert");

const privateKey = fs.readFileSync("./privatekey.pem");
const publicKey = fs.readFileSync("./publickey.pem");

const data = "传输的数据";

// 第一步:用私钥对传输的数据,生成对应的签名
const sign = crypto.createSign("sha256");
// 添加数据
sign.update(data, "utf8");
sign.end();
// 根据私钥,生成签名
const signature = sign.sign(privateKey, "hex");

// 第二步:借助公钥验证签名的准确性
const verify = crypto.createVerify("sha256");
verify.update(data, "utf8");
verify.end();
assert.ok(verify.verify(publicKey, signature, "hex"));

从前面这段代码可以看到,利用私钥进行加密,得到签名值;最后利用公钥进行验证。

总结

之前一直是一知半解,一些概念很模糊,经常混淆散列算法和加密算法。整理完这篇笔记,我才理清楚了常见的加密算法的功能和用途。

除此之外,crypto 模块还提供了其他算法工具,例如 ECDH 在区块链中有应用。这篇文章没有再记录,感兴趣的同学可以去查阅相关资料。

参考链接

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • LeetCode 684.冗余连接 - JavaScript

    输入一个图,该图由一个有着 N 个节点 (节点值不重复 1, 2, …, N) 的树及一条附加的边构成。附加的边的两个顶点包含在 1 到 N 中间,这条附加的边...

    心谭博客
  • NodeJS模块研究 - stream

    构建复杂程序的时候,通常会将系统拆解成若干功能,这些功能的之间的接口遵循一定的规范,以实现组合连接,共同完成复杂任务。例如管道运算符 | 。

    心谭博客
  • 日志库的实现机制与优化方法

    对于日志,一般需要 4 个要素:时间、级别、位置、内容、上下文信息。对于集群或者多台机器来说,日志还需要区分不同机器的唯一标识。

    心谭博客
  • node命令行工具之实现项目工程自动初始化的标准流程

    可以看出,传统的初始化步骤,花费的时间并不少。而且,人工操作的情况下,总有改漏的情况出现。这个缺点有时很致命。 甚至有马大哈,没有更新项目仓库地址,导致提交代码...

    我是leon
  • Qt界面UI之QML初见(学习笔记四)

    一 概述 QML是一种专门用于构建用户界面的编程语言,它允许用户构建高性能,具有流畅特效的可视化应用程序,QML是可读的,声明式的文档,具有类似JSON的语法,...

    用户1198337
  • TinyTools 开源更新一版

    普通程序员偷懒的途径之一就是copy-paste,在长期的编码劳动中不断重复发生着...

    曲水流觞
  • 与其砸掉ERP质疑SaaS未来,不如看巨头们在做什么?

    T客汇官网:tikehui.com 撰文 | 人称T客 “SaaS在中国能做起来吗?” 前不久这句话来自于本土管理软件公司的一位高管,面对这个问题我不知道该说如...

    人称T客
  • 小马智行楼天城:自动驾驶需要什么样的数字人才?

    上周六,在乌镇举办的第六届世界互联网大会“互联网之光”博览会上,大数据文摘联合猎聘举办了“数字经济产业人才研讨会”,并公布了“30位新生代数字经济人才”。

    大数据文摘
  • 自动驾驶的尚方宝剑在哪里?他们或许给出了答案

    日前“2018中国人工智能峰会”圆满落幕,除去主论坛的“星光熠熠”外,四大分论坛的相关议题也获得了行业极大的关注。其中,在智能驾驶分论坛中,行业专家就AI赋能传...

    镁客网
  • 企业自身面临误区的因素之一

      企业自身需求不明确,也许还存在又不知道什么是ERP的情况。企业在选择ERP软件的时候,无法知晓自己的实际需求。人云亦云,邯郸学步。

    明象ERP

扫码关注云+社区

领取腾讯云代金券