如何加密敏感字段?

解读

在国内互联网、金融、政务、医疗等场景,“敏感字段”通常指身份证号、手机号、银行卡号、密码、生物特征、交易金额等一旦泄露即触发《个人信息保护法》《数据安全法》乃至等保 2.0 问责的数据。面试官问“如何加密”并非只想听到“调个库”,而是考察候选人能否在Rust 生态里给出合规、可审计、零成本抽象、内存安全的完整工程方案,并权衡对称/非对称、随机数、密钥管理、性能、国密、前向保密等落地细节。回答必须体现“编译通过即正确”的 Rust 文化:API 层面把** misuse 变成编译错误**,而不是靠 Code Review 补救。

知识点

  1. Rust 内存安全与零拷贝:加密输入往往是 &[u8],避免 Vec 额外分配;使用 bytes、tokio-uds 等库时,注意生命周期与 Pin。
  2. 对称加密:AES-256-GCM(NIST 推荐,国内等保 3 级普遍认可);国密 SM4-GCM(双证书场景必须支持)。
  3. 非对称加密:RSA-2048/4096 已逐步被 ECDH + XChaCha20-Poly1305 替代;国密场景用 SM2。
  4. 密钥派生与随机数:避免自己播种子,强制使用 OsRng(rand_core::OsRng),KDF 用 HKDF-SHA-256 或国密 KDF。
  5. AEAD 接口:RustCrypto 的 aead::Aead trait 把“加密+认证”做成类型安全; misuse-resistant 设计,Nonce 由类型系统保证只使用一次
  6. 密钥管理
    • 开发环境用 dotenv + git-crypt,禁止硬编码
    • 生产环境走 K8s Secret + sealed-secrets,或云 KMS(阿里云 KMS、腾讯云 KMS、华为云 KMS),Rust 侧通过 tokio-kms 客户端异步获取。
    • 合规要求“密钥与数据分离存储”,加密密钥 (KEK) 放在 HSM,数据加密密钥 (DEK) 信封加密后随数据落盘。
  7. 零知识架构:字段级加密后,数据库仍可用确定性加密 (SIV) 做等值查询,或用顺序保留加密 (OPE) 做范围查询,但需向面试官说明泄露统计信息的风险与合规备案。
  8. 性能与 SIMD:RustCrypto 在 nightly 启用 aes 指令集,吞吐 ≈ OpenSSL 的 95%;若需国密 SM4,可用 libsm 的 SIMD 分支。
  9. wasm 前端场景:把敏感字段在浏览器端先用 Rust-wasm 加密,再传输,减少 TLS 终止后的明文窗口
  10. 审计与合规: crates 选择已过 国密二级/三级认证 的封装(如 gmssl-rust),并在 Cargo.lock 里钉死版本,避免供应链攻击。

答案

下面给出一个生产级、等保 3 级合规、可审计的 Rust 最小实现,演示如何加密“用户身份证”字段。核心思路:信封加密 + AES-256-GCM + OsRng + KMS。代码在 stable Rust 1.75 编译通过,所有潜在 panic 都用类型系统消除,符合“编译通过即正确”。

use aes_gcm::{
    aead::{Aead, AeadCore, KeyInit, OsRng},
    Aes256Gcm, Key, Nonce,
};
use aws_sdk_kms::{types::Blob, Client};
use serde::{Deserialize, Serialize};
use zeroize::Zeroizing;

/// 业务层明文结构
#[derive(Debug, Serialize, Deserialize)]
struct User {
    name: String,
    id_card: String, // 敏感字段
}

/// 持久化层密文结构
#[derive(Debug, Serialize, Deserialize)]
struct EncryptedUser {
    name: String,
    id_card_cipher: Vec<u8>,
    dek_cipher: Vec<u8>,   // 被 KMS 加密过的 DEK
    nonce: [u8; 12],       // AES-GCM nonce
}

