NodeJS 加密 —— crypto 模块

在这里插入图片描述


阅读原文


加密简介

加密是以某种算法改变原有的信息数据,使得未受权用户即便得到了已加密信息,因不知解密的方法,没法得知信息真正的含义,经过这种方式提升网络数据传输的安全性,加密算法常见的有哈希算法、HMAC 算法、签名、对称性加密算法和非对称性加密算法,加密算法也分为可逆和不可逆,好比 md5 就是不可逆加密,只能暴力破解(撞库),咱们在 NodeJS 开发中就是直接使用这些加密算法,crypto 模块提供了加密功能,包含对 OpenSSL 的哈希、HMAC、加密、解密、签名以及验证功能的一整套封装,核心模块,使用时不需安装。算法


哈希算法

哈希算法也叫散列算法,用来把任意长度的输入变换成固定长度的输出,常见的有 md5sha1 等,这类算法实现对原数据的转化过程是否能被称为加密备受争议,为了后面叙述方便咱们姑且先叫作加密。浏览器

// 查看哈希加密算法的种类
const crypto = require("crypto");

// getHashes 方法用于查看支持的加密算法
console.log(crypto.getHashes());

// [ 'DSA', 'DSA-SHA', 'DSA-SHA1', 'DSA-cSHA1-old',
//   'RSA-MD4', 'RSA-MD5', 'RSA-MDC2', 'RSA-RIPEMD160',
//   'RSA-SHA', 'RSA-SHA1', 'RSA-SHA1-2', 'RSA-SHA224',
//   'RSA-SHA256', 'RSA-SHA384', 'RSA-SHA512',
//   'dsaEncryption', 'dsaWithSHA', 'dsaWithSHA1', 'dss1',
//   'ecdsa-with-SHA1', 'md4', 'md4WithRSAEncryption',
//   'md5', 'md5WithRSAEncryption', 'mdc2', 'mdc2WithRSA',
//   'ripemd', 'ripemd160', 'ripemd160WithRSA', 'rmd160',
//   'sha', 'sha1', 'sha1WithRSAEncryption', 'sha224',
//   'sha224WithRSAEncryption', 'sha256',
//   'sha256WithRSAEncryption', 'sha384',
//   'sha384WithRSAEncryption', 'sha512',
//   'sha512WithRSAEncryption', 'shaWithRSAEncryption',
//   'ssl2-md5', 'ssl3-md5', 'ssl3-sha1', 'whirlpool' ]

md5 是开发中常用的算法之一,官方称为摘要算法,具备如下几个特色:缓存

  • 不可逆;
  • 无论加密的内容多长,最后输出的结果长度都是相等的;
  • 内容不一样输出的结果彻底不一样,内容相同输出的结果彻底相同。

因为相同的输入通过 md5 加密后返回的结果彻底相同,因此破解时经过 “撞库” 进行暴力破解,当连续被 md5 加密 3 次以上时就很难被破解了,因此使用 md5 通常会进行屡次加密。安全

// md5 加密 —— 返回 Buffer
const crytpo = require("crytpo");

let md5 = crytpo.createHash("md5"); // 建立 md5
let md5Sum = md5.update("hello"); // update 加密
let result = md5Sum.digest(); // 获取加密后结果

console.log(result); // <Buffer 5d 41 40 2a bc 4b 2a 76 b9 71 9d 91 10 17 c5 92>

digest 方法参数用于指定加密后的返回值的格式,不传参默认返回加密后的 Buffer,经常使用的参数有 hexBase64hex 表明十六进制,加密后长度为 32Base64 的结果长度为 24,以 == 结尾。服务器

// md5 加密 —— 返回十六进制
const crypto = require("crypto");

let md5 = crypto.createHash("md5");
let md5Sum = md5.update("hello");
let result = md5Sum.digest("hex");

console.log(result); // 5d41402abc4b2a76b9719d911017c592
// md5 加密 —— 返回 Base64
const crypto = require("crypto");

