從需求分析開始,詳解前端RSA加密與SHA256驗簽實踐

默認文件1615922773293.jpg

最近在復盤項目的時候,想到了之前做的關于前端加密與驗簽的需求,感覺這塊很少有文章介紹,所以我就把這塊內(nèi)容做一下整理,希望可以幫助到后面有這一塊需求的朋友。

后文你會看到:

  • 需求分析與技術選型
  • RSA 加密實踐
  • RSA + SHA256 驗簽實踐
  • 項目實踐中的存在的坑點
  • 如何處理依賴包體積過大問題

首先我們先從需求開始?

為什么要引入加密與驗簽?

這個要從兩件事情說起

  1. 公司委托第三方安全檢測掃描發(fā)現(xiàn)賬號登錄部分存在安全漏洞,也就是密碼安全問題;
  2. 我們發(fā)現(xiàn)項目的下單接口經(jīng)常被某平臺惡意刷單;

于是就有了我們的需求:

  1. 賬號登錄需要對密碼做加密處理;
  2. 下單接口要避免被惡意刷單影響

需求分析與技術選型

需求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算法原理(一)
RSA算法原理(二)

工作流程

為了方便大家理解,我先畫一個 RSA 加密的工作流程圖。

圖片

具體步驟

  • step1:用戶輸入信息,前端使用公鑰進行加密
  • step2:通過 http 請求將密文發(fā)送到后端
  • step3:后端使用私鑰講密文進行解密

這里大家應該注意到了兩個關鍵詞:

公鑰——可以公開的秘鑰,一般為前端使用,對文本加密使用

私鑰——不可公開的秘鑰,一般留給后端解密使用,對已加密文本進行解密

補充:其實也可以通過私鑰加密,公鑰解密,只要保持一部分私有就可以

輔助工具

圖片

我們看看生成好的樣子

// 公鑰
-----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 加密工作

  1. 安裝依賴
npm install jsencrypt --save
  1. 創(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();
  1. 加密處理
// 引入該方法
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ī)則

  1. 將接口參數(shù)轉(zhuǎn)換為字符串 A;
  2. 生成隨機字符串作為鹽值 S;
  3. 首先對接口參數(shù)字符串進行處理生成簽名,再將鹽值與之前生成的簽名合并,繼續(xù)使用 SHA256 算法,生成新簽名,公式為 sha256(sha256(A)+S);
  4. 使用 RSA 對鹽值進行加密;
  5. 將加密后的鹽值與生成的驗簽傳給后端,方便進行校驗;

代碼中的實現(xiàn)

這里我推薦使用 hash.js 中的 sha256 模塊來生成驗簽,為什么選擇它,后面會進行介紹。

  1. 安裝依賴
// SHA256 生成驗簽
npm install hash.js --save
  1. 生成簽名
// 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;
}
  1. 接口中使用
import myEncrypt from '@/common/myEncrypt';
// 驗簽加密處理 params 為接口參數(shù) jsonFlag 為參數(shù)類型
const {sk, sign} = myEncrypt.setSign(params, jsonFlag);

采坑盤點

  1. RSA 加密后的密文需要使用 encodeURIComponent 轉(zhuǎn)碼后傳給后端,因為密文中會存在 + 號之類的特殊符號,在接口傳輸過程中容易丟失;
  2. RSA 加密所使用的依賴 jsencrypt 因為包含 window 對象,我們再小程序中使用,需要進行兼容性處理
  3. 剛開在選擇提供 SHA256 算法的依賴包是最常用的 crypto-js,后面在小程序上使用的時候,包體積直接爆掉,crypto-js 的體積為 434KB,而現(xiàn)在我們用的 hash.js 的體積只有 41.7KB,其實中間我還嘗試過使用通過 js-sha256 來生成驗簽,雖然體積也比較小,但是經(jīng)常會出現(xiàn)驗簽后后端無法匹配上的問題,所以只能棄用。

后記

其實前端加密或者做接口驗簽都不會是絕對的安全,加密可以破解,簽名可以模仿,做這些工作只不過是給那些想搞事情的人,制造點困難。最后希望這篇文章可以幫助到大家,謝謝?。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容