前端加密cryptojs與JSEncrypt使實例詳解

單向散列函數

在網站項目中,有時我們需要對傳給後端的數據,比如 token 等進行加密處理。本文是對幾種常見的前端加密方法,以及如何使用開源的加密庫 crypto-js、JSEncrypt 來實現它們的分享。

又稱為消息摘要算法,是不可逆的加密算法,即對明文進行加密後,無法通過得到的密文還原回去得到明文。一般所謂的比如 MD5 破解,其實是不斷的嘗試用不同的明文進行加密,直到得到的加密結果一致。

常見的單項散列函數有 MD5、SHA1、SHA256、SHA512 ,以及它們之前加上 Hmac(Keyed-hash message authentication codes) 後的 HmacMD5、HmacSHA1 等。下面以 MD5 為例重點介紹,其它幾種則可以舉一反三,不多贅述:

MD5

簡單介紹

MD5 長度固定,不論輸入的內容有多少字節,最終輸出結果都為 128 bit,即 16 字節。這也就解釋瞭為什麼 MD5 以及其它單向散列函數是不可逆的 —— 輸出定長代表會有數據丟失。

通常,我們可以用 16 進制字面值來表示它,每 4 bit 以 16 進制字面值顯示,得到的是一個長度為 32 位的字符串。註意,MD5 等單向散列函數具有高度的離散性,意思是隻要輸入的明文不一樣,得到的結果就完全不一樣,哪怕是僅僅多瞭一個空格。

使用場景

MD5 有下面幾種使用場景:

  • 可以用來做密碼的保護。比如可以不直接存儲用戶的密碼,而是存儲密碼的 MD5 結果。但是現在一般認為用 MD5 來做加密是不太安全的,更多是利用 MD5 的高度離散性特點,用它來做數字簽名、完整性校驗,雲盤秒傳等;
  • 數字簽名。我們可以在發佈程序時同時發佈其 MD5。這樣,別人下載程序後自己計算一遍 MD5,一對比就知道程序是否被篡改過,比如植入瞭木馬。
  • 完整性校驗。比如前端向後端傳輸一段非常大的數據,為瞭防止網絡傳輸過程中丟失數據,可以在前端生成一段數據的 MD5 一同傳給後端,這樣後端接收完數據後再計算一次 MD5,就知道數據是否完整瞭。

使用 crypto-js 進行 MD5 加密

為瞭方便,我采用瞭在普通 html 頁面直接引入 cdn 的方式來引入 crypto-js。

<script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>

引入後,我們就能得到 CryptoJS 這個對象,它包含瞭很多方法,打印結果如下圖:

其中就定義瞭 MD5 方法和 algo 對象,借助它們,可以分別得到輸入數據的 MD5 結果:

CryptoJS.MD5()

CryptoJS.MD5('2022JueJin').toString()

結果為 84231025843afb62d818bf4f21612051。“2022JueJin” 就是我們需要加密的明文數據,得到的結果需要轉為字符串輸出,不然會是一個對象,而不是 16 進制字面值顯示的 32 位字符串(由 0 – 9 和 a – f 組成)。

  • WordArray

傳入CryptoJS.MD5() 的參數除瞭字符串外,還可以是 CryptoJS 定義的一種叫做 WordArray 的數據類型。

const wordArray = CryptoJS.enc.Utf8.parse('2022JueJin')
CryptoJS.MD5(wordArray).toString()

上面這段代碼將 utf8 字符串先轉成瞭 WordArray 對象,再將其傳給 CryptoJS.MD5(),最終得到的結果如果打印輸出的話也是 84231025843afb62d818bf4f21612051。

enc 可以看成是 Encode(編碼) 的縮寫。Utf8 還能換成 Latin1HexBase64 等。如果想將一個 WordArray 對象轉換回 utf8 字符,可以執行

CryptoJS.enc.Utf8.stringify(wordArray)

一般情況下,消息摘要算法得到的結果都是以 16 進制字面值表示,如果想要得到 Base64,可以將加密結果通過 CryptoJS.enc.Base64.stringify() 轉換:

const base64 = CryptoJS.enc.Base64.stringify(CryptoJS.MD5('2022JueJin'))
console.log(base64) // hCMQJYQ6+2LYGL9PIWEgUQ==

