NodeJS 加解密之 crypto 模塊

NodeJS 加解密之 crypto 模塊.png

我們前端一般會很少接觸加解密方面的知識,后端對這方面了解的可能比較多伍宦,但是作為一個不安分的前端给猾,絕對有必要學習下加解密的知識,因為現(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ù)選用的算法不同,密鑰長度也不同萍聊,對應關系如下:
    1. des-cbc 對應 8 位長度密鑰
    2. aes128 對應 16 位長度密鑰
    3. aes192 對應 24 位長度秘鑰
    4. 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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鸭丛,隨后出現(xiàn)的幾起案子竞穷,更是在濱河造成了極大的恐慌,老刑警劉巖鳞溉,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瘾带,死亡現(xiàn)場離奇詭異,居然都是意外死亡熟菲,警方通過查閱死者的電腦和手機看政,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抄罕,“玉大人允蚣,你說我怎么就攤上這事〈艋撸” “怎么了嚷兔?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長榨崩。 經常有香客問我,道長章母,這世上最難降的妖魔是什么母蛛? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮乳怎,結果婚禮上彩郊,老公的妹妹穿的比我還像新娘前弯。我一直安慰自己,他們只是感情好秫逝,可當我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布恕出。 她就那樣靜靜地躺著,像睡著了一般违帆。 火紅的嫁衣襯著肌膚如雪浙巫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天刷后,我揣著相機與錄音的畴,去河邊找鬼。 笑死尝胆,一個胖子當著我的面吹牛丧裁,可吹牛的內容都是我干的。 我是一名探鬼主播含衔,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼煎娇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了贪染?” 一聲冷哼從身側響起缓呛,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎抑进,沒想到半個月后强经,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡寺渗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年匿情,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片信殊。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡炬称,死狀恐怖,靈堂內的尸體忽然破棺而出涡拘,到底是詐尸還是另有隱情玲躯,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布鳄乏,位于F島的核電站跷车,受9級特大地震影響,放射性物質發(fā)生泄漏橱野。R本人自食惡果不足惜朽缴,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望水援。 院中可真熱鬧密强,春花似錦茅郎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至薪鹦,卻和暖如春掌敬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背距芬。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工涝开, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人框仔。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓舀武,卻偏偏與公主長得像,于是被迫代替她去往敵國和親离斩。 傳聞我的和親對象是個殘疾皇子银舱,可洞房花燭夜當晚...
    茶點故事閱讀 45,870評論 2 361

推薦閱讀更多精彩內容