PHP 怎么PHP 加解密最佳实践

wen PHP项目 4

本文目录导读:

PHP 怎么PHP 加解密最佳实践

  1. 核心原则
  2. 场景一:密码存储(不可逆,绝对不要用加密)
  3. 场景二:数据对称加密(AES-256-GCM,可逆)
  4. 场景三:现代加密:使用 libsodium (Sodium)
  5. 场景四:非对称加密(公钥/私钥)
  6. 总结:我应该用哪个?
  7. 永远要避开的坑

在 PHP 中进行加解密的最佳实践,需要根据你的具体需求(是保护静态数据、传输数据还是做身份验证)来选择不同的方案,以下是经过安全验证的推荐做法:

核心原则

  1. 不要自己写加密算法:使用 PHP 内置的 opensslsodium 扩展。
  2. 永远不要使用 mcrypt:该扩展已废弃且不再安全。
  3. 区分加密(Encryption)与哈希(Hashing)
    • 哈希:用于密码存储、数据完整性校验(不可逆),最佳实践:password_hash()password_verify()
    • 加密:用于保护敏感数据(可逆,需要密钥解密)。

密码存储(不可逆,绝对不要用加密)

这是最常见的需求,永远不要存储明文密码,也不要用可逆加密来存储密码

最佳实践:使用 password_hash()password_verify()

<?php
// 注册时:生成密码哈希
$password = 'user_plain_password';
$hash = password_hash($password, PASSWORD_BCRYPT); // 或 PASSWORD_ARGON2ID
// 将 $hash 存入数据库
// 登录时:验证密码
$inputPassword = 'user_input_password';
$storedHash = '从数据库取出的哈希值';
if (password_verify($inputPassword, $storedHash)) {
    echo "密码正确!";
} else {
    echo "密码错误!";
}
// 需要重新哈希时(算法更新)
if (password_needs_rehash($storedHash, PASSWORD_ARGON2ID)) {
    $newHash = password_hash($inputPassword, PASSWORD_ARGON2ID);
    // 更新数据库中的哈希
}
?>

数据对称加密(AES-256-GCM,可逆)

适用于加密小段敏感数据(如身份证号、支付信息),推荐使用 AES-256-GCM(提供认证,防止篡改)。

最佳实践:使用 openssl_encryptopenssl_decrypt

<?php
/**
 * 安全加密函数
 * @param string $plaintext 明文
 * @param string $key 密钥(建议使用随机生成的二进制串,而不是直接写字符串)
 * @return string 经过 base64 编码的密文(包含 IV 和 认证标签)
 */
function safeEncrypt(string $plaintext, string $key): string {
    // 1. 生成随机初始化向量(IV),AES-256-GCM 的 IV 长度是 12 字节
    $iv = openssl_random_pseudo_bytes(12);
    // 2. 加密并生成认证标签
    $tag = ''; // 用于存储认证标签
    $ciphertext = openssl_encrypt(
        $plaintext,
        'aes-256-gcm',
        $key,
        OPENSSL_RAW_DATA, // 返回原始二进制数据
        $iv,
        $tag,
        '', // 附加认证数据 (AAD),可选,可以加上用户ID等增加安全性
        16  // 认证标签长度(16字节)
    );
    // 3. 组合 IV + 密文 + 认证标签,并 Base64 编码
    return base64_encode($iv . $ciphertext . $tag);
}
/**
 * 安全解密函数
 * @param string $encodedData Base64 编码的密文
 * @param string $key 密钥(必须与加密时相同)
 * @return string|false 解密后的明文,失败返回 false
 */
function safeDecrypt(string $encodedData, string $key): string|false {
    // 1. Base64 解码
    $data = base64_decode($encodedData);
    // 2. 提取 IV (前12字节),密文,认证标签(最后16字节)
    $ivLength = 12;
    $tagLength = 16;
    // 检查数据长度是否合理
    if (strlen($data) < $ivLength + $tagLength) {
        return false;
    }
    $iv = substr($data, 0, $ivLength);
    $tag = substr($data, -$tagLength);
    $ciphertext = substr($data, $ivLength, -$tagLength);
    // 3. 解密并验证认证标签
    $plaintext = openssl_decrypt(
        $ciphertext,
        'aes-256-gcm',
        $key,
        OPENSSL_RAW_DATA,
        $iv,
        $tag
    );
    return $plaintext;
}
// 使用示例
$key = base64_decode('你的32字节Base64编码密钥'); // 必须长度为32字节(256位)
$plaintext = '这是需要加密的敏感信息';
$encrypted = safeEncrypt($plaintext, $key);
echo "加密后: " . $encrypted . PHP_EOL;
$decrypted = safeDecrypt($encrypted, $key);
echo "解密后: " . $decrypted . PHP_EOL;
?>

