数据加密
问题
数据库的数据加密方案有哪些?如何选择传输加密和存储加密策略?
答案
一、加密层次
二、传输加密(TLS/SSL)
防止数据在客户端和数据库之间被嗅探和中间人攻击。
应用服务器 ──TLS 加密通道──→ 数据库服务器
↕ 窃听者无法解密
MySQL TLS 配置
my.cnf
[mysqld]
ssl-ca=/etc/mysql/ssl/ca.pem
ssl-cert=/etc/mysql/ssl/server-cert.pem
ssl-key=/etc/mysql/ssl/server-key.pem
require_secure_transport=ON # 强制 TLS
-- 创建用户时强制 SSL
CREATE USER 'app_user'@'%' IDENTIFIED BY 'pass' REQUIRE SSL;
-- 或要求 x509 证书
CREATE USER 'app_user'@'%' IDENTIFIED BY 'pass' REQUIRE X509;
应用连接配置
// Node.js mysql2 连接
const connection = await mysql.createConnection({
host: 'db.example.com',
user: 'app_user',
password: 'pass',
ssl: {
ca: fs.readFileSync('/path/to/ca.pem'),
cert: fs.readFileSync('/path/to/client-cert.pem'),
key: fs.readFileSync('/path/to/client-key.pem'),
rejectUnauthorized: true, // 验证服务器证书
}
});
三、存储加密
TDE(透明数据加密)
数据库自动对存储在磁盘上的数据文件进行加密/解密,对应用完全透明。
-- MySQL InnoDB TDE
ALTER TABLE users ENCRYPTION='Y';
-- MySQL 配置加密密钥管理
[mysqld]
early-plugin-load=keyring_file.so
keyring_file_data=/var/lib/mysql-keyring/keyring
-- PostgreSQL TDE(pgcrypto 扩展 + 磁盘加密)
-- PostgreSQL 原生不支持 TDE,通常使用操作系统级加密:
-- LUKS(Linux 磁盘加密)
-- AWS EBS 加密 / Azure 磁盘加密
| 方案 | 保护范围 | 对应用影响 | 性能影响 |
|---|---|---|---|
| TDE | 磁盘文件 | 无(透明) | 3~5% |
| 磁盘加密 | 整个磁盘 | 无 | 1~3% |
| 备份加密 | 备份文件 | 无 | 取决于算法 |
TDE 不保护什么
TDE 只保护静态数据(物理文件被偷时)。数据在内存中和传输中是明文的。如果攻击者通过 SQL 注入获得查询权限,TDE 无法阻止数据泄露。
四、字段级加密(应用层加密)
对敏感字段在应用层进行加解密后再存储到数据库。
import crypto from 'crypto';
const ALGORITHM = 'aes-256-gcm';
// 加密
function encrypt(plaintext: string, key: Buffer): EncryptedData {
const iv = crypto.randomBytes(12); // GCM 推荐 12 字节 IV
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
const tag = cipher.getAuthTag();
return {
ciphertext: encrypted,
iv: iv.toString('hex'),
tag: tag.toString('hex'),
};
}
// 解密
function decrypt(data: EncryptedData, key: Buffer): string {
const decipher = crypto.createDecipheriv(
ALGORITHM, key,
Buffer.from(data.iv, 'hex')
);
decipher.setAuthTag(Buffer.from(data.tag, 'hex'));
let decrypted = decipher.update(data.ciphertext, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// 使用示例
const key = crypto.randomBytes(32); // 256-bit 密钥
const encrypted = encrypt('13800138000', key);
// 存储 encrypted.ciphertext、encrypted.iv、encrypted.tag 到数据库
const phone = decrypt(encrypted, key);
// phone === '13800138000'
字段加密的代价
- 无法 SQL 查询:加密后的字段不能用 WHERE、INDEX
- 排序失效:密文排序无意义
- 密钥管理复杂:密钥轮换需要重新加密所有数据
- 性能开销:每次读写都有加解密运算
可搜索加密替代方案
需求:手机号加密存储,但仍需精确查询
方案 1:存储完整密文 + 哈希索引列
┌──────────────────────────────────┐
│ phone_encrypted │ phone_hash │
│ 密文(AES-256) │ SHA-256(phone) │
└──────────────────────────────────┘
查询:WHERE phone_hash = SHA256('13800138000')
方案 2:前缀明文 + 后缀加密
phone_prefix: 138****
phone_encrypted: AES(13800138000)
模糊搜索用前缀,精确查询用解密
五、密钥管理
| 存储位置 | 安全性 | 说明 |
|---|---|---|
| 代码硬编码 | ❌ 极危险 | 提交到 Git 就泄露了 |
| 环境变量 | ⚠️ 一般 | 进程可见、日志可能泄露 |
| 配置文件 | ⚠️ 一般 | 需要文件权限控制 |
| 密钥管理服务 | ✅ 推荐 | HashiCorp Vault、AWS KMS |
应用 ──请求密钥──→ Vault / KMS ──返回密钥──→ 应用
↓
审计日志记录密钥访问
常见面试问题
Q1: 密码应该如何存储?
答案:
用户密码永远不要加密存储,必须使用哈希:
import bcrypt from 'bcrypt';
// 注册:哈希密码
const hash = await bcrypt.hash(password, 12); // 12 轮盐
// 存储 hash 到数据库
// 登录:验证密码
const isValid = await bcrypt.compare(inputPassword, storedHash);
- 加密是可逆的(有密钥就能解密)→ 不适合密码
- 哈希是不可逆的(无法从 hash 还原密码)→ 适合密码
- 推荐算法:
bcrypt、argon2(不要用 MD5、SHA-256)
Q2: TDE 和字段加密应该选哪个?
答案:
| 场景 | 方案 |
|---|---|
| 满足合规要求(GDPR 等) | TDE(成本低、透明) |
| 防止 DBA 查看敏感数据 | 字段加密(DBA 无密钥) |
| 保护磁盘/备份被盗 | TDE |
| 保护特定字段(身份证、银行卡) | 字段加密 |
| 两者结合 | 最佳实践 |
Q3: 加密后数据库性能下降怎么办?
答案:
- TDE:性能影响 3~5%,通常可忽略,现代 CPU 支持 AES-NI 硬件加速
- 字段加密:
- 只加密真正敏感的字段(而非所有字段)
- 使用缓存减少加解密次数
- 异步批量加密
- 密钥缓存:将 KMS 密钥缓存在内存中(with TTL),避免每次请求都调 KMS