最近在做一個node項目,需要對前端傳遞給node端的敏感數據進行加密,並在node端對該加密數據進行解密;因為在做node項目之前,與后端配合開發過類似的需求,即前端加密后端解密;所以就嘗試采用RSA非對稱加密算法來實現。由於第一次采用RSA來完成加解密的整個過程,遇到了不少坑;不過由於種種原因,最后采用了AES的加密方式;下面就來說說前端加解密實現方案。
RSA加解密算法
實現思路
當然首先想到采用的加解密算法就是RSA,其關鍵在於算法的公鑰/秘鑰。其主要用法:
- 算法生成一份公鑰和私鑰,其中公鑰是公開的,所有人都可以知道,私鑰是保密的
- 用公鑰解密,要用私鑰解密
於是,基於RSA算法來實現加解密,找到了對應的瀏覽器端庫jsencrypt和node端的庫node-rsa來實現具體的功能。
具體實現思路:
使用jsencrypt在前端實現用公玥加密,使用node-rsa在node端用私鑰解密。
遇到的坑
由於采用的是RSA算法,所以需要前后端約定具體的公鑰和私鑰。怎么生存公鑰私鑰呢?
於是根據jsencrypt庫的介紹,使用openssl方式來生成對應的公鑰和私鑰。於是生成的公鑰和私鑰大概是如下樣子:
// 私鑰
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQ
WMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNR
aY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQAB
AoGAfY9LpnuWK5Bs50UVep5c93SJdUi82u7yMx4iHFMc/Z2hfenfYEzu+57fI4fv
xTQ//5DbzRR/XKb8ulNv6+CHyPF31xk7YOBfkGI8qjLoq06V+FyBfDSwL8KbLyeH
m7KUZnLNQbk8yGLzB3iYKkRHlmUanQGaNMIJziWOkN+N9dECQQD0ONYRNZeuM8zd
8XJTSdcIX4a3gy3GGCJxOzv16XHxD03GW6UNLmfPwenKu+cdrQeaqEixrCejXdAF
z/7+BSMpAkEA8EaSOeP5Xr3ZrbiKzi6TGMwHMvC7HdJxaBJbVRfApFrE0/mPwmP5
rN7QwjrMY+0+AbXcm8mRQyQ1+IGEembsdwJBAN6az8Rv7QnD/YBvi52POIlRSSIM
V7SwWvSK4WSMnGb1ZBbhgdg57DXaspcwHsFV7hByQ5BvMtIduHcT14ECfcECQATe
aTgjFnqE/lQ22Rk0eGaYO80cc643BXVGafNfd9fcvwBMnk0iGX0XRsOozVt5Azil
psLBYuApa66NcVHJpCECQQDTjI2AQhFc1yRnCU/YgDnSpJVm1nASoRUnU8Jfm3Oz
uku7JUXcVpt08DFSceCEX9unCuMcT72rAQlLpdZir876
-----END RSA PRIVATE KEY-----
// 公鑰
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlOJu6TyygqxfWT7eLtGDwajtN
FOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76
xFxdU6jE0NQ+Z+zEdhUTooNRaY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4
gwQco1KRMDSmXSMkDwIDAQAB
-----END PUBLIC KEY-----
於是,使用生成的公鑰,前端使用jsencrypt提供的加密api來對敏感數據加密
var publickey = `-----BEGIN PUBLIC KEY----- xxxxxx -----END PUBLIC KEY-----`;
var encrypt = new JSEncrypt();
encrypt.setPublicKey(publickey);
var encryptdata = encrypt.encrypt(data);
node端使用node-rsa來完成解密:
const privatekey = `-----BEGIN RSA PRIVATE KEY----- xxxx -----END RSA PRIVATE KEY-----`
const rsa = new NodeRSA(privatekey, 'pkcs8-private-pem', {encryptionScheme: 'pkcs1'});
const decryptdata = rsa.decrypt(data, 'utf8');
執行到這里,node-rsa一直報下面的錯誤:
Error: Error during decryption (probably incorrect key). Original error: Error: Incorrect data or key
意思就是對應的解密私鑰不正確,查看node-rsa有關公鑰私鑰,他是有規定的,具體如下:

