PHP 数据库敏感数据加密存储方案

```html PHP 数据库敏感数据加密存储方案

PHP 数据库敏感数据加密存储方案

在用户注册、支付、身份认证等场景中,密码、手机号、身份证号、银行卡号等属于敏感数据,绝不可明文存储于数据库。PHP 提供了成熟的安全工具链,本文介绍一种符合 OWASP 和 GDPR 原则的端到端加密实践方案。

✅ 核心原则

  • 加密而非哈希:对需可逆读取的字段(如手机号)使用 AES-256-GCM 加密;
  • 密钥分离:主密钥(KEK)存于环境变量或密钥管理服务(如 AWS KMS),不与代码/数据库共存;
  • 随机 IV + 认证标签:使用 GCM 模式保障机密性与完整性;
  • 字段级加密:仅加密敏感列(如 encrypted_phone),非整表加密。

🔐 示例:手机号 AES-256-GCM 加密类

<?php
class SecureDataEncryptor
{
    private string $key;

    public function __construct(string $encryptionKey)
    {
        // 确保密钥为 32 字节(AES-256)
        $this->key = hash('sha256', $encryptionKey, true);
    }

    public function encrypt(string $plaintext): string
    {
        $iv = random_bytes(12); // GCM 推荐 96-bit IV
        $ciphertext = openssl_encrypt(
            $plaintext,
            'aes-256-gcm',
            $this->key,
            OPENSSL_RAW_DATA,
            $iv,
            $tag,
            '',
            16 // 认证标签长度
        );

        if ($ciphertext === false) {
            throw new RuntimeException('Encryption failed: ' . openssl_error_string());
        }

        return base64_encode($iv . $tag . $ciphertext);
    }

    public function decrypt(string $encrypted): 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',
            $this->key,
            OPENSSL_RAW_DATA,
            $iv,
            $tag
        );

        if ($plaintext === false) {
            throw new RuntimeException('Decryption failed: ' . openssl_error_string());
        }

        return $plaintext;
    }
}

// 使用示例
$encryptor = new SecureDataEncryptor($_ENV['APP_ENCRYPTION_KEY'] ?? 'your-secure-32-byte-key-here');
$encryptedPhone = $encryptor->encrypt('13812345678');

// 存入数据库(PDO 示例)
$stmt = $pdo->prepare("INSERT INTO users (name, encrypted_phone) VALUES (?, ?)");
$stmt->execute(['张三', $encryptedPhone]);

// 查询时解密
$stmt = $pdo->prepare("SELECT encrypted_phone FROM users WHERE id = ?");
$stmt->execute([1]);
$encrypted = $stmt->fetchColumn();
$phone = $encryptor->decrypt($encrypted); // → "13812345678"
?>
⚠️ 重要提醒: • 密钥严禁硬编码!请通过 .env(配合 vaultdocker secrets)注入; • 加密字段建议使用 TEXT 类型(AES-GCM 后 Base64 编码约增长 33%); • 避免在 SQL 查询中直接解密(如 WHERE decrypt(encrypted_phone) = ?),会破坏索引且暴露风险。

安全无小事。每一次对敏感字段的加密,都是对用户信任的郑重承诺。从今天起,让明文成为历史 —— 用标准、可控、可审计的方式守护每一份数据。

```