sroxck

sroxck

前端RSA與SM4加密流程

公司最新需求居然讓前端拿著秘鑰來解密.... 真是防君子不防小人啊,這裡記錄一下前端使用 RSA,AES,SM4 等加密算法的加解密流程,以及 hooks 的封裝

私鑰與公鑰#

  1. 公鑰:
    可以公開給任何人,用於加密數據,使用公鑰加密的數據,只有對應的私鑰才能解密
  2. 私鑰:
    必須保密,僅由密鑰的擁有者持有,用於解密數據。使用私鑰解密的數據,只有使用相應的公鑰加密過的內容才能被解密

工作流程#

  1. 生成密鑰對:生成一對密鑰,包括公鑰和私鑰。
  2. 數據加密:發送方使用接收方的公鑰加密數據。
  3. 數據傳輸:加密後的數據可以安全地通過不安全的通道發送。
  4. 數據解密:接收方使用自己的私鑰解密收到的數據。

前端加密庫的使用#

RSA 使用 jsencrypt 或 node-rsa#

import JSEncrypt from "jsencrypt";
// 生成 RSA 密鑰對
export const generateKeyPair = () => {
  const key = new NodeRSA({ b: 512 });
  return {
    publicKey: key.exportKey("public"),
    privateKey: key.exportKey("private"),
  };
};

// RSA 加密函數
export const rsaEncrypt = (data, publicKey) => {
  const key = new NodeRSA(publicKey);
  return key.encrypt(data, "base64");
};

// RSA 解密函數
export const rsaDecrypt = (encryptedData, privateKey) => {
  const key = new NodeRSA(privateKey);
  return key.decrypt(encryptedData, "utf8");
};

/**
 * rsa解密 JSEncrypt
 * @param options rsa的私鑰key與密文content
 * @returns 明文
 */
function rsaDecrypt(options) {
  const { key, content } = options;
  const decrypt = new JSEncrypt();
  decrypt.setPrivateKey(key);
  return decrypt.decrypt(content) || "";
}

AES 使用 crypto#

後端使用 AES 加密,前端使用 AES 解密時大概率會出問題導致前端解密不了,是字符和格式問題,建議讓後端解決

// AES 加密函數
export const aesEncrypt = (data, key) => {
  const cipher = createCipheriv("aes-128-ecb", Buffer.from(key), null);
  let encrypted = cipher.update(data, "utf8", "hex");
  encrypted += cipher.final("hex");
  return encrypted;
};

// AES 解密函數
export const aesDecrypt = (encryptedData, key) => {
  const decipher = createDecipheriv("aes-128-ecb", Buffer.from(key), null);
  let decrypted = decipher.update(encryptedData, "hex", "utf8");
  decrypted += decipher.final("utf8");
  return decrypted;
};

SM4 使用 sm-crypto#

import { sm4 } from "sm-crypto";
/**
 * sm4解密
 * @param options sm4的私鑰key與密文content
 * @param cipherMode  CMAC模式 1 - C1C3C2,0 - C1C2C3,默認為1
 * @returns 密文
 */
function sm4Decrypt(options: DecryptOptions, cipherMode = 1): string {
  const { key, content } = options;
  return (
    sm4.decrypt(_base64ToHex(content), _base64ToHex(key), cipherMode) || ""
  );
}
// 加密
sm4.encrypt(inArray, key, options);

編碼轉換#

// 將十六進制字符串轉換為字節數組
export function hexToBase64(hex) {
  const byteArray = new Uint8Array(
    hex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
  );

  // 將字節數組轉換為 Base64 字符串
  let binaryString = "";
  const len = byteArray.length;
  for (let i = 0; i < len; i++) {
    binaryString += String.fromCharCode(byteArray[i]);
  }

  return btoa(binaryString);
}
// 將 Base64 字符串轉換為字節數組
export function base64ToHex(base64) {
  const binaryString = atob(base64);
  const binaryLen = binaryString.length;
  let hexString = "";

  for (let i = 0; i < binaryLen; i++) {
    const hex = binaryString.charCodeAt(i).toString(16);
    hexString += (hex.length === 1 ? "0" : "") + hex; // 確保每個字節都是兩位數
  }

  return hexString;
}

封裝 hooks 完成前端多重解密#

先通過 RSA 解密出 SM4 秘鑰,再通過 SM4 解密密文

import JSEncrypt from "jsencrypt";
import { sm4 } from "sm-crypto";

/**
 * 數據解密流程:
 * 先通過RSA解密出SM4秘鑰,再通過SM4解密密文
 * @param options IJSEncryptOptions RSA秘鑰,SM4加密秘鑰,密文
 * @returns DecryptResult 最終密文,RSA解密函數,SM4解密函數
 */
export function useEncryption(options) {
  const { sm4EnCryptBaseKey, contentEncryptBase64, rsaPrivateKey } = options;
  /**
   * 數據解密
   * @returns 明文
   */
  function _decode() {
    const sm4key = rsaDecrypt({
      key: rsaPrivateKey,
      content: sm4EnCryptBaseKey,
    });
    return sm4Decrypt({ key: sm4key || "", content: contentEncryptBase64 });
  }

  /**
   * rsa解密
   * @param options rsa的私鑰key與密文content
   * @returns 明文
   */
  function rsaDecrypt(options) {
    const { key, content } = options;
    const decrypt = new JSEncrypt();
    decrypt.setPrivateKey(key);
    return decrypt.decrypt(content) || "";
  }

  /**
   * sm4解密
   * @param options sm4的私鑰key與密文content
   * @param cipherMode  CMAC模式 1 - C1C3C2,0 - C1C2C3,默認為1
   * @returns 明文
   */
  function sm4Decrypt(options, cipherMode = 1) {
    const { key, content } = options;
    return (
      sm4.decrypt(_base64ToHex(content), _base64ToHex(key), cipherMode) || ""
    );
  }

  /**
   * 十六進制轉base64
   */
  function _hexToBase64(hex) {
    const byteArray = new Uint8Array(
      hex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
    );
    let binaryString = "";
    const len = byteArray.length;
    for (let i = 0; i < len; i++) {
      binaryString += String.fromCharCode(byteArray[i]);
    }
    return btoa(binaryString);
  }

  /**
   * base64轉十六進制
   */
  function _base64ToHex(base64) {
    const binaryString = atob(base64);
    const binaryLen = binaryString.length;
    let hexString = "";
    for (let i = 0; i < binaryLen; i++) {
      const hex = binaryString.charCodeAt(i).toString(16);
      hexString += (hex.length === 1 ? "0" : "") + hex;
    }
    return hexString;
  }

  return { content: _decode(), rsaDecrypt, sm4Decrypt };
}

此文由 Mix Space 同步更新至 xLog
原始鏈接為 http://www.sroxck.top/posts/fontend/crypt


載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。