前后端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 前后端加解密的编码兼容性问题!

相关推荐
派大鑫wink3 小时前
【JAVA学习日志】SpringBoot 参数配置:从基础到实战,解锁灵活配置新姿势
java·spring boot·后端
程序员爱钓鱼4 小时前
Node.js 编程实战:文件读写操作
前端·后端·node.js
xUxIAOrUIII4 小时前
【Spring Boot】控制器Controller方法
java·spring boot·后端
PineappleCoder4 小时前
工程化必备!SVG 雪碧图的最佳实践:ID 引用 + 缓存友好,无需手动算坐标
前端·性能优化
Dolphin_Home4 小时前
从理论到实战:图结构在仓库关联业务中的落地(小白→中级,附完整代码)
java·spring boot·后端·spring cloud·database·广度优先·图搜索算法
zfj3214 小时前
go为什么设计成源码依赖,而不是二进制依赖
开发语言·后端·golang
weixin_462446234 小时前
使用 Go 实现 SSE 流式推送 + 打字机效果(模拟 Coze Chat)
开发语言·后端·golang
JIngJaneIL4 小时前
基于springboot + vue古城景区管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
敲敲了个代码5 小时前
隐式类型转换:哈基米 == 猫 ? true :false
开发语言·前端·javascript·学习·面试·web
澄江静如练_5 小时前
列表渲染(v-for)
前端·javascript·vue.js