NodeJS 加解密之 crypto 模块

NodeJS 加解密之 crypto 模块.png

我们前端一般会很少接触加解密方面的知识,后端对这方面了解的可能比较多,但是作为一个不安分的前端,绝对有必要学习下加解密的知识,因为现在技术发展的方向,越来是往弱后端的方向发展的,一个血淋淋的事实就是,小程序的云开发,一个前端,同时做了前端后端和运维的活。

NodeJS 的加解密,不需要自己实现,也不需要调用第三方模块,有内部模块支持,这个模块就是 crypto。而且这些算法还不是使用 JS 写的,而是用 C/C++ 实现的,然后通过 cypto 这个模块暴露为 JavaScript 接口。那为什么不用 JS 写呢?因为用纯 JavaScript 代码实现这些功能速度会非常的慢。

好了,我们来看看 NodeJS 常用算法和如何通过 API 使用这些常用算法的。

一、哈希(hash)算法

哈希算法是摘要算法的一种,可以根据你提供的内容,生成一段哈希值,只要提供的内容不变,生成的哈希值就不会改变,而且从哈希值是不能反向推导出原始数据的(所以哈希算法也叫单向哈希算法)。

也就是说哈希算法只能加密不能反向解密。看下在 NodeJS 中如何使用:

const crypto = require("crypto");

const hash = crypto.createHash("md5");

hash.update("Condor");
hash.update("Hero");
const hashCode = hash.digest("hex");

console.log(hashCode); // 输出结果为: 9f29506741761b010f98f908ab8f9e04

createHash 方法传入需要加密的摘要算法,例如 MD5、SHA1、SHA256 和 SHA512。等。如果把案例的 MD5 改成 SHA1 ,加密之后的结果为 d17dfb160fce1ca89ecf94027a0bf39b6dda7f4e

update() 方法默认字符串编码为 UTF-8,也可以传入 Bufferupdate() 可以多次被调用,多次调用只是简单的把要加密的结果拼接起来,例如:

hash.update("CondorHero");

===

hash.update("Condor");
hash.update("Hero");

digest 表示加密之后的结果,以什么编码方式输出,比如:

  • latin1 可以认为是 ASCII 扩展
  • hex 十六进制
  • base64 前端更熟悉的 base64 编码方式

createHash 是根据原始文件,直接生成 MD5,这样完全可以使用 MD5 批量生成,然后存储起来撞库。为了防止这个现象,在生成 MD5 的时候,我们会给原始内容,手动加点东西,专业点叫加一个密钥,然后再生成 MD5。NodeJS 不需要我们这么干,因为有 createHmac,可以认为 Hmac 理解为用随机数「增强」的哈希算法,Hmac 是 Hash 的加强。createHmac 的使用很简单:

const crypto = require("crypto");

const hash = crypto.createHmac("md5", "customz_key");

hash.update("CondorHero");
const hashCode = hash.digest("hex");

console.log(hashCode); // bded9377f8766692d7c6ccdb38542d58

上面讲到了 base64 ,呐,我们知道浏览器默认支持 base64 式编码(btoa)和解码(atob),那么在 NodeJS 中如何实现呢?

二、NodeJS 中 base64 的编码和解码方式

我们需要一个引入一个新的 API 来完成这个。这个 API 就是 Buffer.from,它可把数据例如字符串转化成 buffer 。

  • Base64 编码,对应浏览器中的 btoa:
const name = "CondorHero";
const nameBuffer = Buffer.from(name); // 等同于 Buffer.from(name, "utf-8")
const enecodedName = nameBuffer.toString("base64");
console.log(enecodedName); // Q29uZG9ySGVybw==
  • Base64 解码:对应浏览器中的 atob
const base64Name = "Q29uZG9ySGVybw==";
const decodeBuffer = Buffer.from(base64Name, "base64"); // 第二个参数就不能省略了
const decodedName = decodeBuffer.toString("utf-8");
console.log(decodedName); // CondorHero

这里还有个知识要注意,Buffer 的 toString 和平常见到的 toString 方法使用不同,Buffer 的 toString 方法有三个参数,看下 toString 如何定义的:

(method) Buffer.toString(encoding?: BufferEncoding, start?: number, end?: number): string
  • BufferEncoding 编码
  • start 截取从 start 开始
  • end 截取到 end 结束

三、对称加密 DES / AES

先来看看 createCipheriv 的用法:

    crypto.createCipheriv(algorithm,key,iv [,options])
  • iv是初始化向量,可以 为空 或者 16 字节的字符串
  • key是加密密钥,根据选用的算法不同,密钥长度也不同,对应关系如下:
    1. des-cbc 对应 8 位长度密钥
    2. aes128 对应 16 位长度密钥
    3. aes192 对应 24 位长度秘钥
    4. aes256 对应 32 位长度密钥

DES 是 Data Encryption Standard(数据加密标准)的缩写。它是由 IBM 公司研制的一种对称密码算法,美国国家标准局于1977年公布把它作为非机要部门使用的数据加密标准,DES 是一种对称加密算法,密匙长度必须是8的整数倍,在一些简单的应用场景经常被使用。

为了网络上信息传输的安全(防止第三方窃取信息看到明文),发送发和接收方分别进行加密和解密,这样信息在网络上传输的时候就是相对安全的。

DES 加密模式有: Electronic Codebook (ECB) , Cipher Block Chaining (CBC) , Cipher Feedback (CFB) , Output Feedback (OFB)。这里以密文分组链接模式 CBC 为例,使用了相同的 key 和 iv (Initialization Vector)

const crypto = require("crypto");

// DES 加密
function desEncrypt(message, key) {
    const cipher = crypto.createCipheriv("des-cbc", key, key);
    let crypted = cipher.update(message, "utf8", "base64");
    crypted += cipher.final("base64");
    return crypted;
};

