news 2026/7/3 4:35:40

Java国密SM2算法实战:从Bouncy Castle集成到加解密签名完整实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java国密SM2算法实战:从Bouncy Castle集成到加解密签名完整实现

1. 项目概述:为什么要在Java里折腾SM2?

如果你正在处理涉及金融、政务或者对数据安全有高要求的业务,那么“国密算法”这个词你肯定不陌生。SM2作为国密算法体系中的非对称加密核心,正逐渐成为这些领域的标配。最近我在一个数据交换平台的项目里,就遇到了必须集成SM2进行数据加解密和签名验签的需求。甲方明确要求,所有敏感数据的传输和存储都必须使用国密算法,RSA?AES?对不起,这次不灵了。

网上找了一圈,发现关于Java实现SM2的资料要么是零零散散的代码片段,语焉不详;要么就是直接丢给你一个庞大的、文档稀少的第三方库,让人不知从何下手。更头疼的是,很多文章只讲“怎么调用”,却不讲“为什么这么调”,遇到生成密钥对格式不对、加密后数据长度异常、签名验签总失败这些坑,根本找不到解决方案。所以,我决定把这次从零开始,在Java环境中完整实现SM2加解密和加签验签的实战过程记录下来。这不是一个简单的API调用教程,而是一个包含了密钥管理、算法原理理解、踩坑实录和性能考量的全流程复盘。无论你是正在对接国密需求的开发,还是对密码学感兴趣想深入理解SM2,这篇文章都能给你一套可直接复现、且知道每一步背后逻辑的实操方案。

2. 核心思路与方案选型:不走弯路的起点

在动手写代码之前,理清思路和选对工具至关重要。SM2的实现,核心在于找到一个可靠、易用且符合标准的密码学库。

2.1 为什么选择Bouncy Castle?

Java标准库(JCE)本身并不包含SM2算法。因此,我们必须引入第三方库。主流的选项有Bouncy Castle和国内的GMSSL(Java版)。经过对比,我选择了Bouncy Castle(BC),原因如下:

  1. 生态成熟,资料相对丰富:BC是Java密码学领域事实上的标准扩展库,历史悠久,社区活跃。虽然其对国密的官方支持也是后来加入的,但其整体的稳定性和代码质量经过了长期考验。遇到问题时,在Stack Overflow、GitHub等平台更容易找到相关讨论或线索。
  2. API设计统一,易于集成:BC的API设计与JCE标准兼容性很好。一旦引入,你可以用类似CipherSignature这样的标准JCE类来操作SM2,学习成本和集成成本较低。
  3. 功能全面:BC不仅提供了SM2,还提供了SM3、SM4等国密算法,以及完整的椭圆曲线(ECC)底层支持,这对于理解SM2的密钥生成和参数配置非常有帮助。

当然,GMSSL是国密算法的“原厂”实现,理论上更“正宗”。但在实际Java项目集成中,其文档、Maven依赖的易用性以及与其他框架的兼容性,有时会带来额外的挑战。对于大多数需要快速、稳定上线的业务场景,BC是更稳妥的起点。

2.2 项目依赖与基础环境准备

这里我使用Maven进行依赖管理。BC提供了两个主要的JAR包:bcprov-jdk15on(核心密码学提供者)和bcpkix-jdk15on(处理证书和CRL等)。对于基础的SM2操作,我们只需要核心包。

在你的pom.xml中添加依赖:

<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> <!-- 请使用最新稳定版 --> </dependency>

注意:版本号请务必检查BC官网或Maven中央仓库,使用最新稳定版。不同版本间API可能有细微差别,本文代码基于1.70版本编写。

接下来,我们需要在代码中动态注册BC提供者,或者通过JVM参数静态注册。我推荐在应用启动时动态注册,这样更灵活,不影响其他模块。

