1. 国密算法混合加密的必要性在金融、政务等对数据安全要求极高的场景中传统的RSAAES加密方案存在两个明显痛点一是RSA算法密钥长度需求不断增长导致性能下降二是国际通用算法可能存在潜在合规风险。国密SM2/SM4混合加密方案恰好能解决这些问题。SM2作为非对称加密算法基于椭圆曲线密码学ECC在相同安全强度下密钥长度仅为RSA的1/8。实测数据显示SM2签名速度比RSA快5倍以上而验证速度更是快10倍。SM4作为对称加密算法采用128位分组长度加解密效率比3DES提升近10倍。混合加密的精妙之处在于用SM2安全交换SM4会话密钥再用SM4加密业务数据。这种组合既发挥了SM2在密钥交换中的安全性优势又利用了SM4在大量数据加密时的高效性。就像现实中我们用密码箱SM2传递保险柜钥匙SM4再用保险柜SM4保管贵重物品。2. 环境准备与依赖配置2.1 前端Vue环境安装必要的加密库npm install sm-crypto --save # 国密算法核心库 npm install buffer --save # 处理二进制数据在vue.config.js中需要配置全局Buffer支持configureWebpack: { resolve: { fallback: { buffer: require.resolve(buffer/) } } }2.2 后端SpringBoot环境Maven依赖配置示例!-- Hutool工具包含国密支持 -- dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.16/version /dependency !-- BouncyCastle密码库 -- dependency groupIdorg.bouncycastle/groupId artifactIdbcprov-jdk15to18/artifactId version1.72/version /dependency特别提醒BouncyCastle的版本需要与JDK版本匹配。如果遇到NoSuchAlgorithmException异常需要在启动类中添加static { Security.addProvider(new BouncyCastleProvider()); }3. 密钥管理策略3.1 密钥对生成方案前端生成SM2密钥对const sm2 require(sm-crypto).sm2 const keypair sm2.generateKeyPairHex() const publicKey keypair.publicKey // 04开头未压缩公钥 const privateKey keypair.privateKey // 64位私钥后端生成SM2密钥对的Java实现public MapString, String generateSM2Keys() { SM2 sm2 SmUtil.sm2(); // 获取公钥Q值未压缩04开头 byte[] publicKey ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false); // 获取私钥D值 byte[] privateKey BCUtil.encodeECPrivateKey(sm2.getPrivateKey()); return Map.of( publicKey, HexUtil.encodeHexStr(publicKey), privateKey, HexUtil.encodeHexStr(privateKey) ); }3.2 密钥交换流程前端生成临时SM4密钥const sm4Key randomBytes(16).toString(hex) // 32位HEX字符串使用后端公钥加密SM4密钥const encryptedKey 04 sm2.doEncrypt(sm4Key, serverPublicKey, 0)后端解密获取SM4密钥public String decryptSM4Key(String encryptedKey, String privateKey) { SM2 sm2 new SM2(privateKey, null); // 注意去除开头的04标识 return StrUtil.utf8Str(sm2.decryptFromBcd( encryptedKey.substring(2), KeyType.PrivateKey )); }4. 前后端协同加解密实现4.1 前端加密流程完整请求数据加密示例async function sendEncryptedRequest(data) { // 1. 生成临时SM4密钥 const sm4Key generateSM4Key() // 2. 加密业务数据 const sm4 new SM4({ key: sm4Key }) const encryptedData sm4.encrypt(JSON.stringify(data)) // 3. 加密SM4密钥 const encryptedKey encryptSM4Key(sm4Key) // 4. 发送组合数据 return axios.post(/api/secure, { key: encryptedKey, data: encryptedData }, { headers: { Content-Type: application/json } }) }4.2 后端解密处理SpringBoot拦截器实现public class DecryptInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (requiresDecrypt(request)) { // 1. 获取加密的SM4密钥 String encryptedKey request.getHeader(X-Encrypted-Key); // 2. SM2解密获取SM4密钥 String sm4Key decryptSM4Key(encryptedKey); // 3. 读取加密请求体 String encryptedBody IOUtils.toString(request.getReader()); // 4. SM4解密业务数据 String rawBody SmUtil.sm4(sm4Key.getBytes()).decryptStr(encryptedBody); // 5. 替换请求体 request new DecryptedRequestWrapper(request, rawBody); } return true; } }4.3 响应数据加密使用Spring的ResponseBodyAdvice实现响应加密ControllerAdvice public class EncryptResponseAdvice implements ResponseBodyAdviceObject { Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType mediaType, Class? extends HttpMessageConverter? converterType, ServerHttpRequest request, ServerHttpResponse response) { // 1. 生成临时SM4密钥 String sm4Key generateSM4Key(); // 2. 加密响应数据 String encryptedData SmUtil.sm4(sm4Key.getBytes()) .encryptHex(JSON.toJSONString(body)); // 3. 加密SM4密钥 String encryptedKey SmUtil.sm2(null, clientPublicKey) .encryptBcd(sm4Key, KeyType.PublicKey); // 4. 返回加密结果 return Map.of( key, encryptedKey, data, encryptedData ); } }5. 常见问题解决方案5.1 前端密钥格式问题常见错误Invalid publicKey length通常是因为公钥格式不正确。正确处理方式// 正确格式04开头x坐标y坐标共130字符 const validPublicKey publicKey.startsWith(04) ? publicKey : 04 publicKey5.2 后端解密失败排查典型错误日志分析Caused by: org.bouncycastle.crypto.InvalidCipherTextException: invalid cipher text解决方案检查清单确认使用的加密模式C1C2C3或C1C3C2检查私钥是否匹配公钥验证密文是否包含04前缀确认密文传输过程中未被修改5.3 性能优化建议会话复用成功建立通信后可复用SM4密钥5-10分钟减少SM2解密次数批量处理对列表数据先JSON序列化再整体加密减少加密操作次数硬件加速使用支持SM2/SM4指令集的国产芯片如鲲鹏9206. 完整示例代码6.1 Vue前端实现封装加密工具类import { sm2, sm4 } from sm-crypto export default { // SM2加密处理各种数据类型 encryptSM2(data, publicKey) { const strData typeof data object ? JSON.stringify(data) : String(data) return 04 sm2.doEncrypt(strData, publicKey, 0) }, // SM4加密初始化 initSM4(key) { return { encrypt(data) { const strData JSON.stringify(data) return sm4.encrypt(strData, key) }, decrypt(encrypted) { const decrypted sm4.decrypt(encrypted, key) try { return JSON.parse(decrypted) } catch { return decrypted } } } } }6.2 SpringBoot后端实现统一加解密过滤器WebFilter(/api/*) public class CryptoFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 解密处理 if (isEncryptedRequest(request)) { CryptoRequestWrapper requestWrapper new CryptoRequestWrapper((HttpServletRequest)request); chain.doFilter(requestWrapper, response); } // 加密处理 else if (isEncryptResponse(request)) { CryptoResponseWrapper responseWrapper new CryptoResponseWrapper((HttpServletResponse)response); chain.doFilter(request, responseWrapper); encryptResponse(responseWrapper, (HttpServletResponse)response); } else { chain.doFilter(request, response); } } private void encryptResponse(CryptoResponseWrapper wrapper, HttpServletResponse response) throws IOException { // 获取原始响应数据 String content wrapper.getCaptureAsString(); // 执行加密逻辑 EncryptedResult result encryptService.encryptResponse(content); // 写入加密后响应 response.setContentType(application/json); response.getWriter().write(JSON.toJSONString(result)); } }在实际项目中我们曾遇到一个典型案例某政务系统迁移到国密算法后初期出现约5%的请求解密失败。最终排查发现是前端SM2库默认使用C1C3C2模式而后端使用的是C1C2C3模式。通过统一配置sm2.setMode(SM2Engine.Mode.C1C2C3)解决了该问题。这提醒我们在混合加密方案中前后端的算法参数必须严格保持一致。