PHP 数据库敏感数据加密存储方案
在用户注册、支付、身份认证等场景中,密码、手机号、身份证号、银行卡号等属于敏感数据,绝不可明文存储于数据库。PHP 提供了成熟的安全工具链,本文介绍一种符合 OWASP 和 GDPR 原则的端到端加密实践方案。
✅ 核心原则
- 加密而非哈希:对需可逆读取的字段(如手机号)使用 AES-256-GCM 加密;
- 密钥分离:主密钥(KEK)存于环境变量或密钥管理服务(如 AWS KMS),不硬编码;
- 随机 IV + 认证标签:防止重放与篡改,确保完整性;
- 应用层加密:数据库仅存储密文,DBA 也无法直接读取明文。
🔐 实现示例(PHP 8.1+)
<?php
// config.php —— 密钥从环境变量安全加载
$encryptionKey = $_ENV['APP_ENCRYPTION_KEY'] ?? null;
if (!$encryptionKey || strlen($encryptionKey) !== 32) {
throw new RuntimeException('Invalid or missing encryption key (32-byte required)');
}
// 加密函数(AES-256-GCM)
function encrypt(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 decrypt(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';
$encryptedPhone = encrypt($phone, $encryptionKey);
echo "密文: $encryptedPhone\n"; // 存入数据库 users.phone_enc
$decryptedPhone = decrypt($encryptedPhone, $encryptionKey);
echo "解密: $decryptedPhone\n"; // 输出:13812345678
?>
⚠️ 安全提醒
切勿:使用 md5/sha1 加密敏感字段;将密钥写死在代码中;忽略 IV/Tag 的唯一性;在日志中打印密文或密钥。
建议:结合 ext-sodium(PHP 7.2+)使用 sodium_crypto_aead_xchacha20poly1305_ietf_encrypt() 获取更高安全性;定期轮换加密密钥;对加密字段添加数据库注释(如 /* ENCRYPTED: AES-256-GCM */)便于团队协作。
安全不是功能,而是设计起点。从今天起,让每一行敏感数据,都拥有自己的“数字保险箱”。
```