algo

如果需要生成 MD5 的數據是個大文件,一般我們可以把大文件分為多段。采用下面的方式,先使用 CryptoJS.algo.MD5.create()創建一個對象,命名為 hasher。然後將數據一段段的傳入 hasher.update() 處理:

const hasher = CryptoJS.algo.MD5.create()
hasher.update('2022')
hasher.update('JueJin')
const hash = hasher.finalize()
console.log(hash.toString())

最後調用 hasher.finalize() 表示傳輸完成,finalize() 裡也可以傳入數據。打印得到的結果也是 84231025843afb62d818bf4f21612051。如果想清除之前的 update,可以調用 hasher.reset()

上面介紹的單向散列函數嚴格來說並不是加密算法,更多是用於簽名。項目中需要進行加密的時候,最好采用下面介紹的加密算法。它們的加密和解密過程是可逆的,分為對稱加密和非對稱加密。

對稱加密算法 AES

所謂對稱,指的是加密和解密使用的是相同的秘鑰,常見的有 DES、3DES 、RC4、RC5、RC6 和 AES。下面以我在公司最近的項目中使用的 AES 為重點進行介紹。

英文全稱為 Advanced Encryption Standard,即高級加密標準的意思。它的推出,用於取代已經被證明不安全的 DES 算法。AES 屬於分組加密算法,因為它會把傳入的明文數據以 128 bit 為一組分別處理。其秘鑰長度則可以是 128、192 和 256 bit。AES 或者說對稱加密算法的優點是速度快,缺點就是不安全,因為網站上的代碼和秘鑰都是明文,別人隻要得到瞭加密結果再結合秘鑰就能得到加密的數據瞭。

使用 crypto-js 進行 AES 加密

加密

我們將 “JueJin2022” 通過 AES 加密,得到的將是一個對象,我們需要通過 toString() 將其轉成字符串輸出,最終得到的是一個以 base64 編碼的 “5yOOaUK1NSxVcRc8TA1fZw==”,代碼如下:

const message = CryptoJS.enc.Utf8.parse('JueJin2022')
const secretPassphrase = CryptoJS.enc.Utf8.parse('0123456789asdfgh')
const iv = CryptoJS.enc.Utf8.parse('0123456789asdfgh')
const encrypted = CryptoJS.AES.encrypt(message, secretPassphrase, {
  mode: CryptoJS.mode.CBC,
  paddding: CryptoJS.pad.Pkcs7,
  iv
}).toString()
console.log(encrypted)

CryptoJS.AES.encrypt() 可以傳入 3 個參數: 第 1 個為需要加密的明文; 第 2 個是秘鑰,長度可以是 128、192 或 256 bit; 第 3 個為一個配置對象,可以添加一些配置。常見的配置屬性有:

  • mode:加密模式。默認為 CBC,還支持且常用的是 ECB。CBC 模式需要偏移向量 iv,而 ECB 不需要。
  • paddding:填充方式。默認為 Pkcs7;
  • iv:偏移向量 ;

註意,明文、秘鑰和偏移向量一般先用諸如 CryptoJS.enc.Utf8.parse() 轉成 WordArray 對象再傳入,這樣做得到結果與不轉換直接傳入是不一樣的。

解密

解密的寫法和加密差不多,隻是把 encrypt 方法名改為 decrypt,然後傳入的第 1 個參數由明文替換為密文,最後將之前轉換明文的方式傳入 toString() 即可:

const secretPassphrase = CryptoJS.enc.Utf8.parse('0123456789asdfgh')
const iv = CryptoJS.enc.Utf8.parse('0123456789asdfgh')
const decrypted = CryptoJS.AES.decrypt(
  '5yOOaUK1NSxVcRc8TA1fZw==',
  secretPassphrase,
  {
    mode: CryptoJS.mode.CBC,
    paddding: CryptoJS.pad.Pkcs7,
    iv: '0123456789asdfgh'
  }
).toString(CryptoJS.enc.Utf8)
console.log(decrypted) // JueJin2022

註:如果之前在加密時沒有將明文進行 parse 而是直接傳入的,那麼在解密時,傳入 toString() 的解析方式就是寫默認的 CryptoJS.enc.Utf8