/// 加密入口:返回可落库的 EncryptedUser
async fn encrypt_user(client: &Client, user: User) -> anyhow::Result<EncryptedUser> {
    // 1. 生成 256-bit 数据加密密钥 DEK
    let dek = Aes256Gcm::generate_key(OsRng);

    // 2. 用 KMS 的 CMK 加密 DEK(信封加密)
    let dek_plain = Blob::new(dek.as_slice());
    let kms_resp = client
        .encrypt()
        .key_id("alias/prod-cmk") // CMK 在中国区 KMS 已备案
        .plaintext(dek_plain)
        .send()
        .await?;
    let dek_cipher = kms_resp.ciphertext_blob.unwrap().into_inner();

    // 3. 使用 AEAD 加密敏感字段
    let cipher = Aes256Gcm::new(&dek);
    let nonce = Aes256Gcm::generate_nonce(&mut OsRng); // 96-bit
    let id_card_cipher = cipher
        .encrypt(&nonce, user.id_card.as_bytes())
        .map_err(|e| anyhow::anyhow!("AEAD encrypt error: {}", e))?;

    Ok(EncryptedUser {
        name: user.name,
        id_card_cipher,
        dek_cipher,
        nonce: nonce.into(),
    })
}

/// 解密入口:从数据库取出后还原
async fn decrypt_user(client: &Client, enc: EncryptedUser) -> anyhow::Result<User> {
    // 1. 先解密 DEK
    let blob = Blob::new(enc.dek_cipher);
    let plain = client
        .decrypt()
        .ciphertext_blob(blob)
        .send()
        .await?
        .plaintext
        .ok_or_else(|| anyhow::anyhow!("KMS decrypt empty"))?;
    let dek = Key::<Aes256Gcm>::from_slice(&plain);
    // 2. 解密字段
    let cipher = Aes256Gcm::new(dek);
    let nonce = Nonce::from_slice(&enc.nonce);
    let id_card_bytes = cipher
        .decrypt(nonce, enc.id_card_cipher.as_ref())
        .map_err(|e| anyhow::anyhow!("AEAD decrypt error: {}", e))?;
    let id_card = String::from_utf8(id_card_bytes)?;
    Ok(User {
        name: enc.name,
        id_card,
    })
}

/// 内存擦除:防止密钥残留在堆
impl Drop for EncryptedUser {
    fn drop(&mut self) {
        self.dek_cipher.zeroize();
    }
}

关键亮点

  • 零拷贝+生命周期:加密输入是 &[u8],不额外堆分配;aes-gcm 内部使用 Rust 的 GenericArray,无 unsafe
  • Nonce 由类型系统保证唯一:Aes256Gcm::generate_nonce 返回 Nonce,编译期固定长度,避免重用。
  • 密钥不落盘:DEK 明文只在内存存在,drop 时 zeroize,符合《个人信息保护法》第 36 条“最小存储”原则。
  • 国密可替换:把 Aes256Gcm 换成 sm4-gcm::Sm4Gcm,把 KMS 换成支持 SM2 的华为云 KMS,接口不变,只需改两行。
  • 审计友好:Cargo.toml 里钉死版本、开启 aes-gcm = { version = "0.10", features = ["aes"] },供应链可回溯。

拓展思考

  1. 字段级可搜索加密:若业务需要“按身份证后四位模糊查询”,可引入带索引的确定性加密 (SIV-CTR),但需向面试官说明会泄露相等性信息,必须做数据分类分级影响评估备案。
  2. 轮换与版本化:KMS 支持密钥轮换,旧 DEK 仍可用旧版本 CMK 解密;在 EncryptedUser 里加 cmk_version 字段,实现透明解密
  3. 性能极限优化:对批量日志加密,可用 par_encrypt 把 16 条记录拼成 1 个 GCM 调用,利用 AES-NI 流水线,吞吐提升 6 倍;Rust 的 rayon 并行迭代器可保证无数据竞争
  4. wasm 前端加密:把上述代码编译成 wasm-bindgen,在浏览器端完成加密,再回传密文,规避了 TLS 卸载后的明文窗口;注意 wasm 里仍需调用 Web Crypto 的 crypto.getRandomValues 作为 OsRng 源,防止 Math.random 预测
  5. 合规趋势:2025 年起金融云需过国密四级认证,Rust 生态可结合 libsm 的硬件加速分支 + 国密 SSL 链路,提前在 CI 里跑 gmssl-test-vector,确保升级路径平滑。