import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.Security; public class Sm2Demo { static { // 如果尚未注册,则添加BouncyCastleProvider if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { Security.addProvider(new BouncyCastleProvider()); } } // ... 后续代码 }

完成这一步,你的Java环境就具备了操作SM2等算法的基础能力。

3. SM2密钥对生成与管理

非对称加密的基石就是密钥对。SM2使用的是椭圆曲线密码学(ECC),其密钥对包括一个私钥(自己保管,用于解密和签名)和一个公钥(可以公开,用于加密和验签)。

3.1 生成SM2密钥对

在BC中,我们可以通过指定椭圆曲线参数来生成SM2的密钥对。国密局推荐的SM2曲线参数是固定的。

import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.SecureRandom; public class KeyPairGeneratorDemo { public static KeyPair generateSm2KeyPair() throws Exception { // 获取SM2的椭圆曲线参数规范 ECNamedCurveParameterSpec sm2Spec = ECNamedCurveTable.getParameterSpec("sm2p256v1"); // 实例化密钥对生成器,使用ECC算法,并指定提供者为BC KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME); // 使用SM2的参数初始化生成器 kpg.initialize(sm2Spec, new SecureRandom()); // 生成密钥对 return kpg.generateKeyPair(); } public static void main(String[] args) throws Exception { KeyPair keyPair = generateSm2KeyPair(); System.out.println("私钥格式: " + keyPair.getPrivate().getFormat()); // 通常是PKCS#8 System.out.println("公钥格式: " + keyPair.getPublic().getFormat()); // 通常是X.509 // 获取Base64编码的字符串,便于存储和传输 String privateKeyBase64 = java.util.Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()); String publicKeyBase64 = java.util.Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()); System.out.println("\n--- Base64编码 ---"); System.out.println("私钥: " + privateKeyBase64); System.out.println("公钥: " + publicKeyBase64); } }

关键点解析

  • "sm2p256v1":这是BC中定义的SM2曲线名称。它对应国密标准《GMT 0003-2012》中定义的椭圆曲线参数。
  • KeyPairGenerator.getInstance("EC", ...):虽然算法是SM2,但在JCE体系下,它被归类为椭圆曲线(EC)算法。指定提供者为BC,才能使用其特有的sm2p256v1参数。
  • getEncoded():这个方法返回密钥的标准编码字节流。私钥通常是PKCS#8格式,公钥是X.509格式。这两种格式是业界通用标准,便于与其他系统交换密钥。

3.2 从字节或字符串加载密钥

实际项目中,密钥对通常不是每次运行时生成,而是预先生成并保存在配置文件、数据库或硬件加密机中。因此,从Base64字符串或字节数组加载密钥是必备技能。

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; public class KeyLoader { /** * 从Base64字符串加载SM2私钥 */ public static PrivateKey loadPrivateKey(String base64PrivateKey) throws Exception { byte[] keyBytes = java.util.Base64.getDecoder().decode(base64PrivateKey); // PKCS#8编码规范 PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME); return keyFactory.generatePrivate(keySpec); } /** * 从Base64字符串加载SM2公钥 */ public static PublicKey loadPublicKey(String base64PublicKey) throws Exception { byte[] keyBytes = java.util.Base64.getDecoder().decode(base64PublicKey); // X.509编码规范 X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME); return keyFactory.generatePublic(keySpec); } }

实操心得:这里最容易出问题的地方是密钥格式。务必确保你存储的私钥是PKCS#8格式,公钥是X.509格式。如果你从其他系统(如OpenSSL)生成的密钥,可能需要先进行格式转换。一个常见的错误是,将PEM格式(-----BEGIN PRIVATE KEY-----)的密钥直接去掉头尾行进行Base64解码,这是正确的,因为PEM本质上就是Base64编码的DER内容加上头尾标识。只要解码后的字节是标准的PKCS#8或X.509 DER编码,上述加载方法就有效。

4. SM2非对称加解密实现

SM2加密和解密是非对称加密的典型应用:用公钥加密,用私钥解密。

4.1 加密过程详解与实现

SM2加密过程比RSA复杂一些,它本质上是一种基于椭圆曲线的集成加密方案(ECIES),输出包含一个椭圆曲线点(C1)、密钥派生产生的对称密钥加密的密文(C2)和消息认证码(C3)。但幸运的是,BC帮我们封装了这些细节。

import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.Cipher; import java.security.PublicKey; public class Sm2Encryptor { /** * SM2公钥加密 * @param publicKey SM2公钥 * @param data 待加密的明文数据 * @return 加密后的密文字节数组 */ public static byte[] encrypt(PublicKey publicKey, byte[] data) throws Exception { // 获取SM2加密的Cipher实例,使用BC提供者 // 算法名称为 "SM2" Cipher cipher = Cipher.getInstance("SM2", BouncyCastleProvider.PROVIDER_NAME); // 初始化为加密模式,传入公钥 cipher.init(Cipher.ENCRYPT_MODE, publicKey); // 执行加密 return cipher.doFinal(data); } /** * 一个更实用的方法:加密文本并返回Base64字符串 */ public static String encryptToBase64(PublicKey publicKey, String plainText, String charsetName) throws Exception { byte[] data = plainText.getBytes(charsetName); // 如 "UTF-8" byte[] encrypted = encrypt(publicKey, data); return java.util.Base64.getEncoder().encodeToString(encrypted); } }

为什么加密后的数据长度不固定?与RSA加密后长度固定为密钥长度不同,SM2加密后的数据长度是:64字节(C1点) + 明文长度 + 32字节(C3,即SM3杂凑值)。对于256位曲线,C1点是两个256位整数(x,y)的压缩或未压缩表示,通常为65字节(未压缩格式,04+x+y)或33字节(压缩格式)。BC默认可能使用混合格式或未压缩格式。因此,加密一个很短的字符串,得到的密文也会长达100多字节。这是正常的,是SM2算法机制决定的。

4.2 解密过程详解与实现

解密是加密的逆过程,需要使用配对的私钥。

import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.Cipher; import java.security.PrivateKey; public class Sm2Decryptor { /** * SM2私钥解密 * @param privateKey SM2私钥 * @param encryptedData 密文数据 * @return 解密后的明文字节数组 */ public static byte[] decrypt(PrivateKey privateKey, byte[] encryptedData) throws Exception { Cipher cipher = Cipher.getInstance("SM2", BouncyCastleProvider.PROVIDER_NAME); cipher.init(Cipher.DECRYPT_MODE, privateKey); return cipher.doFinal(encryptedData); } /** * 解密Base64编码的密文字符串 */ public static String decryptFromBase64(PrivateKey privateKey, String base64Encrypted, String charsetName) throws Exception { byte[] encryptedData = java.util.Base64.getDecoder().decode(base64Encrypted); byte[] decrypted = decrypt(privateKey, encryptedData); return new String(decrypted, charsetName); } }

4.3 加解密完整测试用例

让我们把生成、加密、解密串起来,写一个完整的测试。

public class Sm2EncryptionDemo { public static void main(String[] args) { try { // 1. 生成密钥对 KeyPair keyPair = KeyPairGeneratorDemo.generateSm2KeyPair(); PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); String originalText = "这是一段需要加密的敏感数据,比如身份证号或合同金额。"; System.out.println("原始明文: " + originalText); // 2. 加密 String encryptedBase64 = Sm2Encryptor.encryptToBase64(publicKey, originalText, "UTF-8"); System.out.println("加密后(Base64): " + encryptedBase64); System.out.println("密文长度(字节): " + java.util.Base64.getDecoder().decode(encryptedBase64).length); // 3. 解密 String decryptedText = Sm2Decryptor.decryptFromBase64(privateKey, encryptedBase64, "UTF-8"); System.out.println("解密后明文: " + decryptedText); // 4. 验证 System.out.println("解密是否成功: " + originalText.equals(decryptedText)); } catch (Exception e) { e.printStackTrace(); } } }

运行这个Demo,你应该能看到加密解密成功的输出。注意观察密文的长度,体会SM2加密的特点。

5. SM2数字签名与验签实现

数字签名用于验证数据的完整性和来源真实性。发送方用私钥对数据(或其摘要)签名,接收方用公钥验签。

5.1 签名过程详解与实现

SM2的签名算法与ECDSA类似,但使用了SM3作为杂凑函数。在BC中,我们使用Signature类。

import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.PrivateKey; import java.security.Signature; public class Sm2Signer { /** * 使用SM2私钥对数据进行签名 * @param privateKey 私钥 * @param data 原始数据 * @return 签名字节数组 (通常是DER编码的(r,s)序列) */ public static byte[] sign(PrivateKey privateKey, byte[] data) throws Exception { // 获取Signature实例,算法指定为 "SM3withSM2" Signature signature = Signature.getInstance("SM3withSM2", BouncyCastleProvider.PROVIDER_NAME); // 初始化签名对象,进入签名模式 signature.initSign(privateKey); // 传入要签名的数据 signature.update(data); // 执行签名 return signature.sign(); } /** * 对数据进行签名并返回Base64字符串 */ public static String signToBase64(PrivateKey privateKey, String data, String charsetName) throws Exception { byte[] dataBytes = data.getBytes(charsetName); byte[] signBytes = sign(privateKey, dataBytes); return java.util.Base64.getEncoder().encodeToString(signBytes); } }

关键点"SM3withSM2"这个算法名称是BC特有的,它指明了签名算法是SM2,并且使用的消息摘要算法是国密的SM3。这是与SHA256withECDSA等ECDSA算法的关键区别。

5.2 验签过程详解与实现

验签使用公钥,验证签名是否由对应的私钥生成且数据未被篡改。

import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.PublicKey; import java.security.Signature; public class Sm2Verifier { /** * 使用SM2公钥验证签名 * @param publicKey 公钥 * @param data 原始数据 * @param sign 签名字节数组 * @return true-验签成功, false-验签失败 */ public static boolean verify(PublicKey publicKey, byte[] data, byte[] sign) throws Exception { Signature signature = Signature.getInstance("SM3withSM2", BouncyCastleProvider.PROVIDER_NAME); signature.initVerify(publicKey); signature.update(data); return signature.verify(sign); } /** * 验证Base64编码的签名 */ public static boolean verifyFromBase64(PublicKey publicKey, String data, String base64Sign, String charsetName) throws Exception { byte[] dataBytes = data.getBytes(charsetName); byte[] signBytes = java.util.Base64.getDecoder().decode(base64Sign); return verify(publicKey, dataBytes, signBytes); } }

5.3 签名验签完整测试与“坑点”实录

public class Sm2SignatureDemo { public static void main(String[] args) { try { KeyPair keyPair = KeyPairGeneratorDemo.generateSm2KeyPair(); PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); String message = "这是一份重要的电子合同,金额为100万元。"; System.out.println("原始消息: " + message); // 1. 签名 String signatureBase64 = Sm2Signer.signToBase64(privateKey, message, "UTF-8"); System.out.println("数字签名(Base64): " + signatureBase64); // 2. 验签 (正常情况) boolean verifyResult1 = Sm2Verifier.verifyFromBase64(publicKey, message, signatureBase64, "UTF-8"); System.out.println("对原始消息验签结果: " + verifyResult1); // 应为 true // 3. 验签 (消息被篡改) String tamperedMessage = message + "(已被恶意修改)"; boolean verifyResult2 = Sm2Verifier.verifyFromBase64(publicKey, tamperedMessage, signatureBase64, "UTF-8"); System.out.println("对篡改消息验签结果: " + verifyResult2); // 应为 false // 4. 验签 (签名错误) String wrongSignature = "ThisIsNotAValidSignatureAtAll"; // 这里会抛出异常,因为Base64解码失败或签名格式错误 try { boolean verifyResult3 = Sm2Verifier.verifyFromBase64(publicKey, message, wrongSignature, "UTF-8"); System.out.println("对错误签名验签结果: " + verifyResult3); // 应为 false } catch (IllegalArgumentException e) { System.out.println("错误签名导致异常: " + e.getMessage()); } } catch (Exception e) { e.printStackTrace(); } } }

踩坑实录与核心注意事项

  1. 签名数据的编码一致性:这是最常见的坑!签名时对字符串message.getBytes("UTF-8"),验签时也必须用完全相同的字符集message.getBytes("UTF-8")。如果一端用UTF-8,另一端用GBK,即使字符串看起来一样,字节数组也不同,验签必然失败。最佳实践是,对于要签名的业务数据,先明确约定并统一使用一种编码(如UTF-8),或者直接对数据的字节数组进行签名和验签。

  2. 签名值的格式signature.sign()返回的通常是DER编码的ASN.1序列,包含了r和s两个大整数。有些其他平台(如某些C语言实现)可能返回的是r和s的简单拼接(各32字节,共64字节)。如果你需要与这类平台交互,可能需要对签名值进行编解码转换。BC的Signature类在验签时期望的也是DER格式。如果遇到验签失败,先检查签名值的格式是否匹配。

  3. “SM3withSM2”与“SM2”:在Signature.getInstance时,务必使用"SM3withSM2"。如果错误地使用了"SM2",BC可能会找不到该算法而抛出异常。

6. 实战进阶:解决典型问题与性能优化

在实际项目集成中,你可能会遇到比Demo更复杂的情况。下面分享几个实战中的关键问题和优化思路。

6.1 问题一:与OpenSSL或其他语言生成的密钥/签名互通

场景:后端用Java(BC),前端或另一个服务用OpenSSL命令生成的SM2密钥和签名,无法互相加解密或验签。

排查思路

  1. 密钥格式:确认双方使用的密钥格式。OpenSSL默认生成的PEM私钥可能是PKCS#1格式(-----BEGIN EC PRIVATE KEY-----),而Java BC通常期望PKCS#8格式。你需要用OpenSSL命令进行转换:
    # 将PKCS#1转换为PKCS#8 openssl pkcs8 -topk8 -nocrypt -in sm2_private_key.pem -out sm2_private_key_pkcs8.pem
    公钥通常问题不大,X.509格式是通用的。
  2. 曲线参数:确保双方使用的是同一条椭圆曲线,即sm2p256v1。在OpenSSL生成密钥时,需要指定参数:
    openssl ecparam -genkey -name sm2p256v1 -out sm2_private_key.pem
  3. 签名格式:如前所述,关注签名值是DER编码还是裸的r||s拼接。BC默认使用DER编码。如果对方是裸拼接,你需要在验签前将其转换为DER格式。BC提供了ASN1Primitive等类来帮助构建DER序列。

6.2 问题二:加密数据长度限制与分段处理

场景:SM2作为非对称加密,虽然不像RSA有严格的明文长度限制(例如2048位密钥最多加密245字节),但由于其加密过程中包含对称加密步骤(生成C2),理论上可以加密较长的数据。然而,在实际使用中,尤其是与某些硬件加密机或特定平台交互时,可能会有自己的限制。

建议

  • 对于非常长的数据(如超过数MB的文件),非对称加密在性能上是不合适的。标准的做法是采用混合加密
    1. 随机生成一个对称密钥(如SM4密钥)。
    2. 用SM4对称加密算法加密原始大文件。
    3. 用SM2公钥加密这个临时的SM4密钥。
    4. SM2加密后的密钥+SM4加密后的文件数据一起发送或存储。
  • 解密时,先用SM2私钥解密出SM4密钥,再用SM4密钥解密文件数据。

6.3 性能考量与最佳实践

  1. 密钥对象复用KeyPairPublicKeyPrivateKey对象一旦创建,可以安全地重复用于多次加密、解密、签名、验签操作。避免在每次操作前都从Base64字符串解析密钥。
  2. 使用线程安全的类CipherSignature实例不是线程安全的。对于高并发场景,应该为每个线程创建新的实例,或者使用ThreadLocal进行缓存。更简单的方式是在方法内部每次创建新实例,因为创建它们的开销在单次加解密/签名操作中占比很小。
  3. 异常处理:加解密和签名验签操作可能抛出多种异常,如BadPaddingException(解密时密钥不对或数据被篡改)、InvalidKeyException(密钥格式错误)、SignatureException(验签失败)等。在生产代码中,务必进行细致的异常捕获和日志记录,这有助于快速定位是密钥问题、数据问题还是算法配置问题。
  4. 日志脱敏:绝对不要在日志中直接打印明文、密钥或完整的密文。可以打印其长度、哈希值(如SM3)或前几位后几位用于调试。

7. 完整工具类封装与使用示例

将上述功能封装成一个工具类,便于在项目中调用。

import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import javax.crypto.Cipher; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; /** * SM2 加解密及签名工具类 * 依赖:Bouncy Castle 1.70+ */ public class Sm2Util { static { if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { Security.addProvider(new BouncyCastleProvider()); } } private static final String ALGORITHM = "EC"; private static final String SM2_CURVE_NAME = "sm2p256v1"; private static final String SM2_CIPHER_ALG = "SM2"; private static final String SM2_SIGN_ALG = "SM3withSM2"; private static final String CHARSET = "UTF-8"; // ================== 密钥对生成 ================== public static KeyPair generateKeyPair() throws Exception { ECNamedCurveParameterSpec sm2Spec = ECNamedCurveTable.getParameterSpec(SM2_CURVE_NAME); KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGORITHM, BouncyCastleProvider.PROVIDER_NAME); kpg.initialize(sm2Spec, new SecureRandom()); return kpg.generateKeyPair(); } public static String getPublicKeyBase64(PublicKey publicKey) { return Base64.getEncoder().encodeToString(publicKey.getEncoded()); } public static String getPrivateKeyBase64(PrivateKey privateKey) { return Base64.getEncoder().encodeToString(privateKey.getEncoded()); } // ================== 密钥加载 ================== public static PublicKey loadPublicKey(String base64PublicKey) throws Exception { byte[] keyBytes = Base64.getDecoder().decode(base64PublicKey); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM, BouncyCastleProvider.PROVIDER_NAME); return keyFactory.generatePublic(keySpec); } public static PrivateKey loadPrivateKey(String base64PrivateKey) throws Exception { byte[] keyBytes = Base64.getDecoder().decode(base64PrivateKey); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM, BouncyCastleProvider.PROVIDER_NAME); return keyFactory.generatePrivate(keySpec); } // ================== 加解密 ================== public static String encrypt(String plainText, String base64PublicKey) throws Exception { PublicKey publicKey = loadPublicKey(base64PublicKey); Cipher cipher = Cipher.getInstance(SM2_CIPHER_ALG, BouncyCastleProvider.PROVIDER_NAME); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] encrypted = cipher.doFinal(plainText.getBytes(CHARSET)); return Base64.getEncoder().encodeToString(encrypted); } public static String decrypt(String base64CipherText, String base64PrivateKey) throws Exception { PrivateKey privateKey = loadPrivateKey(base64PrivateKey); Cipher cipher = Cipher.getInstance(SM2_CIPHER_ALG, BouncyCastleProvider.PROVIDER_NAME); cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(base64CipherText)); return new String(decrypted, CHARSET); } // ================== 签名验签 ================== public static String sign(String data, String base64PrivateKey) throws Exception { PrivateKey privateKey = loadPrivateKey(base64PrivateKey); Signature signature = Signature.getInstance(SM2_SIGN_ALG, BouncyCastleProvider.PROVIDER_NAME); signature.initSign(privateKey); signature.update(data.getBytes(CHARSET)); byte[] signBytes = signature.sign(); return Base64.getEncoder().encodeToString(signBytes); } public static boolean verify(String data, String base64Sign, String base64PublicKey) throws Exception { PublicKey publicKey = loadPublicKey(base64PublicKey); Signature signature = Signature.getInstance(SM2_SIGN_ALG, BouncyCastleProvider.PROVIDER_NAME); signature.initVerify(publicKey); signature.update(data.getBytes(CHARSET)); return signature.verify(Base64.getDecoder().decode(base64Sign)); } // ================== 使用示例 ================== public static void main(String[] args) throws Exception { // 1. 生成并保存密钥对 KeyPair keyPair = generateKeyPair(); String publicKeyStr = getPublicKeyBase64(keyPair.getPublic()); String privateKeyStr = getPrivateKeyBase64(keyPair.getPrivate()); System.out.println("公钥: " + publicKeyStr.substring(0, 50) + "..."); System.out.println("私钥: " + privateKeyStr.substring(0, 50) + "..."); String originalData = "测试数据123ABC"; System.out.println("\n原始数据: " + originalData); // 2. 加密解密 String encrypted = encrypt(originalData, publicKeyStr); System.out.println("加密后: " + encrypted); String decrypted = decrypt(encrypted, privateKeyStr); System.out.println("解密后: " + decrypted); System.out.println("加解密是否一致: " + originalData.equals(decrypted)); // 3. 签名验签 String signature = sign(originalData, privateKeyStr); System.out.println("\n签名值: " + signature); boolean isValid = verify(originalData, signature, publicKeyStr); System.out.println("验签结果: " + isValid); // 4. 篡改后验签 boolean isTamperedValid = verify(originalData + "x", signature, publicKeyStr); System.out.println("数据篡改后验签结果: " + isTamperedValid); } }

这个工具类Sm2Util提供了从生成密钥到加解密、签名验签的完整闭环方法,并且所有输入输出都使用Base64字符串,方便在JSON、数据库或配置文件中存储和传输。你可以直接将其复制到你的工具模块中使用。

最后,再强调一个至关重要的安全实践:私钥的安全管理。在生产环境中,私钥绝不能像示例中这样硬编码在代码或配置文件中。应该使用安全的密钥管理系统(KMS)、硬件安全模块(HSM)或至少在部署时从高度安全的环境变量、加密的密钥文件中读取。保护好私钥,是整个非对称加密体系安全的根本。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/3 4:32:03

多维聚合实战:从OLAP立方体到语义层的全链路解析

1. 项目概述&#xff1a;这不是简单的“分组求和”&#xff0c;而是多维数据世界的导航仪你有没有遇到过这样的场景&#xff1a;销售报表里要同时按“地区产品线季度”三个维度看销售额&#xff0c;还要对比去年同期、计算环比增长率、标出Top 3区域&#xff0c;最后导出的Exce…

作者头像 李华
网站建设 2026/7/3 4:30:23

从生成视频到交互仿真,地瓜机器人 Uranus 模型实现帧级闭环

机器人仿真为什么难&#xff1f;开发一台能自主完成复杂任务的机器人&#xff0c;离不开大量的测试与验证。然而&#xff0c;真实世界的测试昂贵、耗时且难以复现。让一台机械臂在真实环境中试错成千上万次&#xff0c;意味着设备、人力、场地和时间成本都要持续投入。更麻烦的…

作者头像 李华
网站建设 2026/7/3 4:29:15

欧朋浏览器推新防护功能,可防“点击修复”攻击!

欧朋新防护功能发布ZDNET 要点速览&#xff1a;欧朋&#xff08;Opera&#xff09;发布了一项新的防护功能——“粘贴保护”&#xff08;Paste Protect&#xff09;&#xff0c;可帮助防范“点击修复”&#xff08;ClickFix&#xff09;攻击&#xff0c;且该功能免费并默认开启…

作者头像 李华
网站建设 2026/7/3 4:28:54

一洽小程序接入

接入说明文档以微信小程序作为示例介绍&#xff0c;其他小程序接入操作与此类似1、添加校验文件开发者使用微信小程序提供的 webview 组件可以实现打开一洽的H5对话小程序的“域名配置”中添加一洽的对话域名地址&#xff0c;需要获取校验文件提供给一洽放在域名根目录下&#…

作者头像 李华
网站建设 2026/7/3 4:27:47

搭建微信电商小程序要多少钱:定制和SaaS商城怎么选更适合实体店

很多实体店老板在准备做微信电商小程序时&#xff0c;最先问的往往是价格。但价格本身并不能直接回答“适不适合”。同样是做一个商城小程序&#xff0c;有的项目几百元一年就能上线&#xff0c;有的却要几万甚至更高&#xff0c;本质区别并不只在开发方式&#xff0c;而在需求…

作者头像 李华
网站建设 2026/7/3 4:22:44

具身智能仿真器选型与ROS2实战:MuJoCo/Gazebo/Isaac Sim深度解析

1. 仿真器不是“玩具”&#xff0c;是具身智能的物理世界操作系统很多人第一次接触具身智能&#xff0c;看到Gazebo里小车转圈、机械臂抓杯子&#xff0c;下意识觉得&#xff1a;“哦&#xff0c;就是个3D动画演示”。我带过十几期机器人开发训练营&#xff0c;80%的新手在第三…

作者头像 李华