我們前端一般會很少接觸加解密方面的知識,后端對這方面了解的可能比較多伍宦,但是作為一個不安分的前端给猾,絕對有必要學習下加解密的知識,因為現(xiàn)在技術發(fā)展的方向先朦,越來是往弱后端的方向發(fā)展的缰冤,一個血淋淋的事實就是,小程序的云開發(fā)喳魏,一個前端棉浸,同時做了前端后端和運維的活。
NodeJS 的加解密刺彩,不需要自己實現(xiàn)迷郑,也不需要調用第三方模塊,有內部模塊支持创倔,這個模塊就是 crypto嗡害。而且這些算法還不是使用 JS 寫的,而是用 C/C++ 實現(xiàn)的畦攘,然后通過 cypto 這個模塊暴露為 JavaScript 接口霸妹。那為什么不用 JS 寫呢?因為用純 JavaScript 代碼實現(xiàn)這些功能速度會非常的慢知押。
好了叹螟,我們來看看 NodeJS 常用算法和如何通過 API 使用這些常用算法的。
一台盯、哈希(hash)算法
哈希算法是摘要算法的一種首妖,可以根據(jù)你提供的內容,生成一段哈希值爷恳,只要提供的內容不變有缆,生成的哈希值就不會改變,而且從哈希值是不能反向推導出原始數(shù)據(jù)的(所以哈希算法也叫單向哈希算法)。
也就是說哈希算法只能加密不能反向解密棚壁”兀看下在 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 是根據(jù)原始文件臭猜,直接生成 MD5躺酒,這樣完全可以使用 MD5 批量生成,然后存儲起來撞庫蔑歌。為了防止這個現(xiàn)象羹应,在生成 MD5 的時候,我們會給原始內容次屠,手動加點東西量愧,專業(yè)點叫加一個密鑰,然后再生成 MD5帅矗。NodeJS 不需要我們這么干,因為有 createHmac煞烫,可以認為 Hmac 理解為用隨機數(shù)「增強」的哈希算法浑此,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 中如何實現(xiàn)呢料饥?
二蒲犬、NodeJS 中 base64 的編碼和解碼方式
我們需要一個引入一個新的 API 來完成這個。這個 API 就是 Buffer.from
岸啡,它可把數(shù)據(jù)例如字符串轉化成 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"); // 第二個參數(shù)就不能省略了
const decodedName = decodeBuffer.toString("utf-8");
console.log(decodedName); // CondorHero
這里還有個知識要注意,Buffer 的 toString 和平常見到的 toString 方法使用不同奋隶,Buffer 的 toString 方法有三個參數(shù)擂送,看下 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 字節(jié)的字符串
- key是加密密鑰蟀拷,根據(jù)選用的算法不同,密鑰長度也不同萍聊,對應關系如下:
- des-cbc 對應 8 位長度密鑰
- aes128 對應 16 位長度密鑰
- aes192 對應 24 位長度秘鑰
- aes256 對應 32 位長度密鑰
DES 是 Data Encryption Standard(數(shù)據(jù)加密標準)的縮寫问芬。它是由 IBM 公司研制的一種對稱密碼算法,美國國家標準局于1977年公布把它作為非機要部門使用的數(shù)據(jù)加密標準脐区,DES 是一種對稱加密算法愈诚,密匙長度必須是8的整數(shù)倍,在一些簡單的應用場景經常被使用牛隅。
為了網絡上信息傳輸?shù)陌踩ǚ乐沟谌礁`取信息看到明文)炕柔,發(fā)送發(fā)和接收方分別進行加密和解密,這樣信息在網絡上傳輸?shù)臅r候就是相對安全的媒佣。
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)默伍,微信小程序加密傳輸就是用這個加密算法的欢嘿,詳見 服務端獲取開放數(shù)據(jù)。
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 位训措。這就使加密的計算量很大伪节。為減少計算量光羞,在傳送信息時,常采用傳統(tǒng)加密方法與公開密鑰加密方法相結合的方式架馋,即信息采用對稱加密狞山,對稱加密的密鑰使用 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 橢圓曲線仿寫加解密函數(shù)驳遵,當你完成可以對照下面代碼查看自己的結果:
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);
// 從加密數(shù)據(jù)中分離出 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