PHP 数据库敏感数据加密存储方案
在用户注册、支付、身份认证等场景中,密码、手机号、身份证号、银行卡号等属于敏感数据,绝不可明文存储于数据库。PHP 提供了成熟的安全工具链,本文介绍一种符合 OWASP 和 GDPR 原则的端到端加密实践方案。
✅ 核心原则
- 加密而非哈希:对需可逆读取的字段(如手机号)使用 AES-256-GCM 加密;
- 密钥分离:主密钥(KEK)存于环境变量或密钥管理服务(KMS),不与代码/数据库共存;
- 每字段独立 IV:每次加密生成唯一随机初始化向量(IV),避免相同明文产生相同密文;
- 认证加密:选用 GCM 模式,同时保障机密性与完整性。
🔐 示例:加密存储用户手机号
<?php
// config.php —— 密钥应从环境变量加载(如 .env)
$encryptionKey = base64_decode($_ENV['APP_ENCRYPTION_KEY'] ?? 'your-32-byte-base64-key-here==');
function encryptField(string $plaintext, string $key): string
{
$iv = random_bytes(12); // GCM 推荐 12 字节 IV
$tag = '';
$ciphertext = openssl_encrypt(
$plaintext,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$iv,
$tag,
'', // aad (可选附加认证数据)
16 // tag length
);
if ($ciphertext === false) {
throw new RuntimeException('Encryption failed: ' . openssl_error_string());
}
return base64_encode($iv . $tag . $ciphertext);
}
function decryptField(string $encrypted, string $key): string
{
$data = base64_decode($encrypted);
$iv = substr($data, 0, 12);
$tag = substr($data, 12, 16);
$ciphertext = substr($data, 28);
$plaintext = openssl_decrypt(
$ciphertext,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$iv,
$tag
);
if ($plaintext === false) {
throw new RuntimeException('Decryption failed: ' . openssl_error_string());
}
return $plaintext;
}
// 使用示例
$phone = '+8613800138000';
$encryptedPhone = encryptField($phone, $encryptionKey);
// 存入数据库:INSERT INTO users (encrypted_phone) VALUES (?)
$decryptedPhone = decryptField($encryptedPhone, $encryptionKey);
echo $decryptedPhone; // → '+8613800138000'
?>
⚠️ 重要提醒
不要自行实现密码存储! 对密码请始终使用
password_hash() + password_verify()(基于 Argon2i 或 bcrypt)。本文方案仅适用于需要解密的业务字段(如脱敏展示、短信验证回显等)。
最后,建议结合数据库列级加密(如 MySQL 8.0+ 的 AES_ENCRYPT())、应用层密钥轮换机制及审计日志,构建纵深防御体系。安全不是功能,而是贯穿设计、开发与运维的持续实践。