前端RSA与SM4加密流程

公司最新需求居然让前端拿着秘钥来解密.... 真是防君子不防小人啊，这里记录一下前端使用 RSA,AES,SM4 等加密算法的加解密流程，以及 hooks 的封装

私钥与公钥

公钥:
可以公开给任何人，用于加密数据，使用公钥加密的数据，只有对应的私钥才能解密

私钥:
必须保密，仅由密钥的拥有者持有，用于解密数据。使用私钥解密的数据，只有使用相应的公钥加密过的内容才能被解密

工作流程

生成密钥对：生成一对密钥，包括公钥和私钥。

数据加密：发送方使用接收方的公钥加密数据。

数据传输：加密后的数据可以安全地通过不安全的通道发送。

数据解密：接收方使用自己的私钥解密收到的数据。

前端加密库的使用

RSA 使用 jsencrypt 或 node-rsa

Copy
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 解密时大概率会出问题导致前端解密不了，是字符和格式问题，建议让后端解决

Copy
// 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

Copy
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);

编码转换

Copy
// 将十六进制字符串转换为字节数组
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 解密密文

Copy
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