加密簡介
加密是以某種演算法改變原有的資訊資料,使得未授權使用者即使獲得了已加密資訊,因不知解密的方法,無法得知資訊真正的含義,通過這種方式提高網路資料傳輸的安全性,加密演算法常見的有雜湊演算法、HMAC 演算法、簽名、對稱性加密演算法和非對稱性加密演算法,加密演算法也分為可逆和不可逆,比如 md5
就是不可逆加密,只能暴力破解(撞庫),我們在 NodeJS 開發中就是直接使用這些加密演算法,crypto
模組提供了加密功能,包含對 OpenSSL
的雜湊、HMAC、加密、解密、簽名以及驗證功能的一整套封裝,核心模組,使用時不需安裝。
雜湊演算法
雜湊演算法也叫雜湊演算法,用來把任意長度的輸入變換成固定長度的輸出,常見的有 md5
、sha1
等,這類演算法實現對原資料的轉化過程是否能被稱為加密備受爭議,為了後面敘述方便我們姑且先叫做加密。
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
一般會進行多次加密。
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,常用的引數有 hex
和 Base64
,hex
代表十六進位制,加密後長度為 32
,Base64
的結果長度為 24
,以 ==
結尾。
const crypto = require("crypto");
let md5 = crypto.createHash("md5");
let md5Sum = md5.update("hello");
let result = md5Sum.digest("hex");
console.log(result); // 5d41402abc4b2a76b9719d911017c592複製程式碼
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
型別的轉化流,可以將可讀流轉化成可寫流。
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);
});複製程式碼
md5
加密摘要後的暗文,接收端拿到資料以後將明文摘要按照相同的 md5
演算法加密後與暗文摘要對比驗證,目的是防止資料傳輸過程中被劫持並篡改。md5
加密,每次向伺服器傳送加密後的金鑰進行比對就可以了,不至於對整個檔案內容進行比較。md5
的雜湊演算法加密,別人可以使用同樣的演算法對資訊進行偽造,安全性不高。Hmac 演算法
1、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
,為加密的演算法,常用 sha1
和 sha256
,第二個引數為金鑰。
digest
方法生成的加密結果長度要大於 md5
,hex
生成的結果長度為 64
,Base64
生成的結果長度為 44
,以 =
結尾。
md5
,通過金鑰來加密,不知道金鑰無法破解,缺點是金鑰傳輸的過程容易被劫持,可以通過一些生成隨機金鑰的方式避免。2、建立金鑰的方法
可以安裝 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.key
和 rsa_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
方法有三個引數,第一個引數為公鑰,第二個引數為被驗證的簽名,第三個引數為生成簽名時的格式,返回為布林值,即是否通過驗證。
總結
各種專案在資料傳輸時根據資訊的敏感度以及用途進行不同的加密演算法和加密方式,在 NodeJS 中,crypto
的 API 完全可以實現我們的加密需求,也可以將上面的加密方案組合使用實現更復雜的加密方案。