可以看出,node-rsa的公鑰私鑰的起始字符串有以下兩種:
-
pkcs1: 公鑰(-----BEGIN RSA PUBLIC KEY-----)和私鑰(-----BEGIN RSA PRIVATE KEY-----)
-
pkcs8: 公鑰(-----BEGIN PUBLIC KEY-----) 和 私鑰 (-----BEGIN PRIVATE KEY-----)
不管node-rsa規定的那種私鑰scheme,都與我們之前使用openssl生成的私鑰字符串的開始結束字符不同,導致node-rsa認不出對應的私鑰。
那么,我們是否可以對openssl生成的私鑰的起始字符串按照node-rsa進行修改呢,我們簡單試一下,結果產生如下錯誤:
InvalidAsn1Error: Expected 0x30: got 0x2
所以,既然不能按照openssl生成的公鑰私鑰方式,那么能否有其他方式來生成呢?通過google發現,可以通過node-rsa的相關api來生成對應的公鑰私鑰,並且jsencrypt庫也可以通過其生成的公鑰來解密。node-rsa對應生成公鑰私鑰如下:
//1.創建RSA對象,並指定 秘鑰長度
var key = new NodeRSA({ b: 512 });
key.setOptions({ encryptionScheme: 'pkcs1' });//指定加密格式
//2.生成 公鑰私鑰,使用 pkcs8標准,pem格式
var publicPem = key.exportKey('pkcs8-public-pem');//制定輸出格式
var privatePem = key.exportKey('pkcs8-private-pem');
console.log(pkcsType+'公鑰:\n',publicPem);
console.log(pkcsType+'私鑰:\n', privatePem);
這樣,通過生成的公鑰,前端使用jsencrypt庫來加密,node端使用node-rsa根據私鑰來解密,解決了之前遇到問題。
AES加密算法
在使用RSA加密算法前,使用過前端加密庫crypto-js來完成加解密,因為:
-
它算是比較成熟且github star數也比較多,使用起來比較放心。
-
crypto-js也提供了多種加密算法,唯獨不包含RSA加密算法。
-
該庫是前后端通用的庫,避免引入多個庫
基於此原因,選用了crypto-js提供的AES加密算法來完成需求。
具體的實現方式如下
瀏覽器端加密
具體代碼如下:
import CryptoJS from 'crypto-js';
const AES_KEY = "qq3217834abcdefg"; //16位
const AES_IV = "1234567890123456"; //16位
function aes_encrypt(plainText) {
var encrypted = CryptoJS.AES.encrypt(plainText, CryptoJS.enc.Utf8.parse(AES_KEY), {iv: CryptoJS.enc.Utf8.parse(AES_IV)});
return CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
}
data = 'my message';
encrypt_data = aes_encrypt(data);
console.log(encrypt_data);
node端使用瀏覽器端同樣的key和iv來解密
對應的node端代碼如下:
function aes_decrypt(ciphertext) {
var decrypted = CryptoJS.AES.decrypt(ciphertext, CryptoJS.enc.Utf8.parse(AES_KEY), {iv: CryptoJS.enc.Utf8.parse(AES_IV)});
return decrypted.toString(CryptoJS.enc.Utf8);
}
const encrypt_data = ctx.cookie('data');
cibst decrypt_data = aes_decrypt(encrypt_data);
console.log(decrypt_data);
至此,前后端加解密就大功告成了。
前端加密算法的安全性
上面兩種方式都能實現前后端的加密解密,就其安全性而言存在差別,具體可以參考如下對比表格:
| 加密算法 | 實現方式 | 安全性 |
|---|---|---|
| RSA | 前后端約定統一的公鑰私鑰,前端用暴露的公鑰加密,私鑰存在后server端 | 私鑰存在server端,即使暴露公鑰;加密是安全的 |
| AES | 前端后端都使用同樣的key(或者還有iv)來進行加解密,key同時暴露在前后端 | 由於后端使用同樣的key來解密,由於前端暴露了key,加密不安全 |
對於AES這種將加密key暴露在前端,不夠安全;但是前端加密是防不了小人的,如果真要防,可以將加密算法的js文件進行壓縮加密,不斷更新的手段來使js文件難以獲取,讓攻擊者難以獲取加密算法來防止。
參考文獻
1、jsencrypt
2、PHP 和 Web 端對稱加密傳輸|JSEncrypt|CryptoJS
3、node-rsa非對稱加密
4、js 前端 AES 及 RSA 加解密