// DES 解密
function desDecrypt(text, key) {
    const cipher = crypto.createDecipheriv("des-cbc", key, key);
    let decrypted = cipher.update(text, "base64", "utf8");
    decrypted += cipher.final("utf8");
    return decrypted;
};

const enCode = desEncrypt("CondorHero", "01234567");
console.log(enCode); // /yAMqF2n0wIcXg5/HuTz8A==
const deCode = desDecrypt(enCode, "01234567");
console.log(deCode); // CondorHero

还有一个常见的对称加密算法 AES——高级加密标准(AES,Advanced Encryption Standard),微信小程序加密传输就是用这个加密算法的,详见 服务端获取开放数据

const crypto = require("crypto");

// AES 加密
function aesEncrypt(message, key) {
    const cipher = crypto.createCipheriv("aes128", key, key);
    let crypted = cipher.update(message, "utf8", "hex");
    crypted += cipher.final("hex");
    return crypted;
};

// AES 解密
function aesDecrypt(text, key) {
    const cipher = crypto.createDecipheriv("aes128", key, key);
    let decrypted = cipher.update(text, "hex", "utf8");
    decrypted += cipher.final("utf8");
    return decrypted;
};

const data = "CondorHero";
const key = "0123456789abcdef";
const encrypted = aesEncrypt(data, key);
const decrypted = aesDecrypt(encrypted, key);
console.log("Encrypted text: " + encrypted);
console.log("Decrypted text: " + decrypted);
// Encrypted text: 7fe8c79194fbdf8598c67323a4e7da9a
// Decrypted text: CondorHero

四、RSA 非对称加密

RSA 算法是一种非对称加密算法,即由一个私钥和一个公钥构成的密钥对,通过私钥加密,公钥解密,或者通过公钥加密,私钥解密。其中,公钥可以公开,私钥必须保密。

为提高保密强度,RSA 密钥至少为 500 位长,一般推荐使用 1024 位。这就使加密的计算量很大。为减少计算量,在传送信息时,常采用传统加密方法与公开密钥加密方法相结合的方式,即信息采用对称加密,对称加密的密钥使用 RSA 加密传输。

RSA 算法是 1977 年由 Ron Rivest、Adi Shamir 和 Leonard Adleman 共同提出的,所以以他们三人的姓氏的头字母命名。

简单的来个例子,公钥加密,私钥加密:

const crypto = require("crypto");
const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", {
    modulusLength: 2048,
});
// RSA 公钥加密
function rsaPublicDecrypt(pubKey, message) {
    const crypted = crypto.publicEncrypt(pubKey, Buffer.from(message, "utf8"));
    return crypted.toString("hex");
};

// RSA 私钥解密
function rsaPrivateDecrypt(priKet, enCrypted) {
    const decrypted = crypto.privateDecrypt(priKet, Buffer.from(enCrypted, "hex"));
    return decrypted.toString("utf8");
};

const data = "CondorHero";
const encrypted = rsaPublicDecrypt(publicKey, data);
const decrypted = rsaPrivateDecrypt(privateKey, encrypted);
console.log("Encrypted text: " + encrypted);
console.log("Decrypted text: " + decrypted);

看完之后你可以模仿着写 「私钥加密,公钥加密」,比如使用 ECDH 椭圆曲线仿写加解密函数,当你完成可以对照下面代码查看自己的结果:

const crypto = require("crypto");

// 生成 ECDH 密钥对
const ecdh = crypto.createECDH("secp256k1");
ecdh.generateKeys();

// 获取公钥和私钥
const publicKey = ecdh.getPublicKey(null, "compressed");
const privateKey = ecdh.getPrivateKey(null, "compressed");

// ECDH 公钥加密
function ecdhPublicEncrypt(pubKey, message) {
  const secret = crypto.createECDH("secp256k1").setPrivateKey(privateKey);
  const sharedSecret = secret.computeSecret(pubKey);

  // 生成一个随机的 IV
  const iv = crypto.randomBytes(16);

  // 在加密之前将 IV 与密文拼接在一起,以便在解密时分离它们。
  const cipher = crypto.createCipheriv(
    "aes-256-cbc",
    sharedSecret.slice(0, 32),
    iv
  );
  let encrypted = cipher.update(message, "utf8", "hex");
  encrypted += cipher.final("hex");

  return iv.toString("hex") + encrypted;
}

// ECDH 私钥解密
function ecdhPrivateDecrypt(priKey, enCrypted) {
  const secret = crypto.createECDH("secp256k1").setPrivateKey(priKey);
  const sharedSecret = secret.computeSecret(publicKey);

  // 从加密数据中分离出 IV 和密文。
  const iv = Buffer.from(enCrypted.slice(0, 32), "hex");
  const encrypted = enCrypted.slice(32);

  const decipher = crypto.createDecipheriv(
    "aes-256-cbc",
    sharedSecret.slice(0, 32),
    iv
  );
  let decrypted = decipher.update(encrypted, "hex", "utf8");
  decrypted += decipher.final("utf8");
  return decrypted;
}

const data = "CondorHero";
const encrypted = ecdhPublicEncrypt(publicKey, data);
const decrypted = ecdhPrivateDecrypt(privateKey, encrypted);
console.log(`Encrypted text: ${encrypted}`);
console.log(`Decrypted text: ${decrypted}`);

完~

当前时间 Sunday, January 31, 2021 23:40:34

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,896评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,283评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,085评论 0 367
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,010评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,015评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,492评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,858评论 3 428
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,829评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,374评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,409评论 3 346
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,527评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,131评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,858评论 3 339
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,296评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,434评论 1 276
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,087评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,597评论 2 366

推荐阅读更多精彩内容