首页 / 新闻资讯 / Java程序安全加固密评合规要点,密码应用安全性评估技术整改...
某城商行科技部在迎接密评时遭遇了尴尬:核心交易系统使用Spring Boot + JDK 8构建,虽然通过了等保2.0测评,却在密码应用安全性评估中被判定为“不符合”。测评报告指出:SSL证书使用RSA算法、数据存储采用AES加密、未配置国密SSL协议栈。这意味着系统在“通信安全”“数据存储安全”“身份鉴别”三个关键得分点几乎拿不到分。整改窗口期仅剩4周,团队面临JCE提供者配置、国密双证书部署、代码层算法替换三重技术挑战,且必须保证业务零中断。

GB/T 39786-2021《信息安全技术 密码应用安全性评估准则》中,与Java应用强相关的测评点主要集中在以下三类:
身份鉴别(计分权重0.15):要求使用密码技术对登录用户身份真实性进行保护。对于Java Web系统,这意味着Spring Security或Shiro框架必须对接国密SM2数字证书或SM3摘要算法。
通信安全(计分权重0.20):要求使用符合国密标准的SSL/TLS协议。Java应用需配置国密双证书体系(签名证书+加密证书),并基于国密SSL协议栈建立HTTPS通道。
数据存储安全(计分权重0.20):要求敏感数据(如身份证号、银行卡号)使用国密算法加密存储。需将AES替换为SM4,并将散列算法从SHA-256升级为SM3。
下文将围绕这三个核心评分点,给出可直接落地的代码级改造方案。

