我们前端一般会很少接触加解密方面的知识,后端对这方面了解的可能比较多,但是作为一个不安分的前端,绝对有必要学习下加解密的知识,因为现在技术发展的方向,越来是往弱后端的方向发展的,一个血淋淋的事实就是,小程序的云开发,一个前端,同时做了前端后端和运维的活。
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
,也可以传入 Buffer
。update()
可以多次被调用,多次调用只是简单的把要加密的结果拼接起来,例如:
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是加密密钥,根据选用的算法不同,密钥长度也不同,对应关系如下:
- des-cbc 对应 8 位长度密钥
- aes128 对应 16 位长度密钥
- aes192 对应 24 位长度秘钥
- 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