
最近在復盤項目的時候,想到了之前做的關于前端加密與驗簽的需求,感覺這塊很少有文章介紹,所以我就把這塊內(nèi)容做一下整理,希望可以幫助到后面有這一塊需求的朋友。
后文你會看到:
- 需求分析與技術選型
- RSA 加密實踐
- RSA + SHA256 驗簽實踐
- 項目實踐中的存在的坑點
- 如何處理依賴包體積過大問題
首先我們先從需求開始?
為什么要引入加密與驗簽?
這個要從兩件事情說起
- 公司委托第三方安全檢測掃描發(fā)現(xiàn)賬號登錄部分存在安全漏洞,也就是密碼安全問題;
- 我們發(fā)現(xiàn)項目的下單接口經(jīng)常被某平臺惡意刷單;
于是就有了我們的需求:
- 賬號登錄需要對密碼做加密處理;
- 下單接口要避免被惡意刷單影響;
需求分析與技術選型
需求1:
這里的要求很明確,需要對登錄密碼做加密處理,所以接下來我們就要找一種合適的加密方案。
這里我們沒有過多糾結(jié),直接選擇目前比較常用且相對安全的RSA 非對稱加密。
需求2:
在分析需求之前,我們首先要看一下惡意刷單是怎么做的。
當對方了解了我們下單接口所需的參數(shù),以及基本的后端校驗規(guī)則后,其就可以直接跳過前端校驗,對參數(shù)稍作改動,通過程序頻繁調(diào)用下單接口。
要阻止這個行為,我們要做下面兩點:
第一點 接口節(jié)流處理,這個是后端處理的,這里不做介紹;
第二點 下單接口驗簽,這個需要前端生成簽名和后端進行驗簽比對,具體的前后端驗簽比對的;
接下來的問題就是要再選擇一種適合做驗簽的技術方案,目前處理驗簽的方案有很多,常見的例如MD5 、SHA1 、SHA256等。選擇哪一種,我們必須要結(jié)合到具體的使用場景去分析。
首先,我需要處理的這個項目項目是服務 3000 多家景區(qū)的 Saas 平臺,下單接口的并發(fā)量比較大,所以在并發(fā)性能上有所要求(這里的并發(fā)的壓力是在后端)。其次,這個需求的根本是為了避免被刷單,所以安全性也是重要的衡量標準。
接下來,后端同事根據(jù)約定的簽名規(guī)則使用 MD5、SHA1、SHA-256 簽名,在相同次數(shù)下生成簽名的耗時數(shù)據(jù)對比表如下:
| # | 100次(毫秒) | 10000次(毫秒) | 1000000次(毫秒) |
|---|---|---|---|
| MD5 | 161 | 9018 | 883387 |
| SHA1 | 166 | 8980 | 886486 |
| SHA256 | 2121 | 10590 | 931271 |
通過上面的數(shù)據(jù)對比表我們可以看出:在執(zhí)行字數(shù)比較少的時候,MD5 與SHA1 執(zhí)行效率明顯更優(yōu),但是在執(zhí)行次數(shù)到達10000次以上之后,三者執(zhí)行的總時間差距并沒有拉大。最后通過安全性和服務器承受能力兩個維度考量 ,最終確定使用 SHA256 來生成簽名。雖然它相對會比較損耗性能,但是目前的后端服務器是完全可以承受的,另外它也可以帶來更好的安全性。
這里,我們就確定好了具體的技術方案,接下來進入具體加密與驗簽實踐
RSA 加密實踐
簡介
1977年,三位數(shù)學家 Rivest、Shamir 和 Adleman 設計了一種算法,可以實現(xiàn)非對稱加密。這種算法用他們?nèi)齻€人的名字命名,叫做RSA算法。從那時直到現(xiàn)在,RSA算法一直是最廣為使用的"非對稱加密算法"。毫不夸張地說,只要有計算機網(wǎng)絡的地方,就有RSA算法。
更多 RSA 算法原理,可以打開下方傳送門:
工作流程
為了方便大家理解,我先畫一個 RSA 加密的工作流程圖。
具體步驟:
- step1:用戶輸入信息,前端使用公鑰進行加密
- step2:通過 http 請求將密文發(fā)送到后端
- step3:后端使用私鑰講密文進行解密
這里大家應該注意到了兩個關鍵詞:
公鑰——可以公開的秘鑰,一般為前端使用,對文本加密使用
私鑰——不可公開的秘鑰,一般留給后端解密使用,對已加密文本進行解密
補充:其實也可以通過私鑰加密,公鑰解密,只要保持一部分私有就可以
輔助工具
- 工具1:RSA 公私鑰生成網(wǎng)站:點擊這里
我們看看生成好的樣子
// 公鑰
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5iTKIXHGuDNG9x
......
3QIDAQAB
-----END PUBLIC KEY-----
// 私鑰
-----BEGIN PRIVATE KEY-----
MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQDmJMohcc
......
8jKcsEAozBoeXJiSx0+D
-----END PRIVATE KEY-----
這里已經(jīng)加好了公私鑰的注釋,這個我們需要保留,后面再開發(fā)中會使用到。
- 工具2:RSA 加密與解密校驗網(wǎng)站:點擊這里
加密測試:選擇 RSA公鑰加密 >> 輸入公鑰 >> 輸入待加密內(nèi)容 >> 加密結(jié)果
圖中我們輸入了hello world加密得到:
cTX3VGKEjxGYl0d35JfENIPgtB3amNEWbjjlMw8vMcIUCzM6gHvZwbTjkHvuKDKJGQf0Upcb1zRVsDwWYb8MBYlOia72pme29M6UuRyt4FYy0mL8GTLNJUMcGP+lI9jb2tQ7NmToufV2RI9c666P6B+xx5bT4vHEgI+hs4xKny8=
解密測試:選擇 RSA私鑰解密 >> 輸入私鑰 >> 輸入待解密內(nèi)容 >> 原文本
解密之后,我們重新見到了原文:hello world ?
代碼中的實現(xiàn)
這里我們需要借助 JSEncrypt 來完成前端的 RSA 加密工作
- 安裝依賴
npm install jsencrypt --save
- 創(chuàng)建我們的類方法
// 新建文件 /common/myEncrypt.js
import { JSEncrypt } from 'jsencrypt';
class myEncrypt {
constructor() {
// 這里不要去掉注釋
this.pubsKey = `
-----BEGIN PUBLIC KEY-----
SIb3DQEBAQUAA4GNADCBiQKBgQDI5kmdW9rmxQlTraZ6Wx+C7kYR......省略
-----END PUBLIC KEY-----
`;
}
/**
* RSAj加密
* @param {String} text 需要加密的文本
* @returns {String} 加密后的文本
*/
setRSA(text) {
// 新建JSEncrypt對象
const encryptor = new JSEncrypt();
// 設置公鑰
encryptor.setPublicKey(this.pubsKey);
// 加密數(shù)據(jù)
const resultText = encryptor.encrypt(text);
// 轉(zhuǎn)碼,避免特殊符號在傳輸過程中丟失
return encodeURIComponent(resultText);
}
}
export default new myEncrypt();
- 加密處理
// 引入該方法
import encrypt from '@/common/myEncrypt';
// 對用戶密碼進行加密
const password = '996ICU';
const rsaPass = encrypt.setRSA(password);
注意:在微信小程序中無法使用 window 對象,直接引用依賴處理會報錯,這里需要對 jsencrypt 進行兼容性改造,代碼量很大,這里就不貼了。兼容方案看這里?在小程序使用jsEncrypt.js
SHA256 生成簽名
簡介
SHA-2,名稱來自于安全散列算法2(英語:Secure Hash Algorithm 2)的縮寫,一種密碼散列函數(shù)算法標準,由美國國家安全局研發(fā),由美國國家標準與技術研究院(NIST)在2001年發(fā)布。屬于SHA算法之一,是SHA-1的后繼者。其下又可再分為六個不同的算法標準,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。
更多 SHA256 算法原理看這里?一文讀懂SHA256算法原理及其實現(xiàn)
工作流程
話不多說,直接上圖
具體步驟:
- step1:用戶提交訂單,前端生成簽名
- step2:后端在網(wǎng)關層,依據(jù)雙方約定規(guī)則生成簽名,進行比對
- step3:比對通過進行正常下單流程
這里重點講一下,前端部分生成簽名的規(guī)則
- 將接口參數(shù)轉(zhuǎn)換為字符串 A;
- 生成隨機字符串作為鹽值 S;
- 首先對接口參數(shù)字符串進行處理生成簽名,再將鹽值與之前生成的簽名合并,繼續(xù)使用 SHA256 算法,生成新簽名,公式為
sha256(sha256(A)+S); - 使用 RSA 對鹽值進行加密;
- 將加密后的鹽值與生成的驗簽傳給后端,方便進行校驗;
代碼中的實現(xiàn)
這里我推薦使用 hash.js 中的 sha256 模塊來生成驗簽,為什么選擇它,后面會進行介紹。
- 安裝依賴
// SHA256 生成驗簽
npm install hash.js --save
- 生成簽名
// common/myEncrypt.js
// myEncrypt 類中
/**
* 生成驗簽
* @param {Object} params 請求參數(shù)
* @param {Boolean} isJson 是否為json類型請求
*/
setSign(params, isJson) {
// 隨機字符串,這里省略生成規(guī)則
const salt = ..sfoshx2434..;
// 對接口參數(shù)進行處理,這里可以自己根據(jù)與后端預定的規(guī)則處理一下
const paramsStr = dataToString(params, isJson);
const sign = this.signData(paramsStr, salt);
const sk = this.setRSA(salt);
return {
sk,
sign,
};
}
/**
* 按照約定規(guī)則生成驗簽
* @param {String} data 需要加密的數(shù)據(jù)
* @param {String} salt 鹽值
*/
signData(data, salt) {
const once = sha256().update(data).digest('hex');
const seconed = sha256().update(once + salt).digest('hex');
return seconed;
}
- 接口中使用
import myEncrypt from '@/common/myEncrypt';
// 驗簽加密處理 params 為接口參數(shù) jsonFlag 為參數(shù)類型
const {sk, sign} = myEncrypt.setSign(params, jsonFlag);
采坑盤點
- RSA 加密后的密文需要使用 encodeURIComponent 轉(zhuǎn)碼后傳給后端,因為密文中會存在 + 號之類的特殊符號,在接口傳輸過程中容易丟失;
- RSA 加密所使用的依賴 jsencrypt 因為包含 window 對象,我們再小程序中使用,需要進行兼容性處理;
- 剛開在選擇提供 SHA256 算法的依賴包是最常用的 crypto-js,后面在小程序上使用的時候,包體積直接爆掉,crypto-js 的體積為 434KB,而現(xiàn)在我們用的 hash.js 的體積只有 41.7KB,其實中間我還嘗試過使用通過 js-sha256 來生成驗簽,雖然體積也比較小,但是經(jīng)常會出現(xiàn)驗簽后后端無法匹配上的問題,所以只能棄用。
后記
其實前端加密或者做接口驗簽都不會是絕對的安全,加密可以破解,簽名可以模仿,做這些工作只不過是給那些想搞事情的人,制造點困難。最后希望這篇文章可以幫助到大家,謝謝?。