关键点

  • 密钥管理:密钥是安全核心,不要写在代码里,使用环境变量、密钥管理服务(华为云KMS、AWS KMS)或安全配置文件(权限严格受限)。
  • IV:每次加密都必须使用随机 IV(相同密钥 + 不同 IV = 不同密文)。
  • 认证标签:GCM 模式能检测密文是否被篡改。

现代加密:使用 libsodium (Sodium)

PHP 7.2+ 内置了 Sodium 扩展,是当前最推荐、最安全的现代加密库(由Daniel J. Bernstein设计)。

简单加密/解密(对称加密 - XChaCha20-Poly1305)

<?php
// 加密
$plaintext = '敏感数据';
$key = sodium_crypto_aead_xchacha20poly1305_ietf_keygen(); // 生成密钥
// 通常你需要一个 nonce(一次性随机数),建议使用 randombytes
$nonce = random_bytes(SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
$ciphertext = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(
    $plaintext,
    $ad = '', // 附加数据(可选)
    $nonce,
    $key
);
// 存储时:可以将 nonce 和密文一起存储
$stored = base64_encode($nonce . $ciphertext);
// 解密
$decoded = base64_decode($stored);
$nonce = substr($decoded, 0, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
$ciphertext = substr($decoded, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
$decrypted = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt(
    $ciphertext,
    $ad = '',
    $nonce,
    $key
);
?>

简单的密钥封装(Secret Key - 用于API令牌等)

Sodium 提供了一个非常方便的对称加密函数 crypto_secretbox,但推荐使用上面的 AEAD 模式,因为它更直观。


非对称加密(公钥/私钥)

适用于数据交换,A 用 B 的公钥加密,只有 B 能用私钥解密。

<?php
// 1. 生成密钥对
$keyPair = sodium_crypto_box_keypair();
$secretKey = sodium_crypto_box_secretkey($keyPair);
$publicKey = sodium_crypto_box_publickey($keyPair);
// 2. 加密(需要接收者的公钥)
$message = '秘密消息';
$anonymousMessage = sodium_crypto_box_seal($message, $publicKey); 
// 使用接收者公钥加密,接收者用私钥解密
// 3. 解密(使用自己的私钥)
$decryptedMessage = sodium_crypto_box_seal_open($anonymousMessage, $keyPair);
?>

应用场景

  • 前端用公钥加密信用卡信息,后端用私钥解密。
  • 通信双方交换会话密钥(混合加密)。

我应该用哪个?

需求场景 推荐方案 备注
存储密码 password_hash() + password_verify() 永远不要尝试解密密码
保护数据库敏感字段 openssl_encrypt (AES-256-GCM) 或 Sodium crypto_aead 妥善保管密钥(环境变量/KMS)
安全传输数据、API通信 Sodium crypto_box_sealcrypto_aead 或使用 HTTPS + JWT
前端→后端加密 Web Crypto API (前端) + PHP Sodium (后端) 使用非对称加密或混合加密
防止CSRF/伪造 使用随机令牌 + HMAC 或 JWT 不直接属于加解密,是签名

永远要避开的坑

  1. EBC 模式:只加密,不认证,易被重放和篡改。
  2. 使用 sha1 / md5 做密码哈希:秒破。
  3. keyiv 写死在代码里
  4. 对密文不做完整性校验(如不使用 GCM / CCM)。
  5. 使用 serialize()json_encode() 加密:结构信息泄露。
  6. 使用自定义 xxtea、异或 等“花式”算法

如果你能提供更具体的场景(加密用户手机号”、“与第三方接口加密通信”),我可以给出更精确的代码示例。

抱歉,评论功能暂时关闭!