2. 字段级加密的核心原理:算法与工作模式

好,咱们直接切入正题。

字段级加密,说白了就是在数据库里挑出敏感字段,单独给它加密。比如身份证号、手机号、银行卡号——这些不能明文存。那加密到底怎么加?用啥算法?用什么模式?我踩过的坑不少,咱们一个一个说。

2.1 对称加密:AES 与国密 SM4

字段级加密基本都是对称加密。为什么?你想想看,数据要频繁读写,速度必须快。非对称加密慢得让人抓狂,不适合这种场景。

目前主流的对称加密算法就两个:AES 和 国密 SM4。

特性 AES SM4
分组长度 128 位 128 位
密钥长度 128/192/256 位 128 位
结构 SP 网络 Feistel 结构
轮数 10/12/14 轮 32 轮
典型应用 国际项目、通用场景 国产化合规、政务系统

AES 是国际标准,大家都熟。128位密钥足够安全,256位更安心。我个人习惯是:金融交易类用256位,普通业务字段128位也够。

SM4 是国家密码管理局发布的商用密码算法。它的结构跟 AES 不一样,用的是 Feistel 网络。安全强度对标 AES-128。我前几年做了个政务云项目,客户硬性要求用国密。当时 SM4 的 SDK 还没那么成熟,遇到不少兼容性问题。说白了,选 SM4 更多是合规驱动。

注意: 不要自己做算法实现。用成熟的密码库。AES 用 OpenSSL、Bouncy Castle。SM4 用 GmSSL、BabaSSL。自己实现等于给自己挖坑。

2.2 工作模式:ECB、CBC、GCM、CTR

算法定好了,还得选工作模式。这个很关键。模式不对,加密结果可能不安全,甚至限制业务。

2.2.1 ECB 模式:别碰

ECB 是最简单模式。每个分组独立加密,密钥相同。你猜会怎样?相同的明文块会生成相同的密文块。

我遇到过团队用 ECB 加密用户性别字段。结果密文只有两种值——猜都猜出来哪个是男哪个是女。这还加密干嘛?

结论: 字段级加密中,绝不要用 ECB 模式。

2.2.2 CBC 模式:传统但不完美

CBC 引入了初始化向量 IV。每个密文块会参与下一个块的加密。解决了 ECB 的重复问题。

但 CBC 有个麻烦:加密时必须按块处理。字段长度一般不是 128 位整数倍,需要 PKCS#7 填充。这就带来一个问题——密文长度会变。

// 看看长度变化
原始字段: "13800138000"  (11 字节)
AES/CBC/PKCS7Padding 加密后: 16 字节

你想想看,数据库字段长度要预留。我见过有人没考虑这个,上线后字段截断,数据直接废了。

2.2.3 GCM 模式:我的首选

GCM 是 伽罗瓦计数器模式。它把 CTR 模式和 GMAC 认证组合在一起。一句话总结:既加密又做完整性校验。

  • 不需要填充,密文长度等于明文长度。
  • 提供认证标签,防止篡改。
  • 支持并行计算,性能好。

我现在的项目,几乎都用 GCM。为什么?

首先,字段长度容易控制。存数据库时,VARCHAR 长度就是明文长度加上标签长度(16字节)。很清晰。

其次,安全性更高。CBC 只保证机密性。如果有人篡改密文,CBC 解密后是乱码,但你检测不到。GCM 能直接发现。

我的经验: GCM 里的 IV 不能重复。用 12 字节的随机 IV。每个字段每次加密都用新 IV。IV 重复会直接暴露密钥流。

2.2.4 CTR 模式:轻量选择

CTR 模式把块密码变成流密码。它也是将计数器加密后与明文异或。不支持认证,但效率高。

适合什么场景?你对性能要求极高,并且有独立的 MAC 校验机制。一般我建议业务方直接上 GCM,别在 CTR 上折腾。

2.3 算法与模式的选择矩阵

业务场景 推荐算法 推荐模式 核心考虑
国际化、通用业务 AES-256 GCM 安全、兼容性好
国产化、政务、金融 SM4 GCM 合规要求
高性能、短字段 AES-128 GCM 速度和安全性平衡
遗留系统、只加密 AES-256 CBC 兼容历史实现

2.4 核心流程:字段级加密的骨架

加密流程其实就几步。但每一步都有细节。我画张图,你一看就明白。

字段级加密核心流程 字段级加密核心流程图 敏感字段 e.g. 身份证号、手机号 选择算法与模式 AES-256-GCM / SM4-GCM 生成密钥与 IV 密钥来自KMS,IV随机12字节 加密运算 输出密文 + 认证标签 存储密文到数据库 VARCHAR 长度控制 关键点 密钥与IV分离存储 密文长度需预估 认证标签不能丢弃

2.5 代码演示:Java 与 Python 的典型用法

我习惯用代码说话。给你两段核心实现,感受一下实际对接时的样子。

Java:AES-256-GCM 加密

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class FieldEncryptor {
    private static final int GCM_TAG_LENGTH = 128; // 16字节
    private static final int IV_LENGTH = 12;       // 12字节
    
    public static String encrypt(String plaintext, byte[] key) throws Exception {
        byte[] iv = new byte[IV_LENGTH];
        // 用 SecureRandom 生成随机 IV
        new java.security.SecureRandom().nextBytes(iv);
        
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, spec);
        byte[] ciphertext = cipher.doFinal(plaintext.getBytes("UTF-8"));
        
        // 输出格式:IV + 密文 + 认证标签(ciphertext已经包含标签)
        byte[] combined = new byte[IV_LENGTH + ciphertext.length];
        System.arraycopy(iv, 0, combined, 0, IV_LENGTH);
        System.arraycopy(ciphertext, 0, combined, IV_LENGTH, ciphertext.length);
        
        return Base64.getEncoder().encodeToString(combined);
    }
}

Python:SM4-GCM 加密

from gmssl.sm4 import CryptSM4, SM4_GCM_MODE
import os
import base64

def sm4_gcm_encrypt(plaintext: str, key: bytes) -> str:
    iv = os.urandom(12)
    crypt_sm4 = CryptSM4()
    crypt_sm4.set_key(key, SM4_GCM_MODE)
    
    # GMSSL 的 GCM 接口会返回 (密文, 认证标签)
    ciphertext, tag = crypt_sm4.sm4_crypt_gcm(iv, plaintext.encode('utf-8'))
    
    # 组合 IV + 密文 + 标签
    result = iv + ciphertext + tag
    return base64.b64encode(result).decode('utf-8')

# 调用示例
key = b'\x00' * 16  # 生产环境从KMS获取
encrypted = sm4_gcm_encrypt('13800138000', key)
print(encrypted)

一个小坑: 我早期用 Python 做 SM4-GCM 时,发现不同库的标签拼接顺序不同。有的库 tag 放在密文前面。对接 SDK 时一定要确认编码格式。

2.6 关于 IV 的要点

  • IV 不能重复: 同一个密钥下,IV 重复一次,安全就直接崩了。我曾经在项目中看到有人用固定 IV,被我当场叫停。
  • IV 长度固定: GCM 推荐 12 字节。少于 12 字节会降低安全性。
  • IV 存储: IV 不需要保密,但必须随密文一起存。一般拼在密文前面,解密时先取出来。

你可能会问:IV 能不能用自增计数器?理论上可以,但分布式环境里同步计数器太麻烦。直接用随机数,省心。