1. 程式人生 > >RSA的前端與node層應用

RSA的前端與node層應用

RSA的node層應用

由於前幾天有朋友問我RSA的使用方式,便花了一些時間去看了一下文件自己做了一下前端和node端

由來:由於表單提交的資料用f12或其他抓包工具可以看的一清二楚,所以前端在處理表單提交的時候會要求使用RSA/MD5等類似的加密工具加資料加密後再上傳

用到的類庫: node-rsa

  • 對稱性加密

    顧名思義,對稱性加密就是兩邊的加密、解密方式用的是同一個祕方

    你有兩把相同的鑰匙,你給了我一把鑰匙和一個帶鎖的箱子,我把資料放進箱子交給你,你用另一把鑰匙開啟鎖開啟箱子拿到資料

    兩邊的鑰匙都是一樣的,加密和解密用的是同一個key,稱之為對稱性加密

    • 優勢

      後臺只需要生成一個key,前臺拿到鑰匙直接加密資料
    • 劣勢

      如果有不懷好意的人通過非正常手段拿到了前端保留的公鑰便可以輕而易舉地破解加密的資料,加密便沒有了意義

       

  • 非對稱性加密

    加密、解密分別需要不同的祕方

    你有兩把鑰匙,你給了我一把鑰匙和一個帶鎖的箱子,我把資料放進箱子交給你,你用另一把鑰匙開啟鎖開啟箱子拿到資料

    你給我的鑰匙和你自己保留的鑰匙不一致,開箱子必須用你那把鑰匙,不同key,非對稱性加密

    • 優勢

      後臺生成兩個key,前臺拿到鑰匙後加密資料發給服務端,就算鑰匙被其他人破解,也無法解密加密後的資料,保證了資料的絕密性
    • 劣勢

      後臺管理上需要多維護一個欄位

 

前端程式碼(非對稱性加密)

實現邏輯

  • 先判斷本地有沒有快取publicKey(有就直接用,沒有就需要請求後臺一次)

  • 調獲取getKey介面

  • 拿到key後加密資料

  • 傳送加密後的資料給服務端

我們來看程式碼

import NodeRSA from 'node-rsa'
   
    ...
   
// Vue提交表單
submitForm(formName) {
      this.$refs[formName].validate(async valid => {
        if (valid) {
          // 判斷是否有公鑰儲存,如果沒有,則需要向服務端請求,一般存在sessionStorage/其他資料載體
          if (!this.publicKey) {
            // 向服務端傳送請求,獲得公鑰
            await getPublicKey()
              .then(res => {
                this.publicKey = res
              })
              .catch(err => {
                this.$message.error(err.message || err || "網路錯誤");
            });
          }
          // 對資料進行加密,並獲得加密後的資料
          const entryptData =  this.entryptData(this.models)
          
          // 拿到加密後的資料呼叫登入介面
          await RSALogin(
            entryptData,
          ).then(res => {
            this.$message({
              message: '登入成功',
              type: 'success'
            })
            // ... do something
          }).catch(err => {
             this.$message.error(err.message || err || '登入失敗')
          })
        } else {
          this.$message.error("提交錯誤");
          return false;
        }
      });
    },
    entryptData(props) {
      // 生成RSA物件
      const RSAkey = new NodeRSA({ b: 512 })
      // 匯入公鑰
      RSAkey.importKey(this.publicKey)
      console.log('資料物件加密前:', props)
      const afterEncrypt = Object.keys(props).reduce((init, key) => {
        
        const item = props[key]
        console.log('欄位加密前:', item)
​
        const entryptItem = RSAkey.encrypt(item, 'base64')
        console.log('欄位加密後:', entryptItem)
​
        init[key] = entryptItem
        return init
      }, {})
      console.log('資料物件加密後:', afterEncrypt)
      return afterEncrypt
    },

到這裡,我們就加密好了資料

 
{
  account: "c1gYgdczUyXIJnNtqW4ytoAJ0S9kqAUeUnIZgmmyCZQ5qAFz62jG9ERUE0axkyEUVL5rn/PJPPVWGMLwoF25Ew==",
  password: "m0OOeuGl9HE1/SNjsLD39OF7N00pT/g25Wx4OfNS0BnnaFaDtNnXr69cXStsaugMkfwgvNA56GhDGo5j5YZEPQ=="
}

node實現

實現邏輯

  • 判斷是否快取了key

  • 傳送給前端publicKey

  • 拿到加密後的資料

  • 解密

  • 呼叫正常的login介面

const pojo = require('../../helper/pojo')
const {
  success,
  failed,
} = pojo
const NodeRSA = require('node-rsa')
let myKey = {
  isInit: false,
};
const initKey = () => {
  const key = new NodeRSA({
    b: 512
  }); // 生成新的512位長度金鑰
​
  // 如果被初始化過就是解密需要的步驟
  if (myKey.isInit) {
    key.importKey(myKey.privateKey)
    return key
  }
​
  // 新生成的物件,保留公鑰和私鑰
  const publicDer = key.exportKey('public'); // 公鑰
  const privateDer = key.exportKey('private'); // 私鑰
  myKey = {
    publicKey: publicDer,
    privateKey: privateDer,
    isInit: true,
  }
}
​
// 獲取公鑰的介面
const getKey = async ctx => {
  try {
    let res;
    // 判斷是否存在RSA-key物件
    if (!myKey.isInit) {
      initKey() // 初始化key 物件
    }
    // 返回publicKey給前端
    res = success(myKey.publicKey)
    ctx.body = res
  } catch (err) {
    res = failed(err)
  }
}
​
// 登入介面
const login = async ctx => {
  let res;
  try {
    const val = ctx.request.body
    if (!myKey.isInit) {
      res = {
        retCode: 403,
        message: '金鑰無法匹配,請重新獲取金鑰'
      }
    } else {
      const myKey = initKey()// 通過私鑰獲取RSA物件
      const data = Object.keys(val).reduce((init, key) => {
        const beforeDencrypt = val[key] // 解密前
        const item = myKey.decrypt(beforeDencrypt, 'utf8') // 解密後
        init[key] = item
        return init
      }, {})
      // 呼叫真正的登入介面
      // do something
    }
    ctx.body = res
  } catch (err) {
    res = failed(err)
  }
​
}
module.exports = {
  login,
  getKey,
}

 

完整的程式碼和專案在這裡:前端Node端