1. 项目概述:为什么是SM2与Bouncy Castle?
最近在做一个涉及金融数据交换的项目,甲方明确要求使用国密算法对传输报文进行签名和加密。在一众国密算法中,SM2作为非对称加密算法的代表,自然成了首选。但当我打开JDK的标准库,发现它并没有内置对SM2的支持,这时候,一个老牌且强大的加密提供者——Bouncy Castle(BC)就进入了视野。
简单来说,Bouncy Castle是一个开源的、轻量级的加密算法库,它为Java和C#平台提供了JCE(Java Cryptography Extension)标准之外的、大量的加密算法实现,其中就完整涵盖了SM2、SM3、SM4等国密算法套件。选择BC来实现SM2,不是因为“高大上”,而是因为它几乎是Java生态中处理国密算法最成熟、最稳妥的方案。它就像一个功能齐全的“加密工具箱”,当JDK自带的“螺丝刀”不够用时,它能提供你需要的各种“专业扳手”。
这篇文章,我就结合自己最近在项目中的实战,从头到尾梳理一遍如何在Java项目中,利用Bouncy Castle高效、正确地实现SM2的密钥对生成、加密、解密、签名和验签。我会避开那些教科书式的原理罗列,直接聚焦于开发中最关心的几个问题:如何快速集成?代码怎么写?有哪些坑?以及如何确保性能和安全?无论你是第一次接触国密,还是正在为项目选型而调研,希望这些“踩过坑”的经验能给你带来直接的帮助。
2. 环境准备与Bouncy Castle集成
在开始写代码之前,我们得先把“战场”布置好。使用Bouncy Castle通常有两种方式:作为JCE提供者(Provider)动态注册,或者直接调用其轻量级API。对于SM2这种标准算法操作,我强烈推荐使用Provider模式,因为它更符合Java密码体系的标准用法,与其他加密操作(如SHA256WithRSA)的代码风格保持一致,也便于管理。
2.1 引入依赖
首先,你需要将Bouncy Castle的依赖加入到项目中。如果你使用Maven,在 pom.xml 中添加如下依赖即可。这里我使用的是较新且稳定的 bcprov-jdk18on 版本。
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.78</version>
</dependency>
如果你使用Gradle,对应的配置是:
implementation 'org.bouncycastle:bcprov-jdk18on:1.78'
注意 :版本号请尽量使用官方发布的最新稳定版。Bouncy Castle的版本命名规则是
bcprov-jdk[目标JDK版本]on,例如jdk18on表示兼容JDK 1.8及以上。选择与你的JDK大版本匹配的即可。
2.2 注册加密提供者
引入jar包后,我们需要在代码运行时,将Bouncy Castle注册为JVM的一个安全提供者。这一步至关重要,只有注册成功,后续才能通过标准的 KeyPairGenerator 、 Cipher 等类来使用SM2算法。
注册通常有两种方式:
- 静态注册(推荐用于生产环境) :修改JRE的安全配置文件
$JAVA_HOME/jre/lib/security/java.security。找到security.provider开头的行,在列表末尾添加:
这里的数字“11”需要根据你现有的提供者数量顺延,确保不重复。这种方式一劳永逸,但对部署环境有侵入性。security.provider.11=org.bouncycastle.jce.provider.BouncyCastleProvider - 动态注册(推荐用于开发和测试) :在应用程序启动时,通过代码动态添加。这种方式更灵活,也是我下面示例采用的方式。
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
public class Sm2Demo {
static {
// 动态注册Bouncy Castle提供者
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
// ... 后续代码
}
把这段静态代码块放在你的工具类或应用入口类中,确保在调用任何加密方法前,提供者已经就位。
2.3 一个常见的“坑”:算法名称
这是新手最容易出错的地方。在JDK的标准命名里,你可能习惯用 RSA 、 EC 。但在Bouncy Castle中,SM2对应的算法名称是 SM2 。然而,在获取密钥对生成器或签名实例时,仅仅使用 SM2 可能不够。根据BC的实现,生成SM2密钥对通常使用 EC 算法,并指定特定的椭圆曲线参数。而签名算法,则有一个更具体的名称。
这里先给你一个正确的映射表,避免你走弯路:
| 操作 | 标准算法名称 (在BC中) | 备注 |
|---|---|---|
| 生成密钥对 | EC |
需要额外指定SM2的椭圆曲线参数 |
| 加密/解密 | SM2 |
用于 Cipher 实例的初始化 |
| 签名/验签 | SM3withSM2 |
表示使用SM3做摘要,SM2做签名 |
知道这些名称,我们才能正确地调用API。接下来,我们就进入实战环节,看看这些API具体怎么用。
3. SM2密钥对生成与管理
非对称加密的起点就是密钥对。SM2基于椭圆曲线密码学(ECC),所以它的密钥对包含一个公钥和一个私钥。公钥可以公开,用于加密和验签;私钥必须严格保密,用于解密和签名。
3.1 生成SM2密钥对
使用Bouncy Castle生成SM2密钥对的代码非常直观。核心是使用 KeyPairGenerator ,并为其指定SM2对应的椭圆曲线参数。
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import java.security.*;
public class Sm2KeyGenerator {
/**
* 生成SM2密钥对
* @return 生成的密钥对
* @throws NoSuchAlgorithmException
* @throws InvalidAlgorithmParameterException
*/
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
// 1. 获取SM2的椭圆曲线参数规范
// “sm2p256v1”是国密标准推荐的256位素数域椭圆曲线
ECNamedCurveParameterSpec sm2Spec = ECNamedCurveTable.getParameterSpec("sm2p256v1");
// 2. 获取EC算法的密钥对生成器实例
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
// 3. 使用SM2的参数初始化生成器
keyPairGen.initialize(sm2Spec, new SecureRandom()); // 使用强随机数源
// 4. 生成密钥对
return keyPairGen.generateKeyPair();
}
public static void main(String[] args) throws Exception {
KeyPair keyPair = generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
System.out.println("公钥格式: " + publicKey.getFormat()); // 通常是X.509
System.out.println("私钥格式: " + privateKey.getFormat()); // 通常是PKCS#8
System.out.println("公钥长度(字节): " + publicKey.getEncoded().length);
System.out.println("私钥长度(字节): " + privateKey.getEncoded().length);
}
}
代码解读与注意事项:
- 曲线参数
sm2p2


341

被折叠的 条评论
为什么被折叠?