let md5 = crypto.createHash("md5");
let md5Sum = md5.update("hello");
let result = md5Sum.digest("Base64");

console.log(result); // XUFAKrxLKna5cZ2REBfFkg==

update 方法的返回值就是 this,即当前实例,因此支持链式调用,较长的信息也能够屡次调用 update 方法进行分段加密,调用 digest 方法一样会返回整个加密后的值。cookie

// 链式调用和分段加密
const crypto = require("crypto");

let result = crypto
    .createHash("md5")
    .update("he")
    .update("llo")
    .digest("hex");

console.log(result); // 5d41402abc4b2a76b9719d911017c592

因为可使用 update 进行分段加密,就能够结合流来使用,其实 crypto 的本质是建立 Transform 类型的转化流,能够将可读流转化成可写流。网络

// 对可读流读取的数据进行 md5 加密
const crypto = require("crypto");
let fs = require("fs");

let md5 = crypto.createHash("md5");
let rs = fs.createReadSteam("./readme.txt", {
    highWaterMark: 3
});

// 读取数据并加密
rs.on("data", data => md5.update(data));

rs.on("end", () => {
    let result = md5.digest("hex");
    console.log(result);
});

使用场景 1:常常被使用在数据的校验,好比服务器与服务器之间进行通讯发送的明文摘要加 md5 加密摘要后的暗文,接收端拿到数据之后将明文摘要按照相同的 md5 算法加密后与暗文摘要对比验证,目的是防止数据传输过程当中被劫持并篡改。
使用场景 2:在浏览器缓存策略中,能够经过对静态资源的信息摘要使用 md5 加密,每次向服务器发送加密后的密钥进行比对就能够了,不至于对整个文件内容进行比较。ui

缺点:因为规定使用 md5 的哈希算法加密,别人可使用一样的算法对信息进行伪造,安全性不高。this


Hmac 算法

一、Hmac 算法的使用

Hmac 算法又称加盐算法,是将哈希算法与一个密钥结合在一块儿,用来阻止对签名完整性的破坏,一样具有 md5 加密的几个特色。编码

// 使用加盐算法加密
const crytpo = require("crytpo");

let hmac = crytpo.createHmac("sha1", "panda");
let result = hmac.update("hello").digest("Base64");

console.log(result); // 7spMLxN8WJdcEtQ8Hm/LR9pUE3YsIGag9Dcai7lwioo=

crytpo.createHmac 第一个参数同 crytpo.createHash,为加密的算法,经常使用 sha1sha256,第二个参数为密钥。

digest 方法生成的加密结果长度要大于 md5hex 生成的结果长度为 64Base64 生成的结果长度为 44,以 = 结尾。

安全性高于 md5,经过密钥来加密,不知道密钥没法破解,缺点是密钥传输的过程容易被劫持,能够经过一些生成随机密钥的方式避免。

二、建立密钥的方法

能够安装 openSSH 客户端,并经过命令行生成存储密钥的文件,命令以下。

openssl genrsa -out rsa_private.key 1024

openssl genrsa 表明生成密钥,-out 表明输出文件,rsa_private.key 表明文件名,1024 表明输出密钥的大小。

// 直接读取密钥文件配合加盐算法加密
const fs = require("fs");
const crytpo = require("crytpo");
const path = require("path");

let key = fs.readFileSync(path.join(__dirname, "/rsa_private.key"));
let hmac = crytpo.createHmac("sha256", key);

let result = hmac.update("hello").digest("Base64");

console.log(result); // bmi2N+6kwgwt5b+U+zSgjL/NFs+GsUnZmcieqLKBy4M=


对称性加密

对称性加密是发送数据时使用密钥和加密算法进行加密,接收数据时须要使用相同的密钥和加密算法的逆算法(解密算法)进行解密,也就是说对称性加密的过程是可逆的,crytpo 中使用的算法为 blowfish

// 对称性加密
const fs = require("fs");
const crypto = require("crypto");
const path = require("path");

let key = fs.readFileSync(path.join(__dirname, "/rsa_private.key"));

