3. 密钥存储不当:文件系统权限不足、数据库明文存储、备份泄露风险

密钥存储,说白了就是你把家底藏在哪的问题。我见过太多团队,加密算法选得挺强,密钥长度也够,结果密钥就明文躺在配置文件里——那感觉就像给金库装了最好的锁,然后把钥匙挂在门口。

这一章我们专门聊聊密钥存储的三大坑:文件系统权限失控、数据库里存明文、备份泄露。每个我都踩过,或者看别人踩过。

3.1 文件系统权限不足

很多应用启动时需要读取密钥文件。如果权限设成 644 甚至 777,那任何能登录服务器的用户都能把密钥拷走。

我记得有一次做安全审计,发现某项目的私钥文件权限是 755。问运维为什么,他说“这样方便备份脚本读取”。嗯,方便是方便了,攻击者也方便了。

⚠️ 常见错误配置
  • 密钥文件与代码放在同一目录,权限继承自项目目录(通常是 755)
  • 使用 root 用户启动应用,密钥文件属主为 root,但权限设为 644
  • 将密钥文件放在 Web 可访问的静态资源目录下

正确的做法是什么?我个人的习惯是:

  • 密钥文件属主设为启动应用的服务账户(如 appuser)
  • 权限设为 400(仅属主可读)或 600(属主可读写)
  • 密钥文件放在应用代码目录之外,比如 /etc/secrets/
# 创建专用目录
sudo mkdir -p /etc/secrets
sudo chmod 750 /etc/secrets

# 存放密钥文件
sudo cp private_key.pem /etc/secrets/
sudo chown appuser:appgroup /etc/secrets/private_key.pem
sudo chmod 400 /etc/secrets/private_key.pem
💡 小技巧

可以用 ls -la 定期扫描所有密钥文件的权限。我曾经写过一个简单的 cron 脚本,每天检查一次,发现权限异常就告警。这招帮我抓到了两次运维误操作。

3.2 数据库明文存储

这个坑更常见。业务需要加密某些字段,结果开发同学直接把密钥存到了数据库的配置表里,还是明文。

你想想看,数据库一旦被拖库,攻击者拿到的不只是数据,连解密的钥匙都一并奉上了。这叫什么?这叫买一送一。

我遇到过最离谱的一次,是某金融项目把支付接口的 RSA 私钥存在 MySQL 的 system_config 表里,字段名叫 payment_private_key,值就是 PEM 格式的明文。我当时差点没背过气去。

🔑 核心原则

密钥永远不应该与应用数据存储在同一个数据库中。如果必须存,至少要做到:

  • 使用数据库的加密功能(如 MySQL 的 AES_ENCRYPT)
  • 加密密钥的密钥(KEK)从外部系统获取,不落库
  • 对密钥表的访问权限做严格限制

更好的方案是使用专门的密钥管理服务(KMS),比如 AWS KMS、Azure Key Vault 或 HashiCorp Vault。应用运行时只从这些服务获取密钥,不持久化到本地。

存储方式 风险等级 说明
数据库明文存储 🔴 极高 拖库即泄露,毫无防护
数据库加密存储 🟡 中等 需要额外管理 KEK,增加复杂度
专用密钥管理服务 🟢 低 业界最佳实践,但依赖外部服务
硬件安全模块(HSM) 🟢 极低 物理隔离,成本高

3.3 备份泄露风险

这个点容易被忽略。很多公司对生产环境的密钥管控很严,但备份呢?

我曾经参与过一次事后复盘:某公司服务器硬盘坏了,运维从备份恢复数据。结果发现备份文件里包含了所有密钥,而且备份文件本身没有加密,存储在 AWS S3 上,权限还是公开可读的。这个备份在 S3 上躺了两年,谁也不知道有没有被人下载过。

为什么会这样?因为备份脚本通常由运维编写,他们关注的是数据完整性,很少考虑密钥是否被一并备份了。

⚠️ 备份中的密钥泄露场景
  • 全量备份包含密钥文件
  • 备份文件未加密
  • 备份存储权限配置错误
  • 备份介质丢失(如磁带、硬盘)
  • 备份传输过程未使用加密通道

