1. 项目概述为什么要在移动端与后端同时实现SM4最近在做一个涉及用户隐私数据同步的项目数据在Android App本地生成需要通过网络传输到SpringBoot后端服务进行持久化。数据安全是红线绝对不能出问题。最开始考虑用AES但项目方对合规性有明确要求必须支持国密算法。SM4作为国家密码管理局认定的商用密码算法其安全性和合规性毋庸置疑自然就成了首选。这个需求拆开来看其实包含了两个核心场景一是Android端本地数据的加密存储二是数据在HTTPS通道之上再做一层应用层的端到端加密传输。光有后端的支持是不够的必须两端对齐使用相同的算法、相同的模式、相同的填充方式才能保证加密解密流程畅通无阻。网上关于SM4的资料要么是纯Java后端的要么是C语言的专门讲Android和SpringBoot如何协同工作的完整案例并不多见很多细节需要自己趟坑。所以我决定把这次从零搭建、两端对齐的完整实战过程记录下来。目标很明确在Android Studio和IDEA或你喜欢的任何SpringBoot开发环境中分别实现SM4的ECB和CBC模式加解密并提供可复用的工具类。你会看到如何引入国密算法库、如何处理Android与Java标准库的差异、如何设计一个前后端通用的加解密协议。文末会附上经过实际项目验证的完整代码你可以直接拿去集成到自己的项目里。2. 核心概念与工具选型SM4、算法模式与依赖库在动手写代码之前我们必须把几个关键概念和要用的工具搞清楚这是保证两端不出错的基础。2.1 SM4算法简介与模式选择SM4是一种分组密码算法分组长度和密钥长度均为128位16字节。这意味着它一次处理16个字节的明文输出16个字节的密文。如果数据不是16字节的整数倍就需要进行填充Padding。最常用的两种模式是ECB和CBCECB模式最简单将明文分成若干独立的分组每个分组用同一个密钥加密。缺点是相同的明文分组会加密成相同的密文分组不能很好地隐藏数据模式安全性相对较低一般用于加密密钥等短数据。CBC模式引入了一个初始化向量IV每个明文分组在加密前先与前一个密文分组进行异或操作第一个分组与IV异或。这样即使明文相同加密后的密文也会不同安全性更高是加密大量数据的推荐模式。在我们的实战中为了覆盖不同场景我会同时实现ECB和CBC两种模式。本地存储一个固定值如设备标识可以用ECB而加密传输用户的一段可变文本则强烈建议使用CBC。2.2 依赖库的选择与考量这是第一个容易踩坑的地方。Java后端和Android端虽然都叫Java但它们的运行环境和可用库有差异。SpringBoot后端依赖 对于Java 8及以上的SpringBoot项目最成熟的选择是Bouncy Castle这个强大的密码学提供者。它提供了对SM2、SM3、SM4等国密算法的完整支持。我们通过Maven引入其轻量级的“轻量级安全API”版本即可。dependency groupIdorg.bouncycastle/groupId artifactIdbcprov-jdk15to18/artifactId version1.76/version !-- 请使用最新稳定版 -- /dependency选择这个版本是因为它兼容JDK 15到18覆盖范围广。引入后我们需要在代码中动态将Bouncy Castle注册为安全提供者。Android端依赖 Android环境比较特殊。虽然也可以使用Bouncy Castle但更推荐使用专门为Android优化过的Bouncy Castle版本或者使用Google自己的Conscrypt但Conscrypt对SM4支持可能不完整。为了最大程度保证与后端的一致性我选择使用一个专门打包的bcprov-android。// 在app模块的build.gradle的dependencies中添加 implementation org.bouncycastle:bcprov-jdk15to18:1.76 // 或尝试专用的android版本 // 注意在较新Android Gradle插件版本下可能需要额外处理注意在Android PAPI 28及以上版本Google限制了非公开API的调用并且默认的加密套件有所变化。直接使用标准Bouncy Castle可能会遇到NoSuchProviderException或NoSuchAlgorithmException。一个更稳妥的方案是使用Spongy Castle这是Bouncy Castle在Android上的一个重打包版本已解决命名冲突但由于其维护状态我们也可以选择将标准Bouncy Castle的jar包直接放入libs目录并通过MultiDex等方式解决。本文为求通用将演示使用标准依赖并说明可能遇到的问题及解决方案。2.3 密钥与IV的管理密钥和IV是加密的“钥匙”和“盐”管理不当加密形同虚设。密钥必须是16字节。绝对不要使用简单的字符串如“1234567812345678”直接getBytes()因为不同编码得到的字节长度可能不同。应该使用安全的随机数生成器生成或者对密码字符串进行KDF密钥派生处理。在示例中为简单起见我们会使用一个固定字符串并确保其UTF-8编码恰好为16字节。生产环境务必使用KeyStoreAndroid或专业的密钥管理系统来生成和存储密钥。IV仅CBC模式需要也是16字节。关键原则IV不需要保密但必须不可预测通常随机生成且对于同一个密钥不应重复使用相同的IV。在通信中IV可以随密文一起传输。3. SpringBoot后端SM4加解密实现我们先从后端开始因为Java标准环境下的问题相对较少。我们将创建一个Sm4Util工具类。3.1 项目初始化与依赖注入创建一个新的SpringBoot项目在pom.xml中加入前述的Bouncy Castle依赖。然后我们需要在应用启动时或者工具类静态初始化块中将BC注册为安全提供者。import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.IvParameterSpec; import java.security.Security; import java.util.Base64; public class Sm4Util { private static final String ALGORITHM_NAME SM4; private static final String TRANSFORMATION_ECB SM4/ECB/PKCS5Padding; private static final String TRANSFORMATION_CBC SM4/CBC/PKCS5Padding; private static final String CHARSET UTF-8; static { // 静态代码块确保类加载时注册一次即可 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) null) { Security.addProvider(new BouncyCastleProvider()); } } // ... 后续方法 }TRANSFORMATION字符串定义了算法/模式/填充。PKCS5Padding是PKCS#7 Padding在8字节块下的特例对于16字节的SM4我们实际指的是PKCS#7填充但JCE中通常使用PKCS5Padding这个名字。这是标准做法。3.2 ECB模式加解密方法实现ECB模式不需要IV实现起来最简单。/** * SM4 ECB模式加密 * param data 明文文本 * param key 密钥必须为16字节长度的字符串 * return Base64编码的密文 */ public static String encryptEcb(String data, String key) throws Exception { // 1. 参数校验 if (data null || key null) { throw new IllegalArgumentException(Data and key must not be null); } byte[] keyBytes key.getBytes(CHARSET); if (keyBytes.length ! 16) { throw new IllegalArgumentException(Key length must be 16 bytes (128 bits) in UTF-8 encoding); } // 2. 创建密钥规范 SecretKeySpec secretKeySpec new SecretKeySpec(keyBytes, ALGORITHM_NAME); // 3. 获取并初始化Cipher实例 // 注意这里指定Provider为BC即Bouncy Castle Cipher cipher Cipher.getInstance(TRANSFORMATION_ECB, BC); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); // 4. 执行加密 byte[] encryptedBytes cipher.doFinal(data.getBytes(CHARSET)); // 5. 返回Base64编码结果便于传输和存储 return Base64.getEncoder().encodeToString(encryptedBytes); } /** * SM4 ECB模式解密 * param encryptedData Base64编码的密文 * param key 密钥必须为16字节长度的字符串 * return 解密后的明文 */ public static String decryptEcb(String encryptedData, String key) throws Exception { if (encryptedData null || key null) { throw new IllegalArgumentException(Encrypted data and key must not be null); } byte[] keyBytes key.getBytes(CHARSET); if (keyBytes.length ! 16) { throw new IllegalArgumentException(Key length must be 16 bytes (128 bits) in UTF-8 encoding); } SecretKeySpec secretKeySpec new SecretKeySpec(keyBytes, ALGORITHM_NAME); Cipher cipher Cipher.getInstance(TRANSFORMATION_ECB, BC); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); // 先将Base64字符串解码为字节数组再解密 byte[] decryptedBytes cipher.doFinal(Base64.getDecoder().decode(encryptedData)); return new String(decryptedBytes, CHARSET); }3.3 CBC模式加解密方法实现CBC模式需要IV。我们设计为加密时随机生成IV并将IV拼接到密文前面一起返回解密时先从密文中分离出IV。/** * SM4 CBC模式加密 * param data 明文文本 * param key 密钥必须为16字节长度的字符串 * return Base64编码的密文格式为Base64(IV 实际密文) */ public static String encryptCbc(String data, String key) throws Exception { if (data null || key null) { throw new IllegalArgumentException(Data and key must not be null); } byte[] keyBytes key.getBytes(CHARSET); if (keyBytes.length ! 16) { throw new IllegalArgumentException(Key length must be 16 bytes (128 bits) in UTF-8 encoding); } SecretKeySpec secretKeySpec new SecretKeySpec(keyBytes, ALGORITHM_NAME); Cipher cipher Cipher.getInstance(TRANSFORMATION_CBC, BC); // 生成一个随机的16字节IV byte[] ivBytes new byte[16]; SecureRandom secureRandom new SecureRandom(); secureRandom.nextBytes(ivBytes); IvParameterSpec ivParameterSpec new IvParameterSpec(ivBytes); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec); byte[] encryptedBytes cipher.doFinal(data.getBytes(CHARSET)); // 将IV和密文拼接在一起 byte[] combined new byte[ivBytes.length encryptedBytes.length]; System.arraycopy(ivBytes, 0, combined, 0, ivBytes.length); System.arraycopy(encryptedBytes, 0, combined, ivBytes.length, encryptedBytes.length); return Base64.getEncoder().encodeToString(combined); } /** * SM4 CBC模式解密 * param encryptedDataWithIv Base64编码的密文包含前置的IV * param key 密钥必须为16字节长度的字符串 * return 解密后的明文 */ public static String decryptCbc(String encryptedDataWithIv, String key) throws Exception { if (encryptedDataWithIv null || key null) { throw new IllegalArgumentException(Encrypted data and key must not be null); } byte[] keyBytes key.getBytes(CHARSET); if (keyBytes.length ! 16) { throw new IllegalArgumentException(Key length must be 16 bytes (128 bits) in UTF-8 encoding); } byte[] combined Base64.getDecoder().decode(encryptedDataWithIv); if (combined.length 16) { throw new IllegalArgumentException(Invalid encrypted data: too short to contain IV); } // 分离IV和实际密文 byte[] ivBytes new byte[16]; byte[] encryptedBytes new byte[combined.length - 16]; System.arraycopy(combined, 0, ivBytes, 0, 16); System.arraycopy(combined, 16, encryptedBytes, 0, encryptedBytes.length); SecretKeySpec secretKeySpec new SecretKeySpec(keyBytes, ALGORITHM_NAME); IvParameterSpec ivParameterSpec new IvParameterSpec(ivBytes); Cipher cipher Cipher.getInstance(TRANSFORMATION_CBC, BC); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); byte[] decryptedBytes cipher.doFinal(encryptedBytes); return new String(decryptedBytes, CHARSET); }实操心得这里将IV和密文拼接后一起做Base64编码是一种常见的、便于传输的处理方式。你也可以选择将IV单独用Base64编码后和密文的Base64编码用特定分隔符如:拼接成字符串。前者更紧凑后者更清晰。选择哪一种需要前后端约定一致。3.4 编写测试Controller验证功能创建一个简单的REST接口来测试我们的工具类。RestController RequestMapping(/api/crypto) public class CryptoController { private static final String SECRET_KEY 1234567890abcdef; // 16字节的测试密钥生产环境勿用 PostMapping(/sm4/ecb/encrypt) public String encryptEcb(RequestParam String plaintext) throws Exception { return Sm4Util.encryptEcb(plaintext, SECRET_KEY); } PostMapping(/sm4/ecb/decrypt) public String decryptEcb(RequestParam String ciphertext) throws Exception { return Sm4Util.decryptEcb(ciphertext, SECRET_KEY); } PostMapping(/sm4/cbc/encrypt) public String encryptCbc(RequestParam String plaintext) throws Exception { return Sm4Util.encryptCbc(plaintext, SECRET_KEY); } PostMapping(/sm4/cbc/decrypt) public String decryptCbc(RequestParam String ciphertext) throws Exception { return Sm4Util.decryptCbc(ciphertext, SECRET_KEY); } }启动SpringBoot应用用Postman或curl测试这些接口确保后端加解密功能正常。这是后续与Android联调的基础。4. Android端SM4加解密实现Android端的实现逻辑与后端几乎一致但环境配置是最大的挑战。我们创建一个Sm4AndroidUtil工具类。4.1 环境配置与依赖处理首先在app/build.gradle的dependencies中添加依赖。如前所述我们尝试使用标准Bouncy Castle。dependencies { implementation org.bouncycastle:bcprov-jdk15to18:1.76 // ... 其他依赖 }然后在工具类中注册Provider。这里有一个大坑在Android中Security.addProvider可能会因为类加载器或权限问题失败。更可靠的做法是在使用Cipher时直接指定Provider实例。import android.util.Base64; import java.security.Security; import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.IvParameterSpec; import java.security.SecureRandom; public class Sm4AndroidUtil { private static final String ALGORITHM_NAME SM4; private static final String TRANSFORMATION_ECB SM4/ECB/PKCS5Padding; private static final String TRANSFORMATION_CBC SM4/CBC/PKCS5Padding; private static final String CHARSET UTF-8; // 直接持有Provider实例 private static final BouncyCastleProvider PROVIDER new BouncyCastleProvider(); // ... 后续方法 }4.2 Android端ECB模式实现在加密解密方法中我们通过Cipher.getInstance(transformation, provider)的方式直接传入Provider实例避免依赖全局注册。public static String encryptEcb(String data, String key) throws Exception { if (data null || key null) { throw new IllegalArgumentException(Data and key must not be null); } byte[] keyBytes key.getBytes(CHARSET); if (keyBytes.length ! 16) { throw new IllegalArgumentException(Key length must be 16 bytes (128 bits) in UTF-8 encoding); } SecretKeySpec secretKeySpec new SecretKeySpec(keyBytes, ALGORITHM_NAME); // 关键使用PROVIDER实例而非字符串“BC” Cipher cipher Cipher.getInstance(TRANSFORMATION_ECB, PROVIDER); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); byte[] encryptedBytes cipher.doFinal(data.getBytes(CHARSET)); // 使用Android SDK自带的Base64 return Base64.encodeToString(encryptedBytes, Base64.NO_WRAP); } public static String decryptEcb(String encryptedData, String key) throws Exception { if (encryptedData null || key null) { throw new IllegalArgumentException(Encrypted data and key must not be null); } byte[] keyBytes key.getBytes(CHARSET); if (keyBytes.length ! 16) { throw new IllegalArgumentException(Key length must be 16 bytes (128 bits) in UTF-8 encoding); } SecretKeySpec secretKeySpec new SecretKeySpec(keyBytes, ALGORITHM_NAME); Cipher cipher Cipher.getInstance(TRANSFORMATION_ECB, PROVIDER); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); byte[] decryptedBytes cipher.doFinal(Base64.decode(encryptedData, Base64.NO_WRAP)); return new String(decryptedBytes, CHARSET); }4.3 Android端CBC模式实现逻辑与后端保持一致注意使用Android的Base64和SecureRandom。public static String encryptCbc(String data, String key) throws Exception { if (data null || key null) { throw new IllegalArgumentException(Data and key must not be null); } byte[] keyBytes key.getBytes(CHARSET); if (keyBytes.length ! 16) { throw new IllegalArgumentException(Key length must be 16 bytes (128 bits) in UTF-8 encoding); } SecretKeySpec secretKeySpec new SecretKeySpec(keyBytes, ALGORITHM_NAME); Cipher cipher Cipher.getInstance(TRANSFORMATION_CBC, PROVIDER); byte[] ivBytes new byte[16]; SecureRandom secureRandom new SecureRandom(); secureRandom.nextBytes(ivBytes); IvParameterSpec ivParameterSpec new IvParameterSpec(ivBytes); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec); byte[] encryptedBytes cipher.doFinal(data.getBytes(CHARSET)); byte[] combined new byte[ivBytes.length encryptedBytes.length]; System.arraycopy(ivBytes, 0, combined, 0, ivBytes.length); System.arraycopy(encryptedBytes, 0, combined, ivBytes.length, encryptedBytes.length); return Base64.encodeToString(combined, Base64.NO_WRAP); } public static String decryptCbc(String encryptedDataWithIv, String key) throws Exception { if (encryptedDataWithIv null || key null) { throw new IllegalArgumentException(Encrypted data and key must not be null); } byte[] keyBytes key.getBytes(CHARSET); if (keyBytes.length ! 16) { throw new IllegalArgumentException(Key length must be 16 bytes (128 bits) in UTF-8 encoding); } byte[] combined Base64.decode(encryptedDataWithIv, Base64.NO_WRAP); if (combined.length 16) { throw new IllegalArgumentException(Invalid encrypted data: too short to contain IV); } byte[] ivBytes new byte[16]; byte[] encryptedBytes new byte[combined.length - 16]; System.arraycopy(combined, 0, ivBytes, 0, 16); System.arraycopy(combined, 16, encryptedBytes, 0, encryptedBytes.length); SecretKeySpec secretKeySpec new SecretKeySpec(keyBytes, ALGORITHM_NAME); IvParameterSpec ivParameterSpec new IvParameterSpec(ivBytes); Cipher cipher Cipher.getInstance(TRANSFORMATION_CBC, PROVIDER); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); byte[] decryptedBytes cipher.doFinal(encryptedBytes); return new String(decryptedBytes, CHARSET); }4.4 在Activity中测试及网络通信示例在Android的Activity中我们可以编写一个简单的测试方法。切记加解密是耗时操作必须在子线程中进行private void testSm4() { new Thread(() - { try { String key 1234567890abcdef; String originalText Hello, 国密SM4!; // 测试ECB String ecbEncrypted Sm4AndroidUtil.encryptEcb(originalText, key); Log.d(SM4Test, ECB Encrypted: ecbEncrypted); String ecbDecrypted Sm4AndroidUtil.decryptEcb(ecbEncrypted, key); Log.d(SM4Test, ECB Decrypted: ecbDecrypted); // 测试CBC String cbcEncrypted Sm4AndroidUtil.encryptCbc(originalText, key); Log.d(SM4Test, CBC Encrypted: cbcEncrypted); String cbcDecrypted Sm4AndroidUtil.decryptCbc(cbcEncrypted, key); Log.d(SM4Test, CBC Decrypted: cbcDecrypted); // 验证是否相等 boolean ecbSuccess originalText.equals(ecbDecrypted); boolean cbcSuccess originalText.equals(cbcDecrypted); runOnUiThread(() - { Toast.makeText(MainActivity.this, ECB: ecbSuccess , CBC: cbcSuccess, Toast.LENGTH_LONG).show(); }); } catch (Exception e) { Log.e(SM4Test, Error, e); runOnUiThread(() - Toast.makeText(MainActivity.this, 加解密失败: e.getMessage(), Toast.LENGTH_LONG).show()); } }).start(); }接下来模拟一个网络请求场景Android端加密数据后发送给SpringBoot后端解密。private void sendEncryptedData() { String key 1234567890abcdef; // 前后端共享的密钥实际应从安全渠道分发 String sensitiveData 用户身份证号110101199001011234; new Thread(() - { try { // 使用CBC模式加密更安全 String encryptedPayload Sm4AndroidUtil.encryptCbc(sensitiveData, key); Log.d(Network, Encrypted Payload: encryptedPayload); // 使用OkHttp发送请求 OkHttpClient client new OkHttpClient(); RequestBody body new FormBody.Builder() .add(ciphertext, encryptedPayload) // 参数名需与后端Controller一致 .build(); Request request new Request.Builder() .url(http://你的服务器IP:端口/api/crypto/sm4/cbc/decrypt) .post(body) .build(); Response response client.newCall(request).execute(); if (response.isSuccessful()) { String decryptedResult response.body().string(); Log.d(Network, Server decrypted: decryptedResult); // 处理解密后的数据... } else { Log.e(Network, Request failed: response.code()); } } catch (Exception e) { Log.e(Network, Error sending data, e); } }).start(); }5. 联调测试、常见问题与深度优化当两端代码都写好后真正的挑战才开始联调。这里会遇到一堆“明明本地是好的”的问题。5.1 联调测试步骤与要点独立测试确保SpringBoot后端和Android App各自的加解密功能独立工作正常。用相同的密钥和明文在两端分别进行ECB和CBC的加密解密看结果是否一致。Base64编码一致性这是第一个高频错误点。Java标准库的Base64.getEncoder()和Android的Base64.encodeToString()默认模式可能不同如换行符。务必使用Base64.NO_WRAP标志Android和Base64.getEncoder().withoutPadding()Java或直接使用无换行符的模式进行比较。在我们的代码中Android端已使用Base64.NO_WRAP后端使用标准编码通常兼容。如果遇到问题可以统一输出十六进制字符串进行比对。字符编码一致性确保两端在将字符串转换为字节数组getBytes()和反向转换new String()时使用的字符集完全相同。我们全程使用UTF-8。端到端测试Android端用CBC加密一段数据将得到的Base64字符串通过API发送给后端后端尝试解密。记录下每一步的中间结果如加密后的字节数组转HexIV值等便于排查。5.2 常见问题排查表问题现象可能原因排查步骤与解决方案NoSuchProviderException: BC1. Bouncy Castle JAR未正确引入或冲突。2. Android中全局注册Provider失败。1. 检查依赖是否成功下载。在Android中尝试清理构建Clean Project并重建Rebuild Project。2.改用本文推荐的方式在获取Cipher实例时直接传入PROVIDER对象而非字符串“BC”。InvalidKeyException密钥长度或格式错误。1. 确认密钥字符串经UTF-8编码后恰好为16字节。打印key.getBytes(“UTF-8”).length验证。2. 确认密钥内容合法不含特殊不可见字符。BadPaddingException解密时填充错误。1.最常见前后端使用的算法/模式/填充字符串不匹配。仔细核对TRANSFORMATION常量是否完全一致包括大小写。2. 密钥错误。3. 密文在传输或存储过程中被篡改或截断。4. CBC模式下IV提取错误。解密后得到乱码字符集不一致或解密实际已失败。1. 先确认解密过程未抛出异常。如果没异常但乱码可能是密钥错误导致解密出了无意义的字节再被用UTF-8解释成乱码。可以尝试将解密后的字节数组打印为Hex与原始明文的Hex对比。2. 确认两端字符集统一为UTF-8。Android端在Release包或高版本API上崩溃Proguard混淆或Android系统加密策略限制。1. 在Proguard规则中保留Bouncy Castle相关类-keep class org.bouncycastle.** { *; }。2. 对于API 28如果遇到问题考虑将加密操作封装在NativeC/C代码中或使用Android Keystore System生成和管理密钥。CBC模式加密每次结果都不同这是正常的但后端解密失败IV处理逻辑不一致。检查加密端是否将IV拼接到密文前解密端是否正确地分离了IV。确保拼接和分离的偏移量计算正确都是16字节。5.3 生产环境安全优化建议示例代码为了演示使用了硬编码的密钥这在生产环境中是绝对禁止的。密钥管理Android端使用AndroidKeyStore系统来生成和存储非对称密钥对用于加密一个临时生成的对称密钥如SM4密钥再将这个对称密钥加密后存储在SharedPreferences或文件中。或者在首次启动时从安全服务器动态获取密钥。SpringBoot后端将密钥存储在环境变量、配置中心如Apollo、Nacos或专业的硬件安全模块HSM中绝对不要写在代码或配置文件中提交到代码库。算法模式选择对于需要加密的数据量较大或模式化明显的场景永远优先使用CBC模式并确保每次加密使用随机IV。考虑使用更安全的认证加密模式如GCMGalois/Counter Mode但SM4的GCM模式实现可能需要寻找特定的密码库支持。传输安全应用层加密SM4不能替代传输层加密HTTPS/TLS。必须同时使用。HTTPS保证通道安全防止中间人攻击SM4保证数据在服务器存储和客户端本地存储时的安全即使数据库泄露或设备被破解数据仍是密文。性能考量SM4的软件实现速度很快但对于移动端频繁加密大量数据如文件仍需注意性能损耗和电量消耗。建议在子线程进行并考虑对大数据进行分块处理。在服务端如果加解密成为性能瓶颈可以考虑使用支持国密算法的硬件加速卡。6. 完整代码整合与项目结构参考最后提供一个清晰的项目结构方便你整合代码。SpringBoot后端项目结构src/main/java/com/yourcompany/demo/ ├── config │ └── (可选) 安全配置类 ├── controller │ └── CryptoController.java (测试接口) ├── service │ └── (可选) 业务层加解密服务 ├── util │ └── Sm4Util.java (核心工具类) └── DemoApplication.javaAndroid端项目结构app/src/main/java/com/yourcompany/androidapp/ ├── MainActivity.java (包含测试代码) ├── network │ └── ApiService.java (网络请求封装) └── util └── Sm4AndroidUtil.java (核心工具类)Sm4Util.java和Sm4AndroidUtil.java的完整代码已在上文各节中完整给出直接复制即可使用。一个关键的整合测试在SpringBoot的Sm4Util中添加一个静态方法用于验证与Android端的兼容性。// 在SpringBoot的Sm4Util类中添加 public static void main(String[] args) throws Exception { String key 1234567890abcdef; String text 兼容性测试数据123ABC; // 模拟Android端CBC加密 String androidEncrypted ; // 这里手动填入从Android Logcat中打印出的加密结果 // 例如假设Android加密结果为”iv密文“的Base64 // androidEncrypted xIvW8eNoS123abc...; if (!androidEncrypted.isEmpty()) { String serverDecrypted decryptCbc(androidEncrypted, key); System.out.println(后端解密Android数据结果: serverDecrypted); System.out.println(是否一致: text.equals(serverDecrypted)); } // 反向测试后端加密让Android解密 String serverEncrypted encryptCbc(text, key); System.out.println(后端加密结果可发给Android: serverEncrypted); }通过这个main方法你可以快速进行双向验证确保两端算法、模式、填充、编码方式完全对齐。整个流程走下来从环境搭建、代码编写、坑点排查到安全优化一套完整的Android与SpringBoot国密SM4加密实战方案就清晰了。核心在于细节的一致性和密钥的安全管理。代码本身不复杂但每一个环节的疏忽都可能导致联调失败。希望这篇超详细的实战指南能帮你顺利在项目中落地国密加密既满足合规要求又筑牢数据安全防线。