// 加密
let cipher = crypto.createCipher("blowfish", key);
cipher.update("hello");

// final 方法不能链式调用
let result = cipher.final("hex");
console.log(result); // 3eb9943113c7aa1e

// 解密
let decipher = crypto.createDecipher("blowfish", key);
decipher.update(result, "hex");

let data = decipher.final("utf8");
console.log(data); // hello

加密使用 crypto.createCipher 方法,解密使用 crypto.createDecipher 方法,可是使用的算法和密钥必须相同,须要注意的是解密过程当中 update 中须要在第二个参数中指定加密时的格式,如 hex,在 final 还原数据时须要指定加密字符的编码格式,如 utf8

注意:使用对称性加密的字符串有长度限制,不得超过 7 个字符,不然虽然能够加密成功,可是没法解密。

缺点:密钥在传输过程当中容易被截获,存在安全风险。


非对称性加密

非对称性加密相也是可逆的,较于对称性加密要更安全,消息传输方和接收方都会在本地建立一对密钥,公钥和私钥,互相将本身的公钥发送给对方,每次消息传递时使用对方的公钥加密,对方接收消息后使用他的的私钥解密,这样在公钥传递的过程当中被截获也没法解密,由于公钥加密的消息只有配对的私钥能够解密。

接下来咱们使用 openSSH 对以前生成的私钥 rsa_private.key 产生一个对应的公钥,命令以下。

openssl rsa -in rsa_private.key -pubout -out rsa_public.key

上面的命令意思根据一个私钥生成对应的公钥,-pubout -out 表明公钥输出,rsa_public.key 为公钥的文件名。

// 非对称性加密
const fs = require("fs");
const crypto = require("crypto");
const path = require("path");

// 获取公钥和私钥
let publicKey = fs.readFileSync(path.join(__dirname, "/rsa_public.key"));
let privateKey = fs.readFileSync(path.join(__dirname, "/rsa_private.key"));

// 加密
let secret = crytpo.publicEncrypt(publicKey, Buffer.from("hello"));

// 解密
let result = crytpo.provateDecrypt(privateKey, secret);

console.log(result); // hello

使用公钥加密的方法是 crytpo.publicEncrypt,第一个参数为公钥,第二个参数为加密信息(必须是 Buffer),使用私钥解密的方法是 crytpo.provateDecrypt,第一个参数为私钥,第二个参数为解密的信息。


签名

签名与非对称性加密很是相似,一样有公钥和私钥,不一样的是使用私钥加密,对方使用公钥进行解密验证,以确保这段数据是私钥的拥有者所发出的原始数据,且在网络中的传输过程当中未被修改。

在这里插入图片描述

咱们还使用 rsa_public.keyrsa_private.key 做为公钥和私钥,crypto 实现签名代码以下。

// 签名
const fs = require("fs");
const crypto = require("crypto");
const path = require("path");

// 获取公钥和私钥
let publicKey = fs.readFileSync(path.join(__dirname, "rsa_public.key"), "ascii");
let privateKey = fs.readFileSync(path.join(__dirname, "rsa_private.key"), "ascii");

// 生成签名
let sign = crypto.createSign("RSA-SHA256");
sign.update("panda");
let signed = sign.sign(privateKey, "hex");

// 验证签名
let verify = crypto.createVerify("RSA-SHA256");
verify.update("panda");
let verifyResult = verify.verify(publicKey, signed, "hex");

console.log(verifyResult); // true

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

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

使用场景:常常用于对 cookie 签名返回浏览器,当浏览器访问同域服务器将 cookie 带过来时再进行验证,防止 cookie 被篡改和 CSRF 跨站请求伪造。


总结

各类项目在数据传输时根据信息的敏感度以及用途进行不一样的加密算法和加密方式,在 NodeJS 中,crypto 的 API 彻底能够实现咱们的加密需求,也能够将上面的加密方案组合使用实现更复杂的加密方案。

相关文章
相关标签/搜索