若依框架登录密码用SM2加密,我踩过的那些坑(附完整Java/JS代码)

张开发
2026/4/9 1:36:38 15 分钟阅读

分享文章

若依框架登录密码用SM2加密,我踩过的那些坑(附完整Java/JS代码)
若依框架集成SM2加密实战从密钥管理到前后端联调避坑指南第一次在若依框架里集成SM2国密算法时我对着控制台里那串解密失败的报错信息发了半小时呆。作为国内广泛使用的开源后台管理系统若依默认的密码传输是明文方式这显然不符合等保要求。SM2作为国家密码管理局认定的商用密码算法自然成为首选方案。但真正落地时才发现从密钥生成到前后端加解密联调每个环节都藏着意想不到的坑。1. 环境准备与依赖管理在Spring Boot项目中引入SM2支持首先面临的就是依赖选择问题。目前主流有两个选择Bouncy Castle和sm-crypto。前者是老牌安全库后者是专为国密算法优化的轻量级实现。!-- 推荐使用system范围引入本地jar -- dependency groupIdorg.bouncycastle/groupId artifactIdbcprov-jdk15to18/artifactId version1.71/version /dependency关键注意点避免同时引入多个密码学库容易导致类冲突JDK8需要额外配置安全策略文件若依默认的Spring Security过滤器会影响加密数据流我曾经遇到过NoSuchProviderException异常最后发现是因为没有正确初始化Bouncy Castle提供者。解决方法是在启动类中添加static { Security.addProvider(new BouncyCastleProvider()); }2. 密钥生成与安全存储SM2的公私钥对生成看似简单但实际生产中绝不能像示例代码那样硬编码在类文件里。我们采用的方案是通过启动参数注入密钥java -jar ruoyi.jar --sm2.privateKey您的私钥 --sm2.publicKey您的公钥对应的配置类设计Configuration public class Sm2Config { Value(${sm2.privateKey}) private String privateKey; Value(${sm2.publicKey}) private String publicKey; Bean public Sm2KeyPair sm2KeyPair() { return new Sm2KeyPair(publicKey, privateKey); } }密钥安全实践生产环境推荐使用HSM硬件安全模块管理密钥测试环境可用Vault等密钥管理系统绝对禁止将密钥提交到代码仓库3. 前后端加解密实现差异前端使用sm-crypto库时最容易忽略的是加密模式参数cipherMode。这个参数决定了填充方式和编码格式必须前后端保持一致// 前端加密配置 const cipherMode 1; // C1C3C2模式 const encryptData sm2.doEncrypt(password, publicKey, cipherMode);对应的Java解密代码需要特别注意十六进制处理public String decrypt(String cipherText) { byte[] cipherData Hex.decode(cipherText); SM2Engine engine new SM2Engine(SM2Engine.Mode.C1C3C2); engine.init(false, new ParametersWithID(new ECPrivateKeyParameters(...))); byte[] decrypted engine.processBlock(cipherData, 0, cipherData.length); return new String(decrypted, StandardCharsets.UTF_8); }常见联调问题前端加密结果在控制台显示正常但后端解密失败 → 检查Base64/Hex编码转换中文解密后乱码 → 确认前后端字符集统一为UTF-8长文本加密异常 → SM2单次加密有长度限制需要分段处理4. 若依框架适配改造若依原有的登录逻辑需要做三处关键修改PasswordEncoder替换自定义SM2PasswordEncoder实现Spring Security的PasswordEncoder接口登录参数处理修改LoginBody对象增加加密标记字段public class LoginBody { private String username; private String password; // 加密后的密文 private boolean encrypted true; // 新加字段 }登录接口改造在LoginService中增加解密逻辑public String login(String username, String password, String code, String uuid) { if (!loginBody.isEncrypted()) { throw new RuntimeException(必须使用SM2加密传输); } String plainPwd sm2Utils.decrypt(password); // ...原有逻辑 }5. 性能优化与监控SM2算法虽然安全但性能相比对称加密要差很多。我们通过以下手段优化缓存加密上下文SM2的密钥初始化开销较大可以使用ThreadLocal缓存private static ThreadLocalSM2Engine engineHolder ThreadLocal.withInitial(() - { SM2Engine engine new SM2Engine(); engine.init(..., new ECPrivateKeyParameters(...)); return engine; });监控加密耗时通过Spring AOP记录加解密时间Around(execution(* com.ruoyi..*.*(..)) annotation(cryptoLog)) public Object logCryptoTime(ProceedingJoinPoint pjp, CryptoLog cryptoLog) { long start System.currentTimeMillis(); Object result pjp.proceed(); long cost System.currentTimeMillis() - start; log.info(SM2操作{}耗时: {}ms, cryptoLog.value(), cost); return result; }降级方案设计配置开关控制是否启用加密sm2: enabled: true fallback: false # 是否允许降级6. 完整工具类实现最后分享我们优化后的SM2工具类主要改进包括异常处理细化性能优化线程安全设计public class Sm2Utils { private static final SM2Engine ENGINE new SM2Engine(); private final ECPrivateKeyParameters privateKeyParams; public Sm2Utils(String privateKey) { this.privateKeyParams new ECPrivateKeyParameters( new BigInteger(privateKey, 16), SM2_DOMAIN_PARAMS ); } public String decrypt(String cipherText) { try { byte[] cipherData Hex.decode(cipherText); synchronized (ENGINE) { ENGINE.init(false, privateKeyParams); byte[] decrypted ENGINE.processBlock(cipherData, 0, cipherData.length); return new String(decrypted, StandardCharsets.UTF_8); } } catch (Exception e) { throw new CryptoException(SM2解密失败, e); } } // 其他工具方法... }在前端项目中我们同样封装了更健壮的加密模块class Sm2Crypto { constructor(publicKey) { this.publicKey publicKey this.cipherMode 1 this.retryCount 0 } encrypt(plainText) { try { return sm2.doEncrypt(plainText, this.publicKey, this.cipherMode) } catch (error) { if (this.retryCount 3) { return this.encrypt(plainText) } throw new Error(SM2加密失败) } } }这套方案在金融级应用中已经稳定运行两年多期间根据实际需求不断优化调整。最深刻的体会是密码学实现不能只停留在能用的层面必须考虑工程化的各种边界情况。

更多文章