前后端SM2加密交互问题解析与解决方案

SM2 前后端加密交互问题解析与解决方案

问题背景

在 SM2 国密算法加解密交互中,前后端通常使用不同的编码方式:

  • 前端 (如 sm-crypto)生成的密文默认是 Hex 编码 (不带 04 前缀)。
  • 后端 (如 Java 的 BouncyCastleHutool)生成的密文通常是 Base64 编码 (可能含 04 前缀,目前使用Hutoo-all测试不含04 前缀)。

这导致 前端加密后端解密后端加密前端解密 时出现格式不匹配问题。


核心问题分析

场景 问题 原因
前端加密 → 后端解密 解密失败 前端 Hex 密文不带 04,但后端需要 04
后端加密 → 前端解密 解密失败 后端 Base64 转 Hex 后含 04,但前端不需要

解决方案

1. 前端加密 → 后端解密

步骤:

  1. 前端生成 Hex 密文(如 "a1b2c3...")。

  2. 前端需补 04 后传给后端:

    ini 复制代码
    const cipherText = sm2.doEncrypt(plainText, publicKey); // 生成 Hex 密文
    const cipherTextWith04 = "04" + cipherText; // 补 04
  3. 后端解码时直接处理:

    ini 复制代码
    String cipherText = "04a1b2c3..."; // 前端传来的 Hex
    byte[] cipherBytes = Hex.decode(cipherText); // Hex 转字节
    String plainText = sm2Decrypt(cipherBytes, privateKey); // 解密

2. 后端加密 → 前端解密

步骤:

  1. 后端生成 Base64 密文(如 "BElNTU9S...")。

  2. 后端转 Hex 并去掉 04

    arduino 复制代码
    public static String encryptForFrontend(String plainText, String publicKey) {
        String base64Cipher = sm2Encrypt(plainText, publicKey); // Base64 密文
        byte[] bytes = Base64.getDecoder().decode(base64Cipher);
        String hexCipher = Hex.encodeHexString(bytes); // Base64 → Hex
        if (hexCipher.startsWith("04")) {
            hexCipher = hexCipher.substring(2); // 去掉 04
        }
        return hexCipher;
    }
  3. 前端直接解密:

    ini 复制代码
    const plainText = sm2.doDecrypt(cipherText, privateKey); // 自动处理无 04 的 Hex

完整工具类(Java 版)

arduino 复制代码
import org.apache.commons.codec.binary.Hex;
import java.util.Base64;

public class Sm2CryptoUtils {

    /**
     * 后端加密 → 前端可解密的 Hex(无 04)
     */
    public static String encryptForFrontend(String plainText, String publicKey) {
        String base64Cipher = sm2Encrypt(plainText, publicKey);
        byte[] bytes = Base64.getDecoder().decode(base64Cipher);
        String hexCipher = Hex.encodeHexString(bytes);
        return remove04Prefix(hexCipher);
    }

    /**
     * 解密前端传来的 Hex(补 04)
     */
    public static String decryptFromFrontend(String hexCipher, String privateKey) {
        hexCipher = add04PrefixIfNeeded(hexCipher);
        byte[] cipherBytes = Hex.decodeHex(hexCipher);
        return sm2Decrypt(cipherBytes, privateKey);
    }

    private static String remove04Prefix(String hex) {
        return hex.startsWith("04") ? hex.substring(2) : hex;
    }

    private static String add04PrefixIfNeeded(String hex) {
        return hex.startsWith("04") ? hex : "04" + hex;
    }
}

前端适配代码(JavaScript 版)

php 复制代码
import { sm2 } from 'sm-crypto';

/**
 * 加密 → 传给后端(补 04)
 */
function encryptForBackend(plainText, publicKey) {
  const cipherText = sm2.doEncrypt(plainText, publicKey); // Hex 密文
  return "04" + cipherText; // 补 04
}

/**
 * 解密后端传来的 Hex(去 04)
 */
function decryptFromBackend(cipherText, privateKey) {
  if (cipherText.startsWith("04")) {
    cipherText = cipherText.substring(2); // 去 04
  }
  return sm2.doDecrypt(cipherText, privateKey);
}

关键点总结

  1. 编码差异

    • 前端默认 Hex,后端默认 Base64。
    • SM2 密文的 04 是椭圆曲线未压缩标识,部分库需要它,部分库不需要。
  2. 转换规则

    方向 操作
    前端 → 后端 Hex 补 04
    后端 → 前端 Base64 → Hex → 去 04
  3. 为什么 04 重要?

    • 某些库(如 Java 的 BouncyCastle)要求密文含 04,而 sm-crypto 默认不带。

测试用例

1. 前端加密 → 后端解密

arduino 复制代码
// 前端
const cipherText = encryptForBackend("Hello", "04公钥...");
// 发送 cipherText(带 04)到后端
ini 复制代码
// 后端
String plainText = Sm2CryptoUtils.decryptFromFrontend(cipherText, "私钥");

2. 后端加密 → 前端解密

arduino 复制代码
// 后端
String cipherText = Sm2CryptoUtils.encryptForFrontend("Hello", "公钥");
// 返回 cipherText(无 04)到前端
ini 复制代码
// 前端
const plainText = decryptFromBackend(cipherText, "私钥");

**常见问题 **

Q1:为什么前端生成的 Hex 不带 04

A1:sm-crypto 默认使用压缩格式,而 04 是未压缩标识。可通过配置强制包含:

ini 复制代码
const cipherText = sm2.doEncrypt(plainText, publicKey, { mode: "uncompressed" }); // 含 04

Q2:后端如何判断 Hex 是否需要补 04

A2:SM2 密文长度应为 64 字节(128 字符)或 65 字节(130 字符,含 04)。如果长度是 128,补 04;如果是 130,直接使用。


通过上述方案,可彻底解决 SM2 前后端加解密的编码兼容性问题!

相关推荐
孟陬13 小时前
Claude Code 巧思 `Ctrl+S` 暂存键
前端·后端
雪隐13 小时前
个人电脑玩AI-06让5060 Ti给你打工——不光能画画,Qwen3-TTS还能学人说话,连我老板都信了!
人工智能·后端·python
PedroQue9913 小时前
V1.6.1性能优化:高频路径提速与代码精简
前端·uni-app
Oneslide13 小时前
openEuler 17.1GB Everything ISO 离线本地 DNF 源搭建教程
后端
猩猩程序员13 小时前
将 LiteLLM 迁移到 Rust —— 构建最快、最轻量的 AI Gateway
前端
lichenyang45313 小时前
JSBridge 分发升级:为什么要从 if-else 变成 Registry > 这是「ASCF 架构升级」系列的第 3 篇
前端
蝎子莱莱爱打怪13 小时前
那不是我的黑历史,那是我的来时路啊!😭😭
后端·程序员
用户2986985301413 小时前
Java 实现 Word 文档文本与图片提取的方法
java·后端
码上天下13 小时前
流式响应断了,前端怎么自动重连续传
前端
anyup13 小时前
来简单聊聊鸿蒙开发,万元奖金的事~
前端·华为·harmonyos