本文目录导读:

在 PHP 中进行认证加密,最核心的原则是:永远不要自己实现加密算法,而是使用经过安全审计的官方扩展。
最推荐、最现代、最安全的方式是使用 Sodium 扩展(PHP 7.2+ 自带,现代标准)或 OpenSSL 扩展(传统标准)。
以下是 PHP 认证加密的三种主要实现方式,按推荐程度排序:
关键概念解释
- 认证加密:不仅加密数据(保证机密性),还使用 MAC(消息认证码)验证数据的完整性和真实性(确保数据未被篡改)。
- 你需要的参数:
plaintext:要加密的原文。password/key:你的密钥。永远不要将用户密码直接作为 key 使用,必须经过 KDF(密钥派生函数)处理。nonce/IV:每次加密必须唯一的随机数。不要重复使用。
推荐方式:使用 libsodium(Sodium)
这是 PHP 7.2 之后的内置扩展,代表了现代加密的标准,它简单、安全、难以误用。
函数:sodium_crypto_secretbox(对称加密)
<?php
// 1. 生成一个可靠的密钥(只需执行一次,存储起来)
// 需要从安全随机源生成
$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES); // 32字节
// 2. 加密
function encryptData(string $plaintext, string $key): string {
// 生成一个 Nonce(只使用一次的数字),必须是唯一的
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); // 24字节
// 加密并认证(返回的是原始二进制数据)
$ciphertext = sodium_crypto_secretbox($plaintext, $nonce, $key);
// 安全擦除内存中的明文
sodium_memzero($plaintext);
// 将 Nonce 与密文拼接传输(因为解密时需要 Nonce)
return base64_encode($nonce . $ciphertext);
}
// 3. 解密
function decryptData(string $encryptedBase64, string $key): string|false {
$decoded = base64_decode($encryptedBase64);
if ($decoded === false || strlen($decoded) < SODIUM_CRYPTO_SECRETBOX_NONCEBYTES) {
return false; // 数据被篡改或无效
}
// 拆分 Nonce 和密文
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
// 解密并验证,如果数据被篡改,立即返回 false(这是核心优势)
$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
if ($plaintext === false) {
// 认证失败!数据被篡改或密钥错误
return false;
}
sodium_memzero($ciphertext);
return $plaintext;
}
// ---- 使用示例 ----
$myMessage = "这是绝密信息: 密码是苹果";
$encrypted = encryptData($myMessage, $key);
echo "加密结果: " . $encrypted . PHP_EOL;
$decrypted = decryptData($encrypted, $key);
echo "解密结果: " . $decrypted . PHP_EOL;
// 试试篡改数据:
$tampered = substr_replace($encrypted, 'X', 5, 1); // 修改第6个字符
$result = decryptData($tampered, $key);
var_dump($result); // 输出: bool(false)
?>
优点:
- 防误用:自动使用了
XSalsa20加密(流加密)+Poly1305消息认证码(高效、安全)。 - 如果数据被篡改,
sodium_crypto_secretbox_open直接返回false,逻辑很干净。 - 官方库,性能好。
经典方案:使用 OpenSSL 实现认证加密
如果你必须支持 PHP 5.6+ 且无法使用 Sodium,可以使用 OpenSSL 的 AEAD 模式(AES-256-GCM)。
函数:openssl_encrypt 和 openssl_decrypt
<?php
// 1. 生成密钥
$key = random_bytes(32); // AES-256 需要 32 字节
// 2. 加密
function encryptOpenSSL(string $plaintext, string $key): string {
$cipher = "aes-256-gcm"; // 推荐使用 GCM 模式(认证加密)
$ivlen = openssl_cipher_iv_length($cipher);
$iv = random_bytes($ivlen); // GCM 的 IV 通常为 12 字节
// 标签变量会通过引用填充认证码
$tag = '';
$ciphertext = openssl_encrypt(
$plaintext,
$cipher,
$key,
OPENSSL_RAW_DATA,
$iv,
$tag,
"", // aad:额外的认证数据(可选)
16 // tag长度
);
// 检查是否失败
if ($ciphertext === false) {
throw new Exception('加密失败');
}
// 返回 base64 编码的 [IV + 密文 + 认证标签]
return base64_encode($iv . $ciphertext . $tag);
}
// 3. 解密
function decryptOpenSSL(string $encryptedBase64, string $key): string|false {
$cipher = "aes-256-gcm";
$ivlen = openssl_cipher_iv_length($cipher);
$taglen = 16; // 与加密时对应
$decoded = base64_decode($encryptedBase64);
// 提取 IV、密文、标签
$iv = substr($decoded, 0, $ivlen);
$ciphertext = substr($decoded, $ivlen, -$taglen);
$tag = substr($decoded, -$taglen);
$plaintext = openssl_decrypt(
$ciphertext,
$cipher,
$key,
OPENSSL_RAW_DATA,
$iv,
$tag
);
// 如果认证失败,返回 false
if ($plaintext === false) {
return false;
}
// 安全清理不是必须的,但养成习惯
sodium_memzero($ciphertext); // 注意:这里是假设你加载了 sodium,如果不支持,可以忽略这行
return $plaintext;
}
// ---- 使用示例 ----
$data = "我的秘密配置:API_KEY=xxx";
$enc = encryptOpenSSL($data, $key);
echo "加密: " . $enc . PHP_EOL;
$dec = decryptOpenSSL($enc, $key);
echo "解密: " . $dec . PHP_EOL;
// 篡改测试
$enc2 = substr_replace($enc, 'Z', 10, 1);
echo "篡改结果: ";
var_dump(decryptOpenSSL($enc2, $key)); // false
?>
注意:GCM 的 nonce(IV)和 tag 必须严格分离并存储,openssl_decrypt 会自行验证。
操作建议:如何处理“密码”输入
用户通常输入类似 my_password123 的密码,但不能直接用做加密密钥。
必须使用 KDF(密钥派生函数):
// 用户输入的密码必须是强密码或使用盐
$userPassword = "ilovePHP";
$salt = random_bytes(SODIUM_CRYPTO_PWHASH_SALTBYTES); // 16 字节,保存好
// 使用 Sodium 的 KDF 将密码转为 32 字节 key
$key = sodium_crypto_pwhash(
32, // 输出密钥长度
$userPassword,
$salt,
SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, // 计算强度
SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);
或者使用 hash_pbkdf2(OpenSSL 兼容):
$salt = random_bytes(16);
$key = hash_pbkdf2("sha256", $userPassword, $salt, 100000, 32, true);
应该用哪个?
| 场景 | 推荐方案 |
|---|---|
| PHP 7.2+ 标准环境 | libsodium — 最简单、最安全、最难出错。 |
| PHP 5.6+,需要兼容旧系统 | OpenSSL + AES-256-GCM — 标准通用,但需要自己管理 nonce 和 tag。 |
| 存储密码(哈希,不是加密) | password_hash + password_verify — 永远不要加密密码,要哈希。 |
最终结论:如果你刚开始写代码,学会 sodium_crypto_secretbox 对你的数据保护帮助最大,它能保证你在不仔细思考细节的情况下也不容易犯安全错误。