非對稱加密RSA

所謂的非對稱,即加密和解密用的不是同一個秘鑰。比如用公鑰加密,就要用私鑰解密。非對稱加密的安全性是要好於對稱加密的,但是性能就比較差瞭。

非對稱加密算法中常用的就是 RSA 瞭。它是由在 MIT 工作的 3 個人於 1977 年提出,RSA 這個名字的由來便是取自他們 3 人的姓氏首字母。我們在訪問 github 等遠程 git 倉庫時,如果是使用 SSH 協議,需要生成一對公私秘鑰,就可以使用 RSA 算法。

使用 JSEncrypt 進行 RSA 加密

我們依舊是采用 cdn 方式直接在頁面中引入 JSEncrypt 庫:

<script src="https://cdn.bootcdn.net/ajax/libs/jsencrypt/3.2.1/jsencrypt.min.js"></script>

使用的代碼非常簡單。首先需要 new 一個實例對象出來,然後將通過 openssl 生成的公鑰傳給實例對象的 setKey 方法,之後隻需要把要加密的明文傳給實例的 encrypt() 進行加密即可:

const crypt = new JSEncrypt()
crypt.setKey('openssl 生成的公鑰')
const text = 'JueJin2022'
const enc = crypt.encrypt(text)
console.log(enc)

生成的密文是一段 base64 格式的 1024 位 RSA 私鑰。

使用 JSEncrypt 進行 RSA 解密

解密就是把私鑰傳給實例的 setKey(),之後把密文傳給 decrypt() 進行解密即可:

const crypt = new JSEncrypt()
crypt.setKey('openssl 生成的私鑰')
const enc = 密文
const dec = crypt.decrypt(enc)
console.log(dec)

註意,setKey 有 2 個別名: 如果傳入的是私鑰,可以用 setPrivateKey() 替換 setKey(); 如果傳入的是公鑰,可以用 setPublicKey() 替換 setKey()

OpenSSL

從上面的內容可知,JSEncrypt 的加解密過程需要用到 OpenSSL 來生成秘鑰,OpenSSL 是一個開源的軟件,它是對 SSL 協議的實現。能夠用於生成證書、證書簽名、生成秘鑰和加解密等。比如我公司最近的項目有個需求是要在本地開發時,localhost 使用 https 協議,就有用到 openssl。

安裝

可以去 slproweb.com/products/Wi… 選擇對應版本安裝:

然後在環境變量中添加配置,例如我把 openssl 安裝在瞭 D:\OpenSSL-Win64,就將 D:\OpenSSL-Win64\bin 添加到 Path 中:

生成私鑰

我們可以在想要保存秘鑰的文件夾啟動命令行工具,並輸入以下命令生成秘鑰文件:

openssl genrsa -out rsa_1024_priv.pem 1024
  • genrsa: 生成 RSA 私有密鑰;
  • -out:生成的密鑰文件,後面配置的是我們生成的密鑰文件的名字,可從中提取公鑰;
  • 1024:生成的秘鑰長度為 1024 bit;

生成的秘鑰文件如下:

可以通過 cat rsa_1024_priv.pem 查看秘鑰內容,然後復制粘貼給上面的 crypt.setKey()

註意:秘鑰必須寫成一行以 -----BEGIN PRIVATE KEY----- 開頭,以 -----END PRIVATE KEY----- 結尾。像下面這樣:

crypt.setKey(  '—–BEGIN PRIVATE KEY—–MIICdFCQBj…中間省略…D3t4NbK1bqMA=—–END PRIVATE KEY—–')

生成公鑰

生成上面的私鑰對應的公鑰的命令為

openssl rsa -pubout -in rsa_1024_priv.pem -out rsa_1024_pub.pem
  • rsa:處理 RSA 密鑰的格式轉換等問題;
  • -pubout:指定輸出文件是公鑰;
  • -in:輸入文件,也就是我們上面生成的私鑰文件;
  • -out:輸出文件,也就是是我們要生成的公鑰文件;

查看同樣是使用 cat 命令。

以上就是前端加密cryptojs與JSEncrypt使實例詳解的詳細內容,更多關於前端加密cryptojs JSEncrypt的資料請關註WalkonNet其它相關文章!

推薦閱讀: