
简介面向Java开发者的国密算法实用工具包聚焦SM2非对称加密与SM3摘要算法在字符串及文件场景下的落地应用。针对等保合规、数据脱敏、安全传输等需求提供了可直接运行的加解密示例与封装好的工具类。包内共5个文件其中4个Java源码文件分别承担密钥工具、加解密封装、文件读写与摘要计算等职责另附1个国密算法依赖jar包bcprov-jdk15on-1.68.jar源码覆盖密钥生成、字符串加解密、文件流处理与摘要校验等功能模块引入依赖后即可快速集成到业务系统中。压缩包整体约4.89MB轻量易用。已有428人学习浏览特别适合初次接触国密改造、需要参考成熟代码结构的开发者使用读者可据此掌握SM2密钥对管理、文件分块加解密及SM3数据完整性校验的实现思路并复用现成工具类减少重复开发。1. 不是所有国产算法都叫国密这套 SM2/SM3 加解密 jar 包能直接解决什么问题接手过等保整改或者信创适配的人都懂一个尴尬客户点名要“国密加密”但翻遍业务代码最后只是拿 MD5 加了个盐。真正的国密算法里SM2 是非对称加密SM3 是摘要两者各管一段配合起来才能同时解决“数据不被偷看”和“数据不被篡改”两个问题。这套带 jar 包的 SM2/SM3 字符文件加解密资源解决的就是这种落地场景——不是给你讲论文是把可运行的 jar 包、字符加解密、文件加解密的关键代码一次性凑齐适合正在做接口加密、文件传输校验、密钥管理系统预研的 Java 开发。我用它把某跨平台系统里的敏感文件走完了混合加密改造整个过程踩了不少坑下面按我的实操顺序把每一步写清楚。2. SM2 与 SM3 的原理再确认曲线参数、填充规则和它们为什么成对出现2.1 SM2 的椭圆曲线参数与非对称体系SM2 基于椭圆曲线离散对数问题推荐曲线名为 sm2p256v1素数域 256 位。很多人上手就跑代码忽略了它的几个关键参数后面踩坑都不知道往哪查。标准里明确了基点 G 和非公开的阶 n私钥是一个大整数 d公钥是曲线上一个点 P d * G。这套 jar 包里内置了曲线参数不需要自己写但你得知道它用的是素域而非二进制域密钥长度固定 256 位密文格式分 C1C2C3 和 C1C3C2 两种。参数项值/类型说明曲线名称sm2p256v1标准推荐曲线密钥长度256 位私钥 d公钥 P密文排列C1C2C3 / C1C3C2C1 为点C2 为密文C3 为摘要加密数据上限受密钥长度限制不适合大块数据建议混合加密2.2 SM3 的填充规则与 256 位摘要SM3 的摘要长度固定 256 位输出 32 字节。它的填充规则跟 SHA-256 类似消息尾部补 0x80再补到长度对 512 取模余 448最后 64 位存原始消息长度。这个细节在你做流式分块哈希时特别重要因为分段计算要自己维护长度状态如果每一段都单独算 SM3最后拼出来的摘要会跟整文件计算结果不一致。jar 包里通常提供了一性次计算接口和增量计算接口优先用增量接口处理大文件。2.3 为什么加解密场景里两个算法必须搭配只加密不校验密文被篡改了你自己都发现不了只摘要不加密敏感内容直接明文暴露。SM2 负责用公钥加密、私钥解密SM3 负责生成摘要用来验完整性两者一组合就形成一套完整的保密加防篡改链路。实际项目里最常见的做法是SM2 加密少量数据比如会话密钥、用户口令SM3 给文件或报文生成摘要再配合签名接口做身份认证。这套 jar 包把 SM2 加解密、SM3 摘要、签名验签都封装好了省去从头读标准的时间。3. 把 jar 包跑起来工程结构、JDK 版本与字符加解密第一行代码3.1 jar 包内包含什么怎么引入项目解压后会看到核心可执行 jar、源码包和依赖说明。我拿到手第一件事不是写代码是先确认 JDK 版本和依赖避免后面 class 冲突。这个资源理论上兼容 JDK 8 以上但实际跑的时候我遇到过一个旧环境问题JDK 8 的 JCE 默认限制了密钥长度SM2 本身不受影响但如果你同时引入 BouncyCastle需要留意 provider 注册顺序。建议工程里新建一个 lib 目录直接把 jar 放进去通过 IDE 添加依赖或者用 Maven 安装到本地仓库mvn install:install-file \ -Dfile./lib/sm2sm3-crypto.jar \ -DgroupIdcom.example.crypto \ -DartifactIdsm2sm3-crypto \ -Dversion1.0.0 \ -Dpackagingjar这条命令把本地 jar 装进 Maven 本地仓库之后在 pom.xml 里声明坐标即可引用。-Dfile指向 jar 实际路径-DgroupId和-DartifactId自己定义只要团队内部统一就行。如果你不用 Maven直接把 jar 丢进 WEB-INF/lib 也行但注意别和项目里已有的 BouncyCastle 老版本同时出现容易触发 NoSuchAlgorithmException。3.2 字符加密的完整流程SM2 公钥加密、私钥解密字符加解密是这个 jar 包最直接的用法。核心逻辑是生成密钥对然后用公钥加密字符串私钥解密。下面是去掉异常处理后的核心代码保留关键注释// 生成 SM2 密钥对 KeyPairGenerator generator KeyPairGenerator.getInstance(SM2, BC); KeyPair keyPair generator.generateKeyPair(); byte[] publicKey keyPair.getPublic().getEncoded(); byte[] privateKey keyPair.getPrivate().getEncoded(); // 公钥加密 Cipher cipher Cipher.getInstance(SM2, BC); cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic()); byte[] encrypted cipher.doFinal(敏感字符内容.getBytes(StandardCharsets.UTF_8)); // 私钥解密 cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); byte[] decrypted cipher.doFinal(encrypted);这里有几处容易踩坑KeyPairGenerator.getInstance的第二个参数是 Provider 名称必须确保 BouncyCastle 已经通过Security.addProvider(new BouncyCastleProvider())注册。另外SM2 加密结果不是固定长度它跟明文长度正相关而且会比明文长不少后面会专门说密文膨胀问题。如果doFinal抛InvalidCipherTextException优先检查是否混用了不同 Curve 参数生成的密钥对。3.3 签名与验签SM3 摘要 SM2 签名配合使用实际业务里更常见的是签名验签用 SM3 对原始数据生成摘要再用 SM2 私钥对摘要签名对方拿到公钥验签。这个 jar 包里封装了签名接口比我早期手动拼摘要再签名的方式简洁很多而且避开了摘要编码不一致的坑。// 私钥签名内部自动完成 SM3 摘要 Signature signature Signature.getInstance(SM3withSM2, BC); signature.initSign(keyPair.getPrivate()); signature.update(originalData.getBytes(StandardCharsets.UTF_8)); byte[] signBytes signature.sign(); // 公钥验签 signature.initVerify(keyPair.getPublic()); signature.update(originalData.getBytes(StandardCharsets.UTF_8)); boolean valid signature.verify(signBytes);注意SM3withSM2这个算法名不是标准里写的而是 BouncyCastle 的约定叫法。update方法可以多次调用效果等同于把全部数据拼接后一次性签名所以大报文可以分段update不必一次性加载到内存。验签失败时先看原始数据是否有 BOM 头、换行符差异字节层面不相等验签必失败这属于最常见的“没改业务却验不过”的原因。4. 文件加解密实战混合加密方案与 SM3 完整性校验4.1 为什么不直接用 SM2 加密大文件有人拿 SM2 直接加密整个文件结果翻车了不是加密失败而是性能惨不忍睹密文膨胀严重几百 MB 的文件加密到超时。原因是 SM2 本质面向小数据量椭圆曲线点运算开销大密文也有固定开销。正确的做法是混合加密用对称加密算法 SM4 加密文件内容再用 SM2 加密 SM4 的会话密钥。这样兼顾性能和密钥分发安全是国密改造里最标准的落地套路。4.2 混合加密落地SM4 对称密钥 SM2 保护会话密钥整个流程分三步首先生成一个随机的 SM4 会话密钥其次用 SM4 对文件做流式加解密最后用对方的 SM2 公钥加密这个 SM4 密钥并随密文一起传输。接收方先 SM2 解密得到 SM4 密钥再用它解密文件。// 1. 生成 SM4 会话密钥并加密这里简化为直接生成字节 KeyGenerator sm4Gen KeyGenerator.getInstance(SM4); SecretKey sm4Key sm4Gen.generateKey(); // 2. 用上方 SM2 公钥加密 SM4 密钥字节 Cipher sm2Cipher Cipher.getInstance(SM2, BC); sm2Cipher.init(Cipher.ENCRYPT_MODE, receiverPublicKey); byte[] encryptedSm4Key sm2Cipher.doFinal(sm4Key.getEncoded()); // 3. SM4 流式加密文件 Cipher sm4Cipher Cipher.getInstance(SM4/ECB/PKCS5Padding); sm4Cipher.init(Cipher.ENCRYPT_MODE, sm4Key); try (FileInputStream fis new FileInputStream(sourceFile); FileOutputStream fos new FileOutputStream(encryptedFile)) { byte[] buffer new byte[8192]; int len; while ((len fis.read(buffer)) ! -1) { byte[] output sm4Cipher.update(buffer, 0, len); if (output.length 0) { fos.write(output); } } byte[] finalOutput sm4Cipher.doFinal(); if (finalOutput.length 0) { fos.write(finalOutput); } }这里用了SM4/ECB/PKCS5Padding优点是简单缺点是相同明文块会产生相同密文块对文件这种结构化数据不够安全。我一般建议改SM4/CBC/PKCS5Padding需要在初始化时多传一个IvParameterSpecIV 随机生成并拼在密文文件头。示例代码里read和update配合是标准的流式写法doFinal只处理最后不足一块的数据注意它可能返回空数组不能假设每次都有输出。加密完成后把encryptedSm4Key和 IV 按约定格式写入文件头比如前 4 字节存长度接着是数据。接收方读取时先解析头部再走逆过程解密。4.3 文件校验链SM3 摘要如何保证传输完整性文件传输场景里加密只能防泄露防篡改要靠摘要。做法是加密前先算一次原文件的 SM3 摘要解密后再对解密结果算一次两边比对。下面是用增量接口处理大文件的代码MessageDigest digest MessageDigest.getInstance(SM3, BC); try (FileInputStream fis new FileInputStream(encryptedFile)) { byte[] buffer new byte[8192]; int len; while ((len fis.read(buffer)) ! -1) { digest.update(buffer, 0, len); } } byte[] fileDigest digest.digest(); String hexDigest Hex.toHexString(fileDigest);增量计算的要点是全程用一个MessageDigest实例每次update都是在累加状态。之前有人分段单独调用digest()结果每一段摘要互不相干比对自然失败。Hex.toHexString来自 BouncyCastle也可以自行实现十六进制转换总之确保比对双方都转成同一种编码格式。把摘要和加密文件一起传给接收方接收方解密完重算哈希并比对一旦不等说明文件在传输或存储环节被动过。5. 避坑与常见问题签名验签失败、密文膨胀、密钥格式不兼容5.1 签名验签失败很可能是哈希值没转成字节数组现象用 SM3 算出摘要后转成十六进制字符串拼进待签名字符串结果验签永远返回 false。原因签名算法内部做的是字节比对。你把摘要转成字符串再 getBytes()此时字节内容已经从二进制变成了 ASCII 字符和原始摘要字节完全不同。解决签名和验签全程只操作原始字节。如果必须经过字符串传输用 Base64 编码接收方先解码再喂给验签接口不要直接用 UTF-8 转换二进制数据。5.2 密文长度暴涨SM2 非对称加密不适合大块数据现象用 SM2 加密一段 100 字节的字符串密文变成 200 多字节试着加密 1 MB 文本程序卡死或抛异常。原因SM2 每个加密分组都包含椭圆曲线点 C1至少 65 字节和 SM3 摘要 C332 字节再加上数据本身的 C2密文有固定膨胀。数据块越大性能和空间开销越不可接受。解决回到第 4 章的混合加密方案。SM2 只负责加密 SM4 会话密钥文件本体由 SM4 处理。这也是金融行业标准里推荐的模式。5.3 密钥格式不兼容C1C2C3 与 C1C3C2 的顺序现象对方用 C1C3C2 顺序解析你的密文你按 C1C2C3 输出两边的加解密结果对不上。原因国家标准里两种排列都存在不同厂商实现默认顺序不同。解决先确认 jar 包内部默认采用哪种顺序再看对接方要求。如果可配置在初始化时显式指定不要依赖默认值。对接前先拿一条固定明文和固定密钥互相验证能省掉后期排查的大量时间。5.4 随机数质量导致签名不稳定现象同一段数据对不同密钥对签名验签都通过但用固定密钥签多次每次签名结果不同部分验签失败。原因SM2 签名引入了随机数 k。某些 JDK 版本在无熵源环境下生成的 k 质量差或者实现有缺陷导致签名的 r、s 值出现异常。解决升级 JDK 版本或者检查运行环境的熵源。容器场景里最常见的坑是没有安装 rng-tools导致 SecureRandom 阻塞。代码里不要自己 new Random() 传种子给签名接口必须使用安全的 SecureRandom。6. 更进一步密钥管理系统设计与性能摸底两件事6.1 密钥管理的最小可用方案密钥对生成容易管理难。我在模拟项目X里用的方案是密钥对生成后立即用 Base64 编码保存私钥单独存放到配置中心或者独立密钥文件权限收紧到只有服务账号能读。公钥可以随服务下发但私钥绝不能出现在日志或版本库里。轮换策略上我习惯按季度轮换签名密钥对加密密钥对按年度轮换每次轮换前用旧私钥把存量数据解密重签避免历史数据失效。密钥文件的权限在 Linux 上设为 600属主设为启动服务的专用账号别用 root 跑。6.2 性能基准与参数调优最后说性能摸底。SM2 加密一次约耗时 1 到 5 毫秒远高于 AES 的微秒级所以千万不能让 SM2 出现在高频业务路径上。SM3 的吞吐量大约在每秒几百 MB 到 1 GB 之间取决于实现和 CPU 指令集。做性能测试时线程数从 1 慢慢加到 32观察耗时曲线如果出现拐点提前优先检查 SecureRandom 是否成为瓶颈再考虑连接池和线程池参数。从那以后我每次引入国密组件都会先写一个 5 分钟的压力脚本跑一遍确认加解密耗时、内存占用和 GC 表现稳定再让业务方接进来。这套流程逼着我提前发现了几次环境缺熵的翻车事故省下了上线前的抢救时间希望帮到你。本文还有配套的精品资源点击获取