【密码学实战】Java 实现 SM2 国密算法(签名带id、验签及 C1C3C2 加密解密)

发布于:2025-02-28 ⋅ 阅读:(21) ⋅ 点赞:(0)

前言

SM2是中国国家密码管理局发布的椭圆曲线公钥密码算法标准(GB/T 32918),属于国密算法体系。与RSA和ECDSA相比,SM2在相同安全强度下密钥更短、计算效率更高。本文将介绍如何在Java中实现SM2的密钥生成数字签名验签加密解密功能。

一、结果验证

1.代码运行结果

1.1 不带id签名验签代码运行结果

在这里插入图片描述

1.2 带id签名验签代码运行结果

在这里插入图片描述

1.3 SM2加密解密代码运行结果

在这里插入图片描述

2.工具验证结果

2.1 不带id签名验签工具运行结果

在这里插入图片描述

2.2 带id签名验签工具运行结果

在这里插入图片描述

2.3 SM2加密解密工具运行结果

在这里插入图片描述

二、SM2签名原理

SM2签名过程的核心是利用私钥对消息进行签名,生成签名值 (r, s)。具体步骤如下:

  1. 计算消息的哈希值
    使用SM3哈希算法对消息 M 进行哈希处理,得到哈希值 e

  2. 生成随机数
    选择一个随机数 k,满足 1 < k < n,其中 n 是椭圆曲线的阶。

  3. 计算椭圆曲线点
    使用随机数 k 计算椭圆曲线上的点 Q = kG,其中 G 是椭圆曲线的基点。取点 Qx 坐标 x1

  4. 计算签名值 r
    计算 r = (e + x1) mod n。如果 r = 0r + k = n,则重新选择随机数 k

  5. 计算签名值 s
    计算 s = (1 + d)^{-1} * (k - r * d) mod n,其中 d 是私钥。

  6. 输出签名结果
    签名结果为 (r, s),通常以字节数组的形式存储和传输。

三、SM2验签原理

SM2验签过程的核心是利用公钥验证签名的有效性。具体步骤如下:

  1. 计算消息的哈希值
    使用SM3哈希算法对消息 M 进行哈希处理,得到哈希值 e

  2. 计算值 t
    计算 t = (r + s) mod n,其中 rs 是签名值。

  3. 计算椭圆曲线点
    计算点 R = sG + tP,其中 G 是椭圆曲线的基点,P 是签名者的公钥。取点 Rx 坐标 x1

  4. 验证签名
    验证等式 r = (e + x1) mod n 是否成立。如果成立,则签名有效;否则,签名无效。

四、SM2签名与验签的Java实现

1. 添加依赖

pom.xml中添加Bouncy Castle依赖:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.70</version>
</dependency>

2. 生成密钥对

/**
     * 生成SM2密钥对。
     *
     * @return 生成的密钥对(包含公钥和私钥)
     * @throws Exception 如果密钥生成过程中发生错误
     */
    public static KeyPair generateKeyPair() throws Exception {
   
        // 添加Bouncy Castle安全提供者
        Security.addProvider(new BouncyCastleProvider());

        // 获取SM2椭圆曲线参数(使用sm2p256v1曲线)
        ECParameterSpec sm2Spec = ECNamedCurveTable.getParameterSpec("sm2p256v1");

        // 创建EC密钥对生成器实例
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");

        // 初始化密钥对生成器,指定椭圆曲线参数和随机数生成器

        kpg.initialize(sm2Spec, new SecureRandom());

        // 生成密钥对并返回
        return kpg.generateKeyPair();
    }

3. 签名不带ID

 /**
     * 使用SM2算法进行签名(不使用用户ID)。
     *
     * @param data       待签名的数据(字节数组)
     * @param privateKey 签名使用的私钥
     * @return 签名结果(字节数组)
     * @throws Exception 如果签名过程中发生错误
     */
    public static String signNoId(byte[] data, PrivateKey privateKey) throws Exception {
   
        // 创建SM2签名实例,指定使用SM3哈希算法
        Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);

        // 初始化签名器,使用私钥
        signature.initSign(privateKey);

        // 更新待签名的数据
        signature.update(data);

        // 生成签名
        byte[] signatureBytes = signature.sign();

        // 解析 DER 编码的签名结果
        ASN1Sequence sequence = ASN1Sequence.getInstance(signatureBytes);
        BigInteger r = ASN1Integer.getInstance(sequence.getObjectAt(0)).getValue();
        BigInteger s = ASN1Integer.getInstance(sequence.getObjectAt(1)).getValue();

        // 打印 r 和 s 的值
        System.out.println("r 的十六进制值: " + r.toString(16));
        System.out.println("s 的十六进制值: " + s.toString(16));

        // 将