Spring Boot集成SM4-GCM国密算法实战:从AES迁移到合规数据加密

发布时间:2026/7/14 9:47:13
Spring Boot集成SM4-GCM国密算法实战:从AES迁移到合规数据加密 1. 项目概述为什么是SM4-GCM最近在重构一个金融相关的后台服务时遇到了一个老生常谈但又不得不面对的问题数据加密。项目里历史遗留的AES加密代码散落在各处每次审计或者对接新的监管平台时都要解释一番。终于在最近一次安全合规检查后我们决定彻底拥抱国密算法将核心的敏感数据加密从AES迁移到SM4并且直接采用更现代的GCM模式。如果你也在Spring Boot项目里用过AES并且对“国密改造”感到无从下手或者担心性能、兼容性等问题那么我这次从零到一的实战踩坑记录或许能给你提供一个清晰的路线图。简单来说SM4是一种由国家密码管理局认定的商用密码算法和AES一样是分组加密算法分组长度128位密钥长度也是128位。在安全强度上它与AES-128是相当的。那为什么还要换最直接的驱动力来自合规。在一些对数据安全有明确要求的行业比如金融、政务、电信等使用国家认可的密码算法逐渐成为硬性要求。这不仅仅是“支持国货”的情怀更是项目能够顺利通过验收、安全评估的必备条件。而选择GCMGalois/Counter Mode模式而不是更常见的ECB或CBC模式则是一次技术上的升级。GCM是一种认证加密模式它不仅能保证数据的机密性加密还能同时保证数据的完整性防篡改和真实性防伪造。简单类比一下以前的CBC模式好比只给箱子上了把锁加密但箱子可能在运输途中被调包或拆开重封缺少完整性校验。而GCM模式除了上锁还会在箱子上贴一个独一无二的、无法伪造的封条认证标签。接收方只有用正确的钥匙开锁并且封条完好无损才会认可箱内的东西。这对于防止密文在传输或存储过程中被恶意篡改至关重要尤其是在微服务间API调用或数据持久化场景下。所以这次实战的目标很明确在一个典型的Spring Boot Web应用中集成SM4-GCM算法提供一套易于使用的加密解密工具类并确保其性能、安全性与可维护性最终告别传统的AES。2. 核心思路与整体设计在动手写代码之前先花点时间把整体思路理清楚。我们的目标不是简单地找到一个SM4实现然后调用而是要设计一个适合在Spring Boot工程化项目中使用的、健壮的加密解密组件。2.1 技术选型与依赖考量首先面临的是基础库的选择。Java标准库JCE默认并不包含国密算法的实现。因此我们需要引入第三方提供者。常见的开源选择有Bouncy CastleBC和国密的官方参考实现。Bouncy Castle是一个成熟的、广泛使用的密码学库支持算法全面社区活跃且其BCPG组件就包含了SM4的实现。对于大多数项目选择Bouncy Castle是稳妥且方便的。在Spring Boot项目中我们通过Maven引入依赖。这里需要注意版本兼容性和潜在的安全漏洞。我选择了较新且稳定的版本。dependency groupIdorg.bouncycastle/groupId artifactIdbcprov-jdk15to18/artifactId version1.78/version /dependency为什么是bcprov-jdk15to18这个artifact包含了Bouncy Castle的JCE提供者实现并且命名上指明了其兼容的JDK范围15到18。对于使用JDK 8或11的项目通常使用bcprov-jdk15on但后者版本较老。选择较新版本能获得更好的维护和可能的安全更新。务必在引入后检查是否有已知的高危CVE。2.2 架构设计工具类与配置化我们不希望加密逻辑硬编码在业务代码里。一个好的设计是将其抽象成一个独立的、可配置的工具类Sm4GcmUtil。这个工具类应该承担以下职责初始化静态加载Bouncy Castle提供者确保SM4算法可用。密钥管理提供密钥的生成、转换如从Base64字符串加载和存储建议。密钥绝不能写在代码里核心方法提供encrypt和decrypt方法方法签名要清晰处理GCM模式所需的参数如初始向量IV和认证标签AAD。异常处理定义明确的异常类型如密钥无效、密文被篡改AEADBadTagException等便于上层业务捕获和处理。线程安全确保工具类在多线程环境下安全使用。此外密钥、IV长度等参数应该可配置。我们将利用Spring Boot的ConfigurationProperties或Value注解将其放在application.yml中实现环境隔离开发、测试、生产使用不同的密钥。2.3 GCM模式参数详解这是整个集成的技术核心理解它们才能正确使用。密钥Key128位16字节。这是加解密的根本需要绝对保密。初始向量IV, Initialization Vector在GCM模式下IV推荐长度为12字节96位。它不需要保密但必须唯一对于同一个密钥绝对不要重复使用同一个IV否则会严重削弱安全性。通常每次加密都随机生成一个新的IV并随密文一起传输或存储。附加认证数据AAD, Additional Authenticated Data这是一段不需要加密但需要被认证的明文数据。例如你可以将数据包的头信息、协议版本号作为AAD。在解密时如果AAD与加密时不一致解密会失败认证标签验证不通过。这是一个非常强大的特性可以防止上下文替换攻击。如果暂时没有特定需求可以传null或空字节数组。认证标签Authentication TagGCM加密后输出的两部分之一另一部分是密文。默认长度通常是128位16字节。它就像是那个“封条”用于验证密文和AAD的完整性。设计时我们决定采用一种常见的输出格式将IV、密文、认证标签拼接在一起再进行Base64编码得到一个字符串。解密时再按规则解析。这样对外只需传输或存储一个字符串比较方便。3. 核心工具类实现与代码解析理论清晰后我们来看具体的代码实现。我会创建一个Sm4GcmUtil类并逐部分解释。3.1 静态初始化与算法定义首先在类加载时我们需要将Bouncy Castle注册为JCE的安全提供者。这样后续的Cipher.getInstance(“SM4/GCM/NoPadding”)调用才能成功。import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.SecureRandom; import java.security.Security; import java.util.Base64; public class Sm4GcmUtil { private static final String ALGORITHM “SM4”; private static final String TRANSFORMATION “SM4/GCM/NoPadding”; private static final int TAG_LENGTH_BIT 128; // GCM认证标签长度单位比特 private static final int IV_LENGTH_BYTE 12; // 推荐IV长度单位字节 private static final int KEY_LENGTH_BIT 128; // SM4密钥长度单位比特 static { // 静态代码块确保Bouncy Castle提供者被加载 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) null) { Security.addProvider(new BouncyCastleProvider()); } } // ... 后续方法 }注意static代码块中的判断很重要。重复添加同一个提供者通常不会报错但显式判断一下更严谨。TRANSFORMATION字符串“SM4/GCM/NoPadding”指明了算法、模式和填充方式。GCM模式本身不需要对数据进行填充NoPadding。3.2 密钥生成与加载密钥需要妥善管理。工具类可以提供生成随机密钥的方法但更多时候密钥是从配置中心或环境变量中读取的Base64字符串。/** * 生成一个随机的SM4密钥128位 * return 生成的SecretKey对象 */ public static SecretKey generateKey() throws Exception { KeyGenerator keyGenerator KeyGenerator.getInstance(ALGORITHM, BouncyCastleProvider.PROVIDER_NAME); keyGenerator.init(KEY_LENGTH_BIT, new SecureRandom()); return keyGenerator.generateKey(); } /** * 将Base64编码的密钥字符串转换为SecretKey对象 * param base64Key Base64编码的密钥字符串 * return SecretKey对象 */ public static SecretKey convertStringToKey(String base64Key) { byte[] keyBytes Base64.getDecoder().decode(base64Key); // SM4密钥算法名称就是“SM4” return new SecretKeySpec(keyBytes, ALGORITHM); } /** * 将SecretKey对象转换为Base64编码的字符串便于存储 * param secretKey 密钥对象 * return Base64编码的密钥字符串 */ public static String convertKeyToString(SecretKey secretKey) { byte[] keyBytes secretKey.getEncoded(); return Base64.getEncoder().encodeToString(keyBytes); }实操心得generateKey方法在初始化系统或轮换密钥时有用。而convertStringToKey是日常使用最多的。务必确保你的配置源是安全的如Vault、KMS并且不同环境dev, staging, prod使用不同的密钥。永远不要将生产密钥提交到代码仓库。3.3 加密方法实现加密方法需要接收明文、密钥并处理IV和AAD。我们按照设计输出一个包含IV、密文和标签的Base64字符串。/** * SM4-GCM加密 * param plaintext 明文字节数组 * param key 加密密钥 * param aad 附加认证数据可为null * return Base64编码的字符串格式为IV(12字节) 密文 认证标签(16字节) * throws Exception 加密过程中的异常 */ public static String encrypt(byte[] plaintext, SecretKey key, byte[] aad) throws Exception { // 1. 生成随机IV (12字节) byte[] iv new byte[IV_LENGTH_BYTE]; SecureRandom secureRandom new SecureRandom(); secureRandom.nextBytes(iv); // 2. 初始化Cipher为加密模式 Cipher cipher Cipher.getInstance(TRANSFORMATION, BouncyCastleProvider.PROVIDER_NAME); GCMParameterSpec parameterSpec new GCMParameterSpec(TAG_LENGTH_BIT, iv); cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec); // 3. 如果提供了AAD则更新 if (aad ! null aad.length 0) { cipher.updateAAD(aad); } // 4. 执行加密得到密文认证标签 byte[] ciphertextWithTag cipher.doFinal(plaintext); // 5. 组合 IV 密文与标签 byte[] combined new byte[iv.length ciphertextWithTag.length]; System.arraycopy(iv, 0, combined, 0, iv.length); System.arraycopy(ciphertextWithTag, 0, combined, iv.length, ciphertextWithTag.length); // 6. 返回Base64编码结果 return Base64.getEncoder().encodeToString(combined); } // 重载方法方便处理字符串明文和字符串AAD public static String encrypt(String plaintext, SecretKey key, String aad) throws Exception { byte[] aadBytes (aad null) ? null : aad.getBytes(StandardCharsets.UTF_8); return encrypt(plaintext.getBytes(StandardCharsets.UTF_8), key, aadBytes); }关键点解析SecureRandom生成IV确保密码学意义上的强随机性这是安全的基础。GCMParameterSpec这个类封装了GCM模式所需的参数即标签长度和IV。必须与解密时保持一致。cipher.updateAAD(aad)这一步设定了AAD。注意它必须在cipher.init()之后cipher.doFinal()之前调用。cipher.doFinal()这个方法在GCM模式下返回值包含了密文和认证标签。我们不需要也无法将它们分开解密时需要原样传入。组合IV ciphertextWithTag这是一种常见的封装方式。解密方需要知道如何拆分。我们约定前12字节是IV后面全部是密文标签。3.4 解密方法实现解密是加密的逆过程需要从组合字符串中解析出IV和密文标签然后进行解密和认证。/** * SM4-GCM解密 * param combinedBase64 加密方法返回的Base64字符串 * param key 解密密钥与加密密钥相同 * param aad 附加认证数据必须与加密时一致可为null * return 明文字节数组 * throws Exception 解密失败、认证失败等异常 */ public static byte[] decrypt(String combinedBase64, SecretKey key, byte[] aad) throws Exception { // 1. Base64解码 byte[] combined Base64.getDecoder().decode(combinedBase64); // 2. 拆分出IV和密文认证标签 if (combined.length IV_LENGTH_BYTE) { throw new IllegalArgumentException(“Invalid combined data length”); } byte[] iv new byte[IV_LENGTH_BYTE]; byte[] ciphertextWithTag new byte[combined.length - IV_LENGTH_BYTE]; System.arraycopy(combined, 0, iv, 0, IV_LENGTH_BYTE); System.arraycopy(combined, IV_LENGTH_BYTE, ciphertextWithTag, 0, ciphertextWithTag.length); // 3. 初始化Cipher为解密模式 Cipher cipher Cipher.getInstance(TRANSFORMATION, BouncyCastleProvider.PROVIDER_NAME); GCMParameterSpec parameterSpec new GCMParameterSpec(TAG_LENGTH_BIT, iv); cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec); // 4. 更新AAD必须与加密时一致 if (aad ! null aad.length 0) { cipher.updateAAD(aad); } // 5. 执行解密内部会验证认证标签 return cipher.doFinal(ciphertextWithTag); } // 重载方法返回字符串明文 public static String decryptToString(String combinedBase64, SecretKey key, String aad) throws Exception { byte[] aadBytes (aad null) ? null : aad.getBytes(StandardCharsets.UTF_8); byte[] plaintextBytes decrypt(combinedBase64, key, aadBytes); return new String(plaintextBytes, StandardCharsets.UTF_8); }解密过程的核心在于第5步的cipher.doFinal(ciphertextWithTag)。这个方法会自动验证认证标签。如果标签验证失败即密文或AAD被篡改则会抛出javax.crypto.AEADBadTagException它是BadPaddingException的子类。这意味着我们不需要手动去计算和比较标签密码学库已经帮我们完成了最关键的完整性校验。这是GCM模式相比CBC模式在安全性上的一大优势。4. 在Spring Boot项目中集成与配置有了核心工具类接下来就是如何优雅地将其融入Spring Boot项目。我们的目标是通过配置注入密钥并通过Spring管理的Bean来提供加密服务。4.1 创建配置属性类首先定义一个配置类来读取application.yml中的密钥。import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import lombok.Data; Component ConfigurationProperties(prefix “sm4”) Data public class Sm4Properties { /** * SM4密钥Base64编码格式长度必须为16字节解码后 */ private String key; }在application.yml中配置sm4: key: “你的16字节密钥经过Base64编码后的字符串” # 例如: KkHjfL9pZR8n7qM1警告这里的密钥是示例绝对不要使用简单的或公开的密钥。生产环境应该从安全的密钥管理系统如HashiCorp Vault, AWS KMS, 或公司内部的配置中心动态获取。4.2 创建Spring Bean - 加密服务我们创建一个Sm4GcmService它将Sm4Properties中的密钥加载进来并对外提供加密解密方法。这样业务代码只需要注入这个Service即可。import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.crypto.SecretKey; Service public class Sm4GcmService { Autowired private Sm4Properties sm4Properties; private SecretKey secretKey; /** * 在Bean初始化后根据配置的字符串加载密钥 */ PostConstruct public void init() { if (sm4Properties.getKey() null || sm4Properties.getKey().trim().isEmpty()) { throw new IllegalStateException(“SM4密钥未在配置文件中设置 (sm4.key)”); } this.secretKey Sm4GcmUtil.convertStringToKey(sm4Properties.getKey()); } /** * 加密文本 * param plaintext 明文 * return 加密后的Base64字符串 */ public String encryptText(String plaintext) { try { // 本例中暂不使用AAD return Sm4GcmUtil.encrypt(plaintext, secretKey, (String) null); } catch (Exception e) { throw new RuntimeException(“SM4加密失败”, e); } } /** * 解密文本 * param ciphertext 加密后的Base64字符串 * return 解密后的明文 */ public String decryptText(String ciphertext) { try { return Sm4GcmUtil.decryptToString(ciphertext, secretKey, (String) null); } catch (javax.crypto.BadPaddingException e) { // 特别处理认证失败的情况可能是密钥错误或数据被篡改 throw new RuntimeException(“解密失败认证标签错误数据可能被篡改或密钥不正确”, e); } catch (Exception e) { throw new RuntimeException(“SM4解密失败”, e); } } // 可以继续添加更多方法如加密/解密字节数组、带AAD的方法等。 public byte[] encryptData(byte[] data, byte[] aad) throws Exception { return Base64.getDecoder().decode(Sm4GcmUtil.encrypt(data, secretKey, aad)); } public byte[] decryptData(String base64Ciphertext, byte[] aad) throws Exception { return Sm4GcmUtil.decrypt(base64Ciphertext, secretKey, aad); } }这个Service做了几件重要的事依赖注入通过Autowired获取配置。初始化密钥在PostConstruct注解的方法中将配置的Base64字符串转换为SecretKey对象。如果配置为空则直接抛出异常让应用启动失败避免运行时出错。异常转换将底层密码学异常转换为更友好的RuntimeException并特别处理了BadPaddingException在GCM模式下通常意味着认证失败给业务方更明确的错误信息。提供便捷API提供了最常用的字符串加解密方法。4.3 在业务代码中使用现在在任何需要加解密的Spring管理的Bean中如Controller, Service你都可以轻松地注入并使用Sm4GcmService。import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; RestController RequestMapping(“/api/demo”) public class DemoController { Autowired private Sm4GcmService sm4GcmService; PostMapping(“/encrypt”) public ApiResponse encrypt(RequestParam String data) { String encrypted sm4GcmService.encryptText(data); return ApiResponse.success(“加密成功”, encrypted); } PostMapping(“/decrypt”) public ApiResponse decrypt(RequestParam String ciphertext) { try { String decrypted sm4GcmService.decryptText(ciphertext); return ApiResponse.success(“解密成功”, decrypted); } catch (RuntimeException e) { return ApiResponse.error(“解密失败” e.getMessage()); } } // 一个更实际的例子保护数据库中的手机号字段 Service public class UserService { Autowired private Sm4GcmService sm4GcmService; Autowired private UserRepository userRepository; public void saveUser(User user) { // 入库前加密敏感字段 user.setEncryptedPhone(sm4GcmService.encryptText(user.getPlainPhone())); userRepository.save(user); } public User getUserById(Long id) { User user userRepository.findById(id).orElseThrow(); // 查询后解密敏感字段可在DTO层或视图层按需解密 user.setPlainPhone(sm4GcmService.decryptText(user.getEncryptedPhone())); return user; } } }通过这种方式业务代码与具体的加密算法实现完全解耦。如果未来需要更换算法或调整参数只需要修改Sm4GcmUtil和Sm4GcmService业务逻辑几乎不受影响。5. 关键注意事项与生产环境踩坑实录集成过程并非一帆风顺下面分享几个在测试和生产部署中遇到的真实问题及解决方案。5.1 IV的唯一性与管理问题GCM模式的安全性严重依赖于IV的唯一性。同一个密钥下如果IV重复使用会导致严重的安全漏洞攻击者可能推导出密钥信息。最初我们曾考虑将IV固定存储在配置里这是极其危险的。解决方案必须保证每次加密都使用密码学安全的随机数生成器生成新的IV。我们的工具类中使用了SecureRandom。并且IV需要随密文一起传递给解密方。我们采用的“IV密文标签”的拼接方式是一种可靠的做法。解密方无需猜测IV直接从字符串中提取即可。进阶思考对于海量数据加密SecureRandom的性能可能成为瓶颈。在生产环境中可以考虑使用/dev/urandomLinux或CryptGenRandomWindows作为随机源或者使用从硬件安全模块HSM生成的随机数。在Java中通常new SecureRandom()会默认使用操作系统的强随机源。5.2 认证失败AEADBadTagException排查这是解密时最常见的问题。错误信息通常是javax.crypto.AEADBadTagException: Tag mismatch!。这意味着认证标签验证失败原因可能有密钥错误加密和解密使用的密钥不一致。检查配置sm4.key在加解密双方是否完全相同注意是否有空格或编码问题。AAD不一致如果加密时使用了AAD解密时必须提供完全相同的AAD字节对字节一致。检查AAD字符串的编码如UTF-8和内容。密文被篡改密文在传输或存储过程中发生了任何改变哪怕是一个比特。IV解析错误解密时从组合字符串中拆分IV的规则必须与加密时完全一致。检查IV_LENGTH_BYTE常量必须是12和拆分逻辑。数据格式错误Base64字符串可能包含换行符或URL不安全的字符导致解码错误。确保使用标准的Base64编解码器java.util.Base64。排查清单第一步确认密钥一致。第二步确认加解密双方代码中的TRANSFORMATION、TAG_LENGTH_BIT、IV_LENGTH_BYTE常量完全一致。第三步如果用了AAD确认其一致性。第四步打印并对比加密端生成的combinedBase64字符串和解密端收到的字符串看是否完全相同。第五步在解密方法开始处打印解码后combined数组的长度确认其大于IV长度。5.3 性能考量与线程安全性能SM4的软件实现性能与AES相当。GCM模式由于需要进行Galois域乘法运算比CBC模式略慢但在现代CPU上差异对于大多数应用来说可以接受。如果遇到性能瓶颈首先应检查是否在循环中频繁创建Cipher实例。Cipher对象的初始化getInstance和init开销较大。优化建议考虑使用对象池如Apache Commons Pool来复用Cipher对象尤其是在高并发场景下。但要注意Cipher对象不是线程安全的从池中取出使用后必须确保在返回前将其重置cipher.init(...)到正确的状态。在我们的Sm4GcmUtil中每次加解密都创建新的Cipher实例代码简单且线程安全适用于中低并发场景。对于极高并发的服务对象池是值得考虑的优化方向。5.4 密钥生命周期管理与轮转密钥不能永生。出于安全最佳实践应定期轮换加密密钥。策略建议密钥版本化在加密数据时可以将密钥的版本号或ID作为AAD的一部分或者与密文一起存储。例如ciphertext_v2表示使用第二版密钥加密。加解密支持多版本Sm4GcmService可以维护一个密钥MapMapString, SecretKeykey是版本号。加密时使用当前最新密钥解密时根据密文携带的版本信息选择对应的旧密钥进行解密。轮转流程生成新密钥generateKey并安全地存储和部署到新版本配置中。更新Sm4GcmService使其加密时使用新密钥但解密时同时支持新旧密钥。后台启动数据重加密任务将历史数据用新密钥重新加密读取-用旧密钥解密-用新密钥加密-写回。此过程需谨慎确保数据一致性和可回滚。所有历史数据重加密完成后可以从配置和代码中移除旧密钥的支持。这是一个较为复杂的运维过程需要根据业务数据的敏感性和规模来制定具体的轮转计划。6. 完整代码整合与测试用例最后我们将所有代码整合并提供一个简单的JUnit测试来验证整个流程的正确性。项目结构src/main/java/com/yourcompany/demo/ ├── config/ │ └── Sm4Properties.java ├── service/ │ └── Sm4GcmService.java ├── util/ │ └── Sm4GcmUtil.java └── YourApplication.javaSm4GcmUtil.java(完整版包含之前所有方法)Sm4Properties.java(如上)Sm4GcmService.java(如上)测试类import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import javax.annotation.Resource; import javax.crypto.SecretKey; import java.util.Base64; import static org.junit.jupiter.api.Assertions.*; SpringBootTest public class Sm4GcmServiceTest { Resource private Sm4GcmService sm4GcmService; Test public void testEncryptDecryptCycle() throws Exception { String originalText “这是一段需要加密的敏感数据比如身份证号110101199001011234”; // 测试字符串加解密 String ciphertext sm4GcmService.encryptText(originalText); System.out.println(“加密后(Base64): ” ciphertext); assertNotNull(ciphertext); assertNotEquals(originalText, ciphertext); String decryptedText sm4GcmService.decryptText(ciphertext); System.out.println(“解密后: ” decryptedText); assertEquals(originalText, decryptedText); } Test public void testWithAAD() throws Exception { // 测试AAD功能 String plaintext “交易金额1000.00元”; String aad “TX20231027-001”; // 模拟交易流水号作为AAD SecretKey key Sm4GcmUtil.generateKey(); String keyStr Sm4GcmUtil.convertKeyToString(key); System.out.println(“测试生成密钥: ” keyStr); // 使用AAD加密 String ciphertext Sm4GcmUtil.encrypt(plaintext, key, aad); // 使用相同的AAD解密应成功 String decrypted1 Sm4GcmUtil.decryptToString(ciphertext, key, aad); assertEquals(plaintext, decrypted1); // 使用不同的AAD解密应抛出异常认证失败 assertThrows(javax.crypto.BadPaddingException.class, () - { Sm4GcmUtil.decryptToString(ciphertext, key, “WRONG_AAD”); }); // 篡改密文模拟传输错误应抛出异常 byte[] combined Base64.getDecoder().decode(ciphertext); combined[20] ^ 0x01; // 随意修改一个字节 String tamperedCiphertext Base64.getEncoder().encodeToString(combined); assertThrows(javax.crypto.BadPaddingException.class, () - { Sm4GcmUtil.decryptToString(tamperedCiphertext, key, aad); }); } Test public void testKeyConsistency() { // 验证从配置加载的密钥与工具类转换的密钥一致 String configuredKeyStr “你的测试密钥Base64字符串”; // 应与application-test.yml中一致 SecretKey keyFromConfig Sm4GcmUtil.convertStringToKey(configuredKeyStr); // 假设Sm4GcmService内部使用的就是通过Sm4Properties加载的同一个密钥 // 这里可以通过反射获取service内部的key进行比较或者用一个已知明文测试 String testText “test”; String cipherByUtil Sm4GcmUtil.encrypt(testText, keyFromConfig, (String)null); String cipherByService sm4GcmService.encryptText(testText); // 由于每次加密IV随机密文必然不同但能用同一把钥匙解密 assertEquals(testText, Sm4GcmUtil.decryptToString(cipherByService, keyFromConfig, (String)null)); } }运行这些测试确保加密、解密、AAD验证、篡改检测等核心功能全部正常工作。这不仅是功能验证也是理解GCM模式工作原理的好方法。至此我们已经完成了在Spring Boot项目中集成SM4-GCM国密算法的全部实战内容。从算法选型、原理理解、工具类编写、Spring集成到生产环境注意事项形成了一个完整的闭环。迁移的过程实际上是一次对数据安全机制的升级和规范化。当你下次再看到AES-CBC的代码时或许可以考虑是时候让它“光荣退役”了。

相关新闻