修复方案其实不复杂:

  1. 备份前排除密钥文件:在备份脚本中明确排除 /etc/secrets/ 等目录
  2. 单独备份密钥:密钥使用独立的备份策略,加密后存储
  3. 备份文件本身加密:使用 GPG 或类似工具对备份文件加密
  4. 定期验证备份恢复:确保恢复流程中密钥能正确还原
# 备份脚本示例:排除密钥目录
tar czf /backup/app_$(date +%Y%m%d).tar.gz \
  --exclude=/etc/secrets \
  /var/www/app

# 单独加密备份密钥
gpg --encrypt --recipient backup@company.com \
  /etc/secrets/private_key.pem

3.4 修复方案:加密存储 + 访问控制

讲完了问题,我们聊聊怎么修。核心就两招:加密存储和访问控制。

3.4.1 加密存储

密钥本身也要加密。听起来有点绕,但这是分层加密的基础思想。

我习惯的做法是:

  • 使用主密钥(Master Key)加密所有业务密钥
  • 主密钥存储在 HSM 或 KMS 中
  • 业务密钥加密后存储在文件系统或数据库中
  • 应用启动时,先从 KMS 获取主密钥,再用主密钥解密业务密钥
📐 分层加密架构

主密钥 → 加密 → 密钥加密密钥(KEK)→ 加密 → 数据加密密钥(DEK)→ 加密 → 业务数据

这样即使业务密钥文件泄露,攻击者没有主密钥也无法解密。

# Python 示例:使用 KMS 加密本地密钥
import boto3
from cryptography.fernet import Fernet

kms = boto3.client('kms')

# 生成数据密钥
response = kms.generate_data_key(
    KeyId='alias/my-master-key',
    KeySpec='AES_256'
)

# 明文数据密钥(仅内存中使用)
plaintext_key = response['Plaintext']

# 加密后的数据密钥(可安全存储)
encrypted_key = response['CiphertextBlob']

# 使用数据密钥加密业务密钥
fernet = Fernet(plaintext_key)
encrypted_business_key = fernet.encrypt(business_key_bytes)

# 存储 encrypted_key 和 encrypted_business_key
# 丢弃 plaintext_key

3.4.2 访问控制

加密做得再好,如果谁都能读到密钥文件,那也是白搭。

访问控制要覆盖三个层面:

层面 措施 说明
操作系统 文件权限、SELinux、AppArmor 限制哪些进程可以读取密钥文件
应用层 最小权限原则、密钥轮换 每个服务只拥有它需要的密钥
网络层 KMS 访问策略、VPC 端点 限制密钥管理服务的网络访问来源

我个人比较推崇“零信任”的思路:不信任任何网络、任何进程、任何用户。每次访问密钥都要显式授权,并且记录审计日志。

📋 避坑指南

我曾经遇到过一个问题:开发环境为了方便,把密钥权限放得很宽。结果测试同学写了个自动化脚本,不小心把密钥文件上传到了公开的 GitHub 仓库。从那以后,我坚持所有环境(包括开发环境)的密钥权限必须和生产环境一致。麻烦是麻烦了点,但安全没有捷径。

3.5 知识体系总览

下面这张图总结了密钥存储不当的核心问题与修复路径,你可以把它当作本章的思维导图:

密钥存储不当:漏洞与修复方案 密钥存储不当:漏洞与修复方案 密钥存储不当 文件系统权限不足 数据库明文存储 备份泄露风险 权限设为 644/777 与代码目录混放 配置表明文存储 无 KEK 分层加密 备份包含密钥文件 备份未加密存储 修复方案:加密存储 + 访问控制 加密存储:分层加密 + KMS/HSM 访问控制:OS + 应用 + 网络三层 核心思路:密钥永不落明文,访问永不缺管控

这张图把三个漏洞和修复方案串在了一起。你可以看到,文件系统、数据库、备份这三个入口,最终都指向同一个修复方向:加密存储 + 访问控制。

好了,这一章的内容就到这里。密钥存储是密钥管理的基础,基础打不牢,上层建筑再漂亮也没用。希望你能把这些原则落实到实际项目中,别让密钥成为你系统的短板。