Java原生JCE(Java Cryptography Extension)不包含国密算法实现。要让JDK支持SM2/SM3/SM4,必须注册第三方JCE提供者。Bouncy Castle是目前生产环境验证最充分的方案,但需要特别注意版本兼容性和提供者优先级配置。
对于Maven项目,在pom.xml中引入Bouncy Castle依赖。JDK 8和JDK 11/17的依赖版本不同:
<!-- JDK 8 使用 jdk15on 版本 --> org.bouncycastle bcprov-jdk15on 1.70 <!-- JDK 11及以上使用 bcprov-jdk15to18 --> org.bouncycastle bcprov-jdk15to18 1.78 | 注册方式 | 实现方法 | 适用场景 | 风险点 |
|---|---|---|---|
| 静态注册 | 编辑$JAVA_HOME/jre/lib/security/java.security,添加security.provider.n=org.bouncycastle.jce.provider.BouncyCastleProvider | 全JVM级别生效,适合容器化或物理机部署 | 修改JDK安装目录,多应用共享JDK时可能相互影响 |
| 动态注册 | Security.addProvider(new BouncyCastleProvider()) | 应用级别隔离,适合多应用共用JDK环境 | 需确保注册时机早于首次调用国密算法;Spring Boot可在@PostConstruct中执行 |
| 配置文件注册 | 通过java.security.properties系统参数指定外部配置文件 | 云原生/容器场景,不侵入基础镜像 | 需挂载配置文件到容器指定路径 |
生产推荐方案:动态注册 + 静态注册双保险。在应用启动类中显式注册,同时确保java.security文件中Bouncy Castle优先级不低于第3位。
import org.bouncycastle.jce.provider.BouncyCastleProvider;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import javax.annotation.PostConstruct;import java.security.Security;@SpringBootApplicationpublic class Application { @PostConstruct public void init() { // 动态注册JCE提供者 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { Security.addProvider(new BouncyCastleProvider()); } // 可选:将Bouncy Castle提升到第一位(谨慎操作,可能影响RSA等国际算法) // Security.insertProviderAt(new BouncyCastleProvider(), 1); } public static void main(String[] args) { SpringApplication.run(Application.class, args); }}import java.security.Provider;import java.security.Security;public class JCEValidator { public static void main(String[] args) { Provider bc = Security.getProvider("BC"); if (bc != null) { System.out.println("Bouncy Castle Provider 已加载"); // 打印支持的SM系列算法 bc.getServices().stream() .filter(s -> s.getAlgorithm().toUpperCase().contains("SM")) .forEach(s -> System.out.println(" " + s.getAlgorithm())); } else { System.err.println("Bouncy Castle Provider 未找到"); } }}预期输出应包含SM2、SM3、SM4/CBC、SM4/ECB等算法标识。

国密SSL协议采用双证书体系:签名证书用于身份认证,加密证书用于密钥协商。这与国际TLS的单一证书逻辑完全不同。Java应用部署国密HTTPS主要有两条技术路线:
这是金融行业最成熟的方案——在Java应用前部署Nginx,由Nginx完成国密SSL卸载,Java应用只处理HTTP明文流量。
操作步骤:
Nginx配置示例:
server { listen 443 ssl; server_name your-domain.com; # 签名证书配置 ssl_certificate /path/to/sign-cert.pem; ssl_certificate_key /path/to/sign-key.pem; # 加密证书配置(国密特有) ssl_enc_certificate /path/to/enc-cert.pem; ssl_enc_certificate_key /path/to/enc-key.pem; # 指定国密密码套件 ssl_ciphers ECC-SM4-SM3:ECDHE-SM4-SM3; location / { proxy_pass http://localhost:8080; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }}对于无法增加前置代理的场景(如内部微服务调用、数据库连接),需在JVM层面支持国密TLS。BGMProvider是华为开源的国密TLS Java实现,已在毕昇JDK中集成验证。
JDBC连接GaussDB国密TLS示例(适用于华为生态):
import java.sql.Connection;import java.sql.DriverManager;import java.util.Properties;public class GMTLSConnection { public static void main(String[] args) throws Exception { String url = "jdbc:gaussdb://192.168.1.100:5432/mydb"; Properties props = new Properties(); props.setProperty("user", "app_user"); props.setProperty("password", "encrypted_password"); // 启用SSL并指定国密TLS props.setProperty("ssl", "true"); props.setProperty("sslmode", "verify-ca"); // 签名证书配置 props.setProperty("sslcert", "/path/client.crt"); props.setProperty("sslkey", "/path/client.key.pk8"); // 加密证书配置(国密双证书特有) props.setProperty("sslenccert", "/path/client_enc.crt"); props.setProperty("sslenckey", "/path/client_enc.key.pk8"); // 国密密码套件 props.setProperty("sslgmcipher", "ECDHE_SM4_SM3"); // 加载国密JCE提供者 Class.forName("com.huawei.gaussdb.jdbc.Driver"); Connection conn = DriverManager.getConnection(url, props); }}关键约束:使用BGMProvider需要JDK 8u302及以上版本,且国密TLS性能较国际TLS有明显劣化,建议只在合规要求的链路上启用。
密评要求敏感数据必须使用国密算法加密存储。存量系统的改造难点在于算法替换的平滑迁移和密钥管理。
参照国密标准,SM4支持CBC和ECB模式,推荐使用CBC模式配合随机IV。
import org.bouncycastle.jce.provider.BouncyCastleProvider;import javax.crypto.Cipher;import javax.crypto.KeyGenerator;import javax.crypto.SecretKey;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import java.security.SecureRandom;import java.security.Security;import java.util.Base64;public class SM4Util { static { Security.addProvider(new BouncyCastleProvider()); } private static final String ALGORITHM = "SM4/CBC/PKCS5Padding"; private static final int KEY_SIZE = 128; private static final int IV_SIZE = 16; /** * 生成SM4密钥(Base64编码) */ public static String generateKey() throws Exception { KeyGenerator kg = KeyGenerator.getInstance("SM4", "BC"); kg.init(KEY_SIZE, new SecureRandom()); SecretKey key = kg.generateKey(); return Base64.getEncoder().encodeToString(key.getEncoded()); } /** * 加密 */ public static String encrypt(String plaintext, String base64Key) throws Exception { byte[] keyBytes = Base64.getDecoder().decode(base64Key); SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "SM4"); // 生成随机IV byte[] iv = new byte[IV_SIZE]; new SecureRandom().nextBytes(iv); IvParameterSpec ivSpec = new IvParameterSpec(iv); Cipher cipher = Cipher.getInstance(ALGORITHM, "BC"); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); byte[] encrypted = cipher.doFinal(plaintext.getBytes("UTF-8")); // 将IV拼接在密文前,便于解密时提取 byte[] result = new byte[iv.length + encrypted.length]; System.arraycopy(iv, 0, result, 0, iv.length); System.arraycopy(encrypted, 0, result, iv.length, encrypted.length); return Base64.getEncoder().encodeToString(result); } /** * 解密 */ public static String decrypt(String ciphertext, String base64Key) throws Exception { byte[] keyBytes = Base64.getDecoder().decode(base64Key); SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "SM4"); byte[] fullData = Base64.getDecoder().decode(ciphertext); // 提取IV(前16字节) byte[] iv = new byte[IV_SIZE]; byte[] encrypted = new byte[fullData.length - IV_SIZE]; System.arraycopy(fullData, 0, iv, 0, IV_SIZE); System.arraycopy(fullData, IV_SIZE, encrypted, 0, encrypted.length); IvParameterSpec ivSpec = new IvParameterSpec(iv); Cipher cipher = Cipher.getInstance(ALGORITHM, "BC"); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); byte[] decrypted = cipher.doFinal(encrypted); return new String(decrypted, "UTF-8"); }}对于使用JPA/Hibernate的系统,可以通过@Converter注解实现字段级别的自动加解密,避免业务代码改动:
import javax.persistence.AttributeConverter;import javax.persistence.Converter;@Converterpublic class SM4FieldConverter implements AttributeConverter { // 密钥应从KMS或配置文件密文获取,不可硬编码 private static final String ENCRYPT_KEY = System.getProperty("sm4.storage.key"); @Override public String convertToDatabaseColumn(String attribute) { if (attribute == null || attribute.isEmpty()) { return attribute; } try { return SM4Util.encrypt(attribute, ENCRYPT_KEY); } catch (Exception e) { throw new RuntimeException("SM4加密失败", e); } } @Override public String convertToEntityAttribute(String dbData) { if (dbData == null || dbData.isEmpty()) { return dbData; } try { return SM4Util.decrypt(dbData, ENCRYPT_KEY); } catch (Exception e) { throw new RuntimeException("SM4解密失败", e); } }}// 实体类使用示例@Entitypublic class Customer { @Id private Long id; @Convert(converter = SM4FieldConverter.class) private String idNumber; // 身份证号自动加解密存储} 密评测评师会重点检查密钥的生成、存储、分发、销毁全生命周期管理。硬编码密钥直接判定为严重不符合。生产环境推荐方案:
@Componentpublic class SM4KeyManager { @Value("${sm4.current.key.id}") private String currentKeyId; // 从配置中心或KMS获取当前密钥 public String getCurrentKey() { return KeyManagementService.getKey(currentKeyId); } // 解密时支持历史密钥 public String decryptWithKeyRollback(String ciphertext, String keyIdHint) { String key = KeyManagementService.getKey(keyIdHint); return SM4Util.decrypt(ciphertext, key); }}以下是某股份制银行信贷审批系统密评整改后提交的测评材料核心数据:
| 整改项 | 整改前 | 整改后 | 密评得分 |
|---|---|---|---|
| JCE提供者配置 | 使用JDK默认SunJCE | Bouncy Castle BC优先,静态+动态双注册 | 5/5 |
| HTTPS国密双证书 | RSA证书,未配置国密SSL | Nginx前置代理,SM2双证书+Tongsuo | 5/5 |
| 用户密码存储 | BCrypt(SHA-256派生) | SM3摘要+动态盐 | 4.5/5 |
| 身份证号加密 | AES-128-CBC | SM4-CBC,密钥KMS托管 | 5/5 |
| 敏感日志脱敏 | 正则表达式替换 | SM3哈希匿名化 | 4/5 |
| 整体密评得分 | 47.5分(不符合) | 86.3分(符合) | - |
整改周期:5人团队,耗时23个工作日,其中JCE提供者配置及验证4天、国密SSL部署7天(含前置Nginx改造)、数据加密改造8天、测试及文档编写4天。
关键经验:
sun.security.rsa提供者存在SPI冲突,需在java.security中将BC优先级调整为第2位绕过密评的关键不是“临时打补丁”,而是建立体系化的密码应用能力。技术团队应将JCE国密提供者配置标准化为Maven archetype模板,将国密SSL部署沉淀为基础设施即代码脚本,将SM4加解密封装为统一的安全组件。以上整改方案已在上文完整呈现,直接从代码和配置入手即可启动改造。