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

相关推荐
前端爆冲5 分钟前
项目中无用export的检测方案
前端
HelloDam5 分钟前
基于元素小组的归并排序算法
后端·算法·排序算法
Net分享5 分钟前
在 ASP.NET Core 中使用 Confluent.Kafka 实现 Kafka 生产者和消费者
后端
HelloDam6 分钟前
单元格法近似求解多边形最大内接矩形问题【思路讲解+java实现】
后端
Winwoo8 分钟前
服务端推送 SSE
后端
热爱编程的小曾33 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin44 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox1 小时前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
uhakadotcom1 小时前
使用 Model Context Protocol (MCP) 构建 GitHub PR 审查服务器
后端·面试·github
Asthenia04121 小时前
详细分析:ConcurrentLinkedQueue
后端