跳到主要内容

数据加密

问题

数据库的数据加密方案有哪些?如何选择传输加密和存储加密策略?

答案

一、加密层次

二、传输加密(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'
字段加密的代价
  1. 无法 SQL 查询:加密后的字段不能用 WHERE、INDEX
  2. 排序失效:密文排序无意义
  3. 密钥管理复杂:密钥轮换需要重新加密所有数据
  4. 性能开销:每次读写都有加解密运算

可搜索加密替代方案

需求:手机号加密存储,但仍需精确查询

方案 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 还原密码)→ 适合密码
  • 推荐算法:bcryptargon2(不要用 MD5、SHA-256)

Q2: TDE 和字段加密应该选哪个?

答案

场景方案
满足合规要求(GDPR 等)TDE(成本低、透明)
防止 DBA 查看敏感数据字段加密(DBA 无密钥)
保护磁盘/备份被盗TDE
保护特定字段(身份证、银行卡)字段加密
两者结合最佳实践

Q3: 加密后数据库性能下降怎么办?

答案

  1. TDE:性能影响 3~5%,通常可忽略,现代 CPU 支持 AES-NI 硬件加速
  2. 字段加密
    • 只加密真正敏感的字段(而非所有字段)
    • 使用缓存减少加解密次数
    • 异步批量加密
  3. 密钥缓存:将 KMS 密钥缓存在内存中(with TTL),避免每次请求都调 KMS

相关链接