PHP 数据库敏感数据加密存储方案
在用户注册、支付、身份认证等场景中,密码、手机号、身份证号、银行卡号等属于敏感数据,绝不可明文存储于数据库。PHP 提供了成熟的安全工具链,本文介绍一种符合 OWASP 和 GDPR 原则的端到端加密实践方案。
✅ 核心原则
- 加密而非哈希:对需可逆读取的字段(如手机号、邮箱)使用对称加密;密码等仅需验证的字段仍用
password_hash()哈希。 - 密钥分离:主密钥(Master Key)不硬编码,建议通过环境变量或密钥管理服务(KMS)注入。
- AES-256-GCM:采用带认证的加密模式,防篡改、防重放,且 PHP 7.1+ 原生支持。
🔐 示例:加密存储用户手机号
<?php
// config.php —— 从环境变量安全加载密钥
$encryptionKey = $_ENV['APP_ENCRYPTION_KEY'] ?? '32-byte-secret-key-for-aes-256-gcm';
if (mb_strlen($encryptionKey, '8bit') !== 32) {
throw new RuntimeException('Encryption key must be exactly 32 bytes.');
}
function encryptField(string $plaintext, string $key): string
{
$iv = random_bytes(12); // GCM recommended IV length: 12 bytes
$tag = '';
$ciphertext = openssl_encrypt(
$plaintext,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$iv,
$tag,
'', // aad (optional)
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 = mb_substr($data, 0, 12, '8bit');
$tag = mb_substr($data, 12, 16, '8bit');
$ciphertext = mb_substr($data, 28, null, '8bit');
$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);
echo "加密后: {$encryptedPhone}\n"; // 存入数据库 users.phone_encrypted 字段
$decryptedPhone = decryptField($encryptedPhone, $encryptionKey);
echo "解密后: {$decryptedPhone}\n"; // 显示或校验时调用
?>
💡 最佳实践提醒
⚠️ 切勿:将密钥写死在代码中;使用 ECB 模式;忽略 IV/Tag 安全存储;在日志中打印加密前数据。
此外,建议结合 Laravel 的 encrypt()/decrypt() 或 Symfony 的 Symfony\Component\Security\Crypto\Encryptor(已弃用,推荐使用 defuse/php-encryption 库)进一步提升可靠性。生产环境务必启用 PHP 的 OpenSSL 扩展,并定期轮换密钥。
安全不是功能,而是贯穿生命周期的设计习惯。从今天起,让每一行敏感数据,都拥有自己的数字盾牌。
```