数据加密:敏感数据的存储与传输保护
一、泄露后的代价
2025年某医疗平台数据泄露:30万条患者记录被拖走,包含姓名、身份证号、手机号、病历——全部明文存储。
后果:罚款500万,用户信任崩塌,股价跌了20%。
事后审查发现,平台对加密的理解停留在"HTTPS就够了"——传输加密了,但数据库里的数据是裸的。攻击者只要能进数据库,所有数据一览无余。
加密的正确认知:
- HTTPS保护传输过程,不保护存储数据
- 数据库加密保护静态数据,不保护传输过程
- 两层都要做,缺一不可
二、加密体系全景
2.1 加密类型与适用场景
┌──────────────────────────────────────────────────┐ │ 数据加密体系 │ ├────────────────┬─────────────────────────────────┤ │ 传输加密 │ HTTPS/TLS(客户端到服务端) │ │ │ mTLS(服务间通信) │ │ │ SSH(运维通道) │ ├────────────────┬─────────────────────────────────┤ │ 存储加密 │ 字段级加密(身份证、密码等) │ │ │ 文件级加密(附件、日志等) │ │ │ 磁盘级加密(TDE/云加密) │ ├────────────────┬─────────────────────────────────┤ │ 密钥管理 │ 密钥生成、存储、轮换、销毁 │ │ │ KMS(密钥管理服务) │ │ │ HSM(硬件安全模块) │ └────────────────┴─────────────────────────────────┘2.2 对称加密 vs 非对称加密
| 维度 | 对称加密(AES) | 非对称加密(RSA/ECC) |
|---|---|---|
| 密钥 | 加密解密用同一密钥 | 公钥加密,私钥解密 |
| 速度 | 极快(硬件加速) | 慢(数学运算密集) |
| 适用 | 大量数据加密 | 密钥交换、数字签名 |
| 密钥分发 | 需安全通道传密钥 | 公钥可公开分发 |
| 安全等级 | AES-256足够安全 | RSA-2048,ECC-P256 |
实战组合:非对称加密交换密钥,对称加密加密数据——这就是TLS和混合加密的核心思路。
2.3 加密算法选择
推荐算法: - 对称加密:AES-256-GCM(认证加密,防篡改) - 非对称加密:RSA-2048用于签名,ECC-P256用于密钥交换 - 哈希:SHA-256/SHA-512 - 密码哈希:bcrypt/Argon2(带盐、可调成本) - HMAC:SHA-256 HMAC(消息认证码) 不推荐/已淘汰: - DES/3DES:密钥太短 - MD5/SHA-1:已被攻破 - RC4:存在已知弱点 - ECB模式:相同明文产生相同密文,不安全三、传输加密实践
3.1 HTTPS/TLS配置
# Nginx HTTPS安全配置 server { listen 443 ssl http2; # 证书配置 ssl_certificate /etc/ssl/certs/server.crt; ssl_certificate_key /etc/ssl/private/server.key; # TLS版本(仅允许1.2和1.3) ssl_protocols TLSv1.2 TLSv1.3; # 密码套件(优先ECDHE,支持前向保密) ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305'; # HSTS(强制HTTPS) add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # OCSP Stapling(在线证书状态检查) ssl_stapling on; ssl_stapling_verify on; # 会话恢复 ssl_session_timeout 1d; ssl_session_cache shared:SSL:50m; }3.2 服务间mTLS
# Istio mTLS配置(服务间加密通信)apiVersion:security.istio.io/v1beta1kind:PeerAuthenticationmetadata:name:defaultnamespace:productionspec:mtls:mode:STRICT# 严格模式:所有通信必须加密mTLS证书管理:
// 服务间证书自动轮换@ServicepublicclassCertificateRotationService{@Scheduled(cron="0 0 2 * * ?")// 每天凌晨2点检查publicvoidcheckAndRotate(){List<ServiceCertificate>certs=certRepo.findAll();for(ServiceCertificatecert:certs){// 证书7天内过期则提前轮换if(cert.getExpiry().isBefore(LocalDateTime.now().plusDays(7))){rotateCertificate(cert);}}}privatevoidrotateCertificate(ServiceCertificateoldCert){// 1. 向Istio/CA申请新证书CertificateRequestreq=CertificateRequest.builder().serviceId(oldCert.getServiceId()).validityDays(90).build();ServiceCertificatenewCert=caClient.issueCertificate(req);// 2. 灰度部署新证书(双证书并存)certDeployer.deployDualCert(oldCert,newCert);// 3. 确认新证书生效后撤销旧证书certDeployer.confirmAndRevokeOld(oldCert);}}四、存储加密实践
4.1 字段级加密
对身份证号、手机号等敏感字段单独加密存储:
@ServicepublicclassDataEncryptionService{@AutowiredprivateKeyManagementServicekms;/** * 字段级加密 */publicStringencryptField(Stringplaintext,StringkeyId){// 1. 从KMS获取数据加密密钥DataKeydataKey=kms.getDataKey(keyId);// 2. AES-256-GCM加密(认证加密,防篡改)Ciphercipher=Cipher.getInstance("AES/GCM/NoPadding");GCMParameterSpecspec=newGCMParameterSpec(128,dataKey.getIv());cipher.init(Cipher.ENCRYPT_MODE,dataKey.getSecretKey(),spec);byte[]ciphertext=cipher.doFinal(plaintext.getBytes(UTF_8));// 3. 存储密文 + 密钥ID + IV(密钥ID用于解密时找对应密钥)returnBase64.getEncoder().encodeToString(join(dataKey.getKeyId(),dataKey.getIv(),ciphertext));}/** * 字段级解密 */publicStringdecryptField(StringencryptedData){byte[]raw=Base64.getDecoder().decode(encryptedData);// 1. 解析密钥ID、IV、密文StringkeyId=extractKeyId(raw);byte[]iv=extractIv(raw);byte[]ciphertext=extractCiphertext(raw);// 2. 从KMS获取对应密钥DataKeydataKey=kms.getDataKey(keyId);// 3. AES-GCM解密(GCM自带完整性校验)Ciphercipher=Cipher.getInstance("AES/GCM/NoPadding");GCMParameterSpecspec=newGCMParameterSpec(128,iv);cipher.init(Cipher.DECRYPT_MODE,dataKey.getSecretKey(),spec);returnnewString(cipher.doFinal(ciphertext),UTF_8);}}数据库表设计:
-- 用户表:敏感字段加密存储CREATETABLEusers(idBIGINTPRIMARYKEY,usernameVARCHAR(50),-- 非敏感,明文phone_encryptedTEXT,-- 手机号加密存储id_card_encryptedTEXT,-- 身份证号加密存储emailVARCHAR(100),-- 非敏感,明文password_hashVARCHAR(100),-- 密码哈希(bcrypt)encryption_key_idVARCHAR(50),-- 加密密钥IDcreated_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP);4.2 密码哈希存储
@ServicepublicclassPasswordService{// bcrypt密码哈希(cost factor=12)publicStringhashPassword(Stringpassword){returnBCrypt.hashpw(password,BCrypt.gensalt(12));}// 密码验证publicbooleanverifyPassword(Stringpassword,Stringhash){returnBCrypt.checkpw(password,hash);}}密码哈希对比:
| 算法 | 优势 | 劣势 | 推荐 |
|---|---|---|---|
| bcrypt | 自带盐,可调成本因子 | 不支持并行验证 | 推荐(cost=10-12) |
| Argon2 | 抗GPU/ASIC,内存硬 | 实现复杂 | 推荐(密码比赛冠军) |
| PBKDF2 | 标准化,广泛支持 | 不抗GPU攻击 | 可用 |
| MD5/SHA | 快 | 无盐、无成本因子、快=不安全 | 禁止使用 |
4.3 信封加密模式
信封加密(Envelope Encryption)是云厂商推荐的加密模式:数据密钥加密数据,主密钥加密数据密钥。
【信封加密流程】 加密: 1. KMS生成数据密钥(DataKey)→ 明文DK + 密文DK 2. 明文DK加密数据 → 密文数据 3. 密文DK + 密文数据 → 一起存储 4. 明文DK立即销毁(不在内存中持久化) 解密: 1. 取出密文DK → 请求KMS解密 → 得到明文DK 2. 明文DK解密密文数据 → 明文数据 3. 明文DK使用后立即销毁@ServicepublicclassEnvelopeEncryptionService{@AutowiredprivateKmsClientkmsClient;publicEncryptedDataencrypt(Stringplaintext){// 1. 请求KMS生成数据密钥GenerateDataKeyResultresult=kmsClient.generateDataKey("alias/main-key","AES_256");// 2. 用明文数据密钥加密数据Stringciphertext=aesEncrypt(plaintext,result.getPlaintextDataKey());// 3. 销毁明文密钥,只保留密文密钥result.destroyPlaintextKey();returnEncryptedData.builder().encryptedDataKey(result.getEncryptedDataKey())// 密文密钥.ciphertext(ciphertext)// 密文数据.build();}publicStringdecrypt(EncryptedDataencrypted){// 1. 请求KMS解密数据密钥StringplaintextKey=kmsClient.decryptDataKey(encrypted.getEncryptedDataKey());// 2. 用明文密钥解密数据Stringplaintext=aesDecrypt(encrypted.getCiphertext(),plaintextKey);// 3. 使用后立即销毁明文密钥destroyKey(plaintextKey);returnplaintext;}}五、密钥管理
5.1 密钥生命周期
密钥生命周期: 创建 → 分发 → 存储 → 使用 → 轮换 → 撤销 → 销毁 关键安全措施: - 创建:KMS/HSM生成,禁止人工指定密钥 - 存储:密钥绝不与数据同库存储 - 使用:信封加密模式,数据密钥用后销毁 - 轮换:主密钥每年轮换,数据密钥每次加密新生成 - 撤销:密钥泄露时立即撤销 - 销毁:密钥退役后安全销毁,不可恢复5.2 密钥轮换
@ServicepublicclassKeyRotationService{// 主密钥自动轮换(每年)@Scheduled(cron="0 0 0 1 1 ?")// 每年1月1日publicvoidrotateMasterKey(){StringoldKeyId=kmsClient.getCurrentMasterKeyId();// 1. 创建新主密钥StringnewKeyId=kmsClient.createMasterKey("AES_256");// 2. 设置新密钥为活跃密钥kmsClient.setActiveKey(newKeyId);// 3. 旧密钥标记为"仅解密"(不再用于加密新数据)kmsClient.setKeyState(oldKeyId,KeyState.DECRYPT_ONLY);// 4. 30天后,旧密钥数据全部重新加密到新密钥scheduleReEncryption(oldKeyId,newKeyId,30);}// 批量重新加密数据publicvoidreEncryptData(StringoldKeyId,StringnewKeyId){// 查询所有使用旧密钥加密的数据List<EncryptedRecord>records=dataRepo.findByKeyId(oldKeyId);for(EncryptedRecordrecord:records){// 用旧密钥解密 → 用新密钥重新加密Stringplaintext=envelopeService.decrypt(record.getEncryptedData());EncryptedDatanewEncrypted=envelopeService.encrypt(plaintext);record.setEncryptedData(newEncrypted);record.setKeyId(newKeyId);dataRepo.save(record);}// 5. 重新加密完成后,旧密钥可以安全销毁kmsClient.destroyKey(oldKeyId);}}六、踩坑实录
坑点1:ECB模式导致密文可推断
问题:使用AES-ECB加密身份证号,相同身份证号产生相同密文,攻击者通过密文比对可推断身份证号是否相同。
解决:使用AES-GCM或AES-CBC+HMAC,每次加密使用随机IV,相同明文产生不同密文。
坑点2:密钥硬编码在代码中
问题:密钥写在配置文件或代码中,代码泄露后密钥泄露。
解决:密钥只存在KMS/HSM中,应用通过API获取,密钥永不出现在代码或配置文件中。
坑点3:HTTPS了就安全了
问题:HTTPS只保护传输,数据库中的数据仍为明文。
解决:传输加密(HTTPS)+ 存储加密(字段级加密)双层防护。
坑点4:加密后无法查询
问题:手机号加密后无法做模糊搜索(如按号段筛选)。
解决:
- 方案1:存储明文哈希(SHA-256)用于精确匹配,加密原文用于还原
- 方案2:保留格式加密(FPE),加密后保持手机号格式,可按号段筛选
- 方案3:建立加密索引表(密文 → 明文哈希映射)
// 加密索引方案publicvoidsaveUser(Useruser){// 加密手机号StringencryptedPhone=encryptService.encryptField(user.getPhone(),"phone-key");// 同时存储手机号的哈希(用于搜索)StringphoneHash=DigestUtils.sha256Hex(user.getPhone());user.setPhoneEncrypted(encryptedPhone);user.setPhoneHash(phoneHash);// 索引字段userRepo.save(user);}// 搜索时用哈希匹配publicUserfindByPhone(Stringphone){StringphoneHash=DigestUtils.sha256Hex(phone);returnuserRepo.findByPhoneHash(phoneHash);}七、选型对比
| 方案 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| 应用层加密 | 高度敏感数据 | 最灵活,字段级控制 | 开发成本高 |
| TDE(透明加密) | 数据库全量加密 | 无需改代码,DB层加密 | 无法字段级控制 |
| 云KMS | 云原生应用 | 免运维,自动轮换 | 云厂商依赖 |
| 自建KMS | 合规要求 | 完全自主可控 | 运维成本高 |
| HSM | 金融级安全 | 硬件级密钥保护 | 成本极高 |
八、总结
数据加密不是可选功能,是安全底线。
核心要点:
- 传输加密(HTTPS/mTLS)+ 存储加密(字段级加密)双层防护
- AES-256-GCM用于数据加密,bcrypt/Argon2用于密码哈希
- 信封加密模式:数据密钥加密数据,主密钥加密数据密钥
- 密钥管理是加密体系的核心——密钥泄露等于加密白做
- 密钥轮换是常态操作,不是例外操作
一句话总结:加密两层做,密钥不落地,算法选主流,轮换是常态。
作者:架构实战团队
日期:2026-07-16
标签:#数据加密 #AES #HTTPS #密钥管理 #信封加密