14-字段级SM4加密与SM2签名

14-字段级SM4加密与SM2签名

EA03字段元数据里有encrypted和signed两个布尔------身份证号加密存库、关键业务数据加签名防篡改。这篇拆两个策略类的实现:BouncyCastle的SM4/CBC/PKCS7、SM3withSM2签名算法、hex密钥装载,以及它们在引擎里的触发点。

文章目录

源码:

browise-metadata/src/main/java/com/browise/ea/core/crypto/Sm4EncryptStrategy.java(82行)

browise-metadata/src/main/java/com/browise/ea/core/crypto/Sm2SignStrategy.java(71行)


一、两个SPI接口

引擎层只认接口不认实现:

java 复制代码
// EncryptStrategy.java(187B)
public interface EncryptStrategy {
    String encrypt(String plaintext) throws Exception;
    String decrypt(String ciphertext) throws Exception;
}

// SignStrategy.java(188B)
public interface SignStrategy {
    String sign(String data) throws Exception;
    boolean verify(String data, String signature) throws Exception;
}

EaConfig.customEncryptStrategy()可注入自定义实现------政务客户要求"必须走加密机"时,实现一对接口注册进去,引擎无感知切换(第32篇的starter条件装配就是干这个的)。


二、Sm4EncryptStrategy:82行实现

2.1 静态块注册Provider

java 复制代码
static {
    if (Security.getProvider(PROVIDER) == null) {
        Security.addProvider(new BouncyCastleProvider());
    }
}

JDK不带SM4------必须BouncyCastle。静态块在类加载时注册一次,if null判断防止重复注册(多模块都加载BC时不会冲突)。

2.2 构造与密钥

java 复制代码
public Sm4EncryptStrategy(String key, String iv, String mode) {
    this.key = key;                        // hex编码的密钥字符串
    this.iv = iv;                          // CBC模式的初始向量(hex)
    this.mode = mode != null ? mode.toUpperCase() : "CBC";
}

配置来源(application.yml):

yaml 复制代码
browise:
  metadata:
    encrypt-key: "0123456789abcdef0123456789abcdef"   # 32个hex字符=16字节=SM4密钥长度
    encrypt-iv:  "fedcba9876543210fedcba9876543210"
    encrypt-mode: "CBC"                                # 或ECB

2.3 encrypt的三步

java 复制代码
public String encrypt(String plaintext) throws Exception {
    Cipher cipher = Cipher.getInstance("SM4/" + mode + "/PKCS7Padding", PROVIDER);
    //                    SM4/CBC/PKCS7Padding------算法/模式/填充三段式
    SecretKeySpec keySpec = new SecretKeySpec(hexToBytes(key), ALGORITHM);
    if ("CBC".equals(mode) && iv != null) {
        IvParameterSpec ivSpec = new IvParameterSpec(hexToBytes(iv));
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);   // CBC带IV
    } else {
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);            // ECB不带IV
    }
    byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
    return Base64.getEncoder().encodeToString(encrypted);    // 密文Base64存储
}

三个技术点:

PKCS7Padding------SM4是块加密(16字节一块),明文不够一块要填充。PKCS7是国密标准推荐填充(JDK自带的是PKCS5,BC的PKCS7对SM4块长兼容)。

CBC vs ECB ------CBC(密码块链接)每块加密前先异或前一块密文+IV,相同明文加密出不同密文(身份证号重复也不暴露);ECB相同明文块加密结果相同(模式图风险)。默认CBC,ECB只在不方便管IV的极简场景用。

Base64输出------密文是二进制,数据库列是VARCHAR------Base64编码成字符串落库。解密先Base64 decode再doFinal。

2.4 hexToBytes

java 复制代码
private static byte[] hexToBytes(String hex) {
    byte[] data = new byte[hex.length() / 2];
    for (int i = 0; i < hex.length(); i += 2) {
        data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
                + Character.digit(hex.charAt(i + 1), 16));
    }
    return data;
}

配置文件里密钥用hex字符串(可打印、可校验长度),运行时转字节数组。16字节密钥=32个hex字符------配置错了长度,SecretKeySpec不报错但加密结果错------这是SM4排障的第一检查点。


三、Sm2SignStrategy:71行实现

3.1 密钥装载

java 复制代码
private static final String SIGN_ALGORITHM = "SM3withSM2";  // 国密标准签名算法

public Sm2SignStrategy(String privateKeyHex, String publicKeyHex) {
    KeyFactory kf = KeyFactory.getInstance("EC", "BC");
    this.privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(hexToBytes(privateKeyHex)));
    this.publicKey  = kf.generatePublic(new X509EncodedKeySpec(hexToBytes(publicKeyHex)));
}

SM3withSM2 不是裸SM2------是"用SM3做摘要、SM2椭圆曲线做签名"的组合算法(对标RSAwithSHA256的组合方式)。密钥格式:私钥PKCS8、公钥X509------标准的非对称密钥编码,gm工具或BC的KeyPairGenerator可生成。

