基於NodeJS的HTTP server Plus 6:加密(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,以 == 結尾。bash

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 方法一樣會返回整個加密後的值。服務器

鏈式調用和分段加密
const crypto = require("crypto");

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

console.log(result); // 5d41402abc4b2a76b9719d911017c592複製代碼

因爲可使用 update 進行分段加密,就能夠結合流來使用,其實 crypto 的本質是建立 Transform 類型的轉化流,能夠將可讀流轉化成可寫流。cookie

對可讀流讀取的數據進行 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 加密,每次向服務器發送加密後的密鑰進行比對就能夠了,不至於對整個文件內容進行比較。

缺點:因爲規定使用 md5 的哈希算法加密,別人可使用一樣的算法對信息進行僞造,安全性不高。

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,第二個參數爲密鑰。ui

digest 方法生成的加密結果長度要大於 md5hex 生成的結果長度爲 64Base64 生成的結果長度爲 44,以 = 結尾。this

安全性高於 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,第一個參數爲私鑰,第二個參數爲解密的信息。

簽名

簽名與非對稱性加密很是相似,一樣有公鑰和私鑰,不一樣的是使用私鑰加密,對方使用公鑰進行解密驗證,以確保這段數據是私鑰的擁有者所發出的原始數據,且在網絡中的傳輸過程當中未被修改。


crypto 簽名

咱們還使用 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 徹底能夠實現咱們的加密需求,也能夠將上面的加密方案組合使用實現更復雜的加密方案。

相關文章
相關標籤/搜索