如何基于密码在数据库存储加密文本并查询解密?
解决加密文本存储与解密还原的问题
哦,我太懂你现在的困惑了!你踩了一个非常常见的坑:把哈希算法和加密算法搞混了。password_hash和hash_hmac都是不可逆的哈希工具——它们的唯一作用是验证「输入的内容和原内容是否一致」,根本没法还原出原始文本。要实现「加密存库、密码验证后解密还原」的需求,你得用对称加密算法,比如AES-256-GCM(这是当前安全标准级别的选择)。
核心思路
- 用用户提供的密码+固定salt生成一个安全的加密密钥(结合pepper增强安全性)
- 用这个密钥对原始文本进行对称加密,把加密后的内容、初始化向量(IV)、校验标签一起存入数据库
- 查询时,用用户输入的密码重新生成密钥,再结合存库的IV和标签解密出原始文本
完整PHP代码实现
<?php // 建议把这些敏感配置放到环境变量里,不要硬编码在代码中 $salt = 'c0d4#'; $pepper = 'nsa-cia-fbi'; $cipher = 'aes-256-gcm'; // 安全的加密模式,自带完整性校验 /** * 从密码生成加密密钥 */ function generateEncryptionKey(string $password, string $salt, string $pepper): string { // 把pepper和密码拼接,再用PBKDF2生成密钥(比单纯hash更安全) $passwordWithPepper = $password . $pepper; // 生成32字节的密钥(对应AES-256) return hash_pbkdf2('sha256', $passwordWithPepper, $salt, 100000, 32, true); } /** * 加密文本 */ function encryptText(string $plaintext, string $key, string $cipher): array { // 生成随机初始化向量(IV),每个加密都要不同 $ivLength = openssl_cipher_iv_length($cipher); $iv = openssl_random_pseudo_bytes($ivLength); // 加密,同时生成GCM模式需要的校验标签 $ciphertext = openssl_encrypt($plaintext, $cipher, $key, OPENSSL_RAW_DATA, $iv, $tag); // 返回需要存储的所有数据:加密文本、IV、标签(都转base64方便存库) return [ 'ciphertext' => base64_encode($ciphertext), 'iv' => base64_encode($iv), 'tag' => base64_encode($tag) ]; } /** * 解密文本 */ function decryptText(string $ciphertext, string $iv, string $tag, string $key, string $cipher): ?string { // 把base64编码的内容转回原始字节 $ciphertextRaw = base64_decode($ciphertext); $ivRaw = base64_decode($iv); $tagRaw = base64_decode($tag); // 解密并校验标签(确保内容没被篡改) $plaintext = openssl_decrypt($ciphertextRaw, $cipher, $key, OPENSSL_RAW_DATA, $ivRaw, $tagRaw); return $plaintext !== false ? $plaintext : null; } // ------------------- 示例:加密并存储到数据库 ------------------- $userPassword = 'your-user-password'; // 用户输入的密码 $secretText = '这是需要加密存储的秘密文本'; // 原始秘密内容 // 生成密钥 $encryptionKey = generateEncryptionKey($userPassword, $salt, $pepper); // 加密文本 $encryptedData = encryptText($secretText, $encryptionKey, $cipher); // 这里模拟把encryptedData存入数据库(比如存到一个表的ciphertext、iv、tag字段) // $pdo->prepare("INSERT INTO secret_data (ciphertext, iv, tag) VALUES (?, ?, ?)")->execute([$encryptedData['ciphertext'], $encryptedData['iv'], $encryptedData['tag']]); // ------------------- 示例:查询并解密 ------------------- // 假设从数据库取出存储的数据 $storedCiphertext = $encryptedData['ciphertext']; $storedIv = $encryptedData['iv']; $storedTag = $encryptedData['tag']; // 用户查询时输入的密码 $inputPassword = 'your-user-password'; // 重新生成密钥(用输入的密码) $decryptionKey = generateEncryptionKey($inputPassword, $salt, $pepper); // 解密 $decryptedText = decryptText($storedCiphertext, $storedIv, $storedTag, $decryptionKey, $cipher); if ($decryptedText !== null) { echo "解密成功!原始文本:" . $decryptedText; } else { echo "密码错误或数据已被篡改!"; } ?>
关键注意事项
- 不要硬编码salt和pepper:把它们放到服务器的环境变量里,避免代码泄露导致密钥被破解
- IV必须随机且唯一:每次加密都要生成新的IV,不能重复使用,否则会降低安全性
- GCM标签必须一起存储:它用来验证加密内容是否被篡改,解密时必须提供
- 密码复杂度:提醒用户使用强密码,否则即使加密算法再安全,也可能被暴力破解
内容的提问来源于stack exchange,提问作者code-8
相关产品推荐
相关产品推荐