3.2 sign与verify

java 复制代码
public String sign(String data) throws Exception {
    Signature sig = Signature.getInstance(SIGN_ALGORITHM, "BC");
    sig.initSign(privateKey);
    sig.update(data.getBytes(StandardCharsets.UTF_8));
    return Base64.getEncoder().encodeToString(sig.sign());   // 签名值Base64
}

public boolean verify(String data, String signature) throws Exception {
    Signature sig = Signature.getInstance(SIGN_ALGORITHM, "BC");
    sig.initVerify(publicKey);
    sig.update(data.getBytes(StandardCharsets.UTF_8));
    return sig.verify(Base64.getDecoder().decode(signature));
}

对称的典型JCA用法------签名私钥、验签公钥。私钥只在签名方------验证方拿公钥就能验,不触碰私钥。


四、引擎里的触发点

查询侧:postProcess逐字段解密

EaEngine.executeQuery → resultSetToList → postProcess(row, fields)(第06篇讲过):

java 复制代码
private void postProcess(Map<String, Object> row, List<EaFieldMeta> fields) {
    for (EaFieldMeta f : fields) {
        String key = f.getFieldAlias().toLowerCase();
        Object val = row.get(key);
        if (val == null) continue;
        String strVal = val.toString();
        if (f.isEncrypted() && encryptStrategy != null) {
            strVal = encryptStrategy.decrypt(strVal);   // ①先解密
            row.put(key, strVal);
        }
        if (f.isMasked()) {
            row.put(key, mask(strVal, f.getMaskRule())); // ②再脱敏
        }
    }
}

①②顺序不可反 ------密文脱敏没有意义,脱敏后的星号解不开。库里的Base64密文 → 内存里解密成明文 → 输出前脱敏成 3201**1234

写入侧:EaController的save路径

browise-formbuilder/.../EaController.java 的save链路在执行insert/update前,按fields元数据把encrypted字段的明文加密------前端传明文,库里存密文,查询吐明文(或脱敏)。加密对业务代码完全透明。

策略初始化的null语义

java 复制代码
// EaEngine.initEncryptStrategy()
EncryptStrategy custom = config.customEncryptStrategy();
if (custom != null) { this.encryptStrategy = custom; return; }
if (config.getEncryptKey() != null) {
    this.encryptStrategy = new Sm4EncryptStrategy(...);
}
// 不配key → encryptStrategy保持null → postProcess跳过解密

没配密钥=加密功能关闭 。字段标了encrypted=1但引擎没有策略------查出来的还是库里的密文原样(不报错)。这是容错也是坑:上线前要验证配置真生效(用一条加密数据查一遍确认吐的是明文)。


五、加密与签名的分工

SM4加密 SM2签名
解决什么 看不到(保密性) 改不了(完整性)
密钥 对称------加解密同一把 非对称------私签公验
数据库存储 密文(Base64) 明文+MAC列(Base64签名值)
查询输出 解密→(脱敏) 验签校验
典型字段 身份证号/银行卡号 待遇金额/审批结论

为什么待遇金额不加密只签名------金额要参与SQL聚合(SUM/ORDER),加密了没法算;但绝不能被悄悄篡改------签名保证"这行数据的金额从写入起没被人改过"。保密性和完整性是两个独立需求,各用各的算法。


✅ 亮点:两个82/71行的策略类完整拆解------SM4/CBC/PKCS7Padding三段式、hex密钥与Base64密文的编码链、SM3withSM2组合算法、postProcess先解密再脱敏的顺序约束、加密与签名解决保密vs完整性的分工。适合做国密落地的人。扩展方向:第15篇mask脱敏规则、第32篇starter的BC/HSM条件装配。

相关推荐
数据库小学妹37 分钟前
AI Agent记忆怎么存?Redis+向量库三层记忆架构实战
人工智能·架构·向量数据库·ai数据库·数据库趋势·ai agent记忆
许彰午2 小时前
13-describe元数据输出
java·低代码·架构·状态模式
李昊哲小课2 小时前
SpringBoot4 云端咖啡站 阶段二:数据访问与分层架构
spring boot·架构
用户3721574261353 小时前
Java 设置 Word 文档编辑限制并允许指定区域编辑
java
宠友信息3 小时前
社区类源码开发实践中的仿小红书系统技术要点分析
java·spring boot·redis·mysql·uni-app·vue·内容运营
用户3721574261353 小时前
Java 设置 PDF 表单域只读或扁平化
java
luteres4 小时前
Spring学习笔记
java·后端·spring
深念Y4 小时前
登录日志与管理员审计日志存储决策
前端·arm开发·后端·微服务·云原生·架构
南城以南溫暖如初1475 小时前
从零搭建24小时自助健身系统:技术选型与核心模块实战
java·spring boot·redis·mysql·vue·mybatis