PHP 数据库敏感数据加密存储方案
在用户注册、支付、身份认证等场景中,密码、手机号、身份证号、银行卡号等属于敏感数据,绝不可明文存储于数据库。PHP 提供了成熟的安全工具链,本文介绍一种符合 OWASP 和 GDPR 原则的端到端加密实践方案。
✅ 核心原则
- 加密而非哈希:对需可逆读取的字段(如手机号)使用 AES-256-GCM 加密;
- 密钥分离:主密钥(KEK)存于环境变量或密钥管理服务(如 AWS KMS),不硬编码;
- 随机 IV + 认证标签:使用 GCM 模式确保机密性与完整性;
- 字段级加密:仅加密敏感列(如
encrypted_phone),非整表加密。
🔐 示例:手机号加密存储
<?php
// config.php —— 从环境变量加载密钥(推荐使用 dotenv 或系统 env)
$key = base64_decode($_ENV['APP_ENCRYPTION_KEY'] ?? 'your-32-byte-base64-key-here==');
if (mb_strlen($key, '8bit') !== 32) {
throw new RuntimeException('Invalid encryption key length');
}
// 加密函数(AES-256-GCM)
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);
if (strlen($data) < 28) {
throw new RuntimeException('Invalid encrypted data length');
}
$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 = '13812345678';
$encrypted = encryptField($phone, $key);
echo "加密后: $encrypted\n"; // 存入数据库 encrypted_phone 字段
$decrypted = decryptField($encrypted, $key);
echo "解密后: $decrypted\n"; // 输出: 13812345678
?>
⚠️ 重要提醒
❌ 切勿使用 md5()、sha1() 或未加盐 hash 存储可逆敏感数据;
❌ 避免自研加密逻辑;优先使用 OpenSSL 扩展(PHP ≥ 7.1);
✅ 密钥必须定期轮换,并记录审计日志;
✅ 生产环境禁用
❌ 避免自研加密逻辑;优先使用 OpenSSL 扩展(PHP ≥ 7.1);
✅ 密钥必须定期轮换,并记录审计日志;
✅ 生产环境禁用
display_errors,防止密钥/错误信息泄露。
最后,建议结合 Laravel 的 encrypt cast 或 Symfony 的 Security Component 进行工程化封装,提升可维护性与安全性。
安全不是功能,而是设计起点。
```