本文目录导读:

- 核心结论(选型总结)
- 绝对首选:AES-256-GCM(推荐)
- 次选方案:AES-256-CBC + HMAC(兼容旧环境)
- 密钥管理(比算法更重要)
- 防坑指南(注意事项)
- 终极替代方案:现代加密库(libsodium)
- 最终决策树
在 PHP 中进行对称加密,首选方案是 openssl_encrypt / openssl_decrypt,结合 AES-256-GCM 模式。
以下是详细的选型指南、代码示例及安全注意事项。
核心结论(选型总结)
| 算法/模式 | 推荐度 | 说明 |
|---|---|---|
| AES-256-GCM | ⭐⭐⭐⭐⭐ | 首选,提供认证加密(AEAD),防止密文被篡改,性能优秀,PHP 7.1+ 原生支持。 |
| AES-256-CBC + HMAC-SHA256 | ⭐⭐⭐⭐ | 次选,若环境不支持 GCM(如老版本),用于在不支持 AEAD 时实现"加密+签名"。 |
| AES-256-CTR | ⭐⭐⭐ | 流模式,无填充,但必须配合 HMAC,否则不安全。 |
mcrypt |
❌ 弃用 | PHP 7.1 已废弃,7.2 移除,严禁使用。 |
Sodium |
⭐⭐⭐⭐⭐ | PHP 7.2+ 内置,现代加密库(libsodium),但 API 相对底层。 |
绝对首选:AES-256-GCM(推荐)
GCM 模式在加密的同时自动生成认证标签(Tag),解密时若不提供正确的 TAG 会直接失败,防止"填充预言攻击"。
示例代码(PHP 7.1+)
<?php
/**
* AES-256-GCM 加密
* @param string $plaintext 明文
* @param string $key 密钥(必须是 32 字节,建议用 hash('sha256', $password, true) 派生)
* @return array [密文, IV, TAG]
*/
function aes_gcm_encrypt(string $plaintext, string $key): array {
// 生成随机 IV(推荐 12 字节,GCM 标准长度)
$iv = random_bytes(12);
// 加密并生成认证标签(TAG)
$ciphertext = openssl_encrypt(
$plaintext,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA, // 不进行 Base64 编码,直接返回原始二进制
$iv,
$tag // 16 字节的认证标签
);
return [$ciphertext, $iv, $tag];
}
/**
* AES-256-GCM 解密
* @param string $ciphertext 密文
* @param string $iv IV
* @param string $tag 认证标签
* @param string $key 密钥
* @return string|false 明文,验证失败返回 false
*/
function aes_gcm_decrypt(string $ciphertext, string $iv, string $tag, string $key) {
$plaintext = openssl_decrypt(
$ciphertext,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$iv,
$tag
);
return $plaintext;
}
// --- 使用示例 ---
$key = hash('sha256', '很长的密码/密钥', true); // 确保 32 字节密钥
[$ciphertext, $iv, $tag] = aes_gcm_encrypt('这是要加密的机密数据', $key);
// 存储时建议将三者合并:base64_encode($iv . $tag . $ciphertext)
$combined = base64_encode($iv . $tag . $ciphertext);
echo "加密后: " . $combined . PHP_EOL;
// 解密
$decoded = base64_decode($combined);
$iv = substr($decoded, 0, 12);
$tag = substr($decoded, 12, 16);
$ciphertext = substr($decoded, 28);
$plaintext = aes_gcm_decrypt($ciphertext, $iv, $tag, $key);
var_dump($plaintext); // 输出明文
?>
次选方案:AES-256-CBC + HMAC(兼容旧环境)
如果目标环境 PHP < 7.1(几乎绝迹),或需要兼容特殊库,使用 CBC 模式,但必须对密文进行 HMAC 完整性校验(加密后再签名)。
关键点:先加密(CBC),再计算 MAC(HMAC);解密时先验证 MAC,再解密。
<?php
function cbc_encrypt($plaintext, $key) {
$iv = random_bytes(16); // CBC 需要 16 字节 IV
$ciphertext_raw = openssl_encrypt(
$plaintext,
'aes-256-cbc',
$key,
OPENSSL_RAW_DATA,
$iv
);
// 生成 HMAC(签名密文 + IV)
$hmac = hash_hmac('sha256', $iv . $ciphertext_raw, $key, true);
// 合并并返回
return base64_encode($iv . $hmac . $ciphertext_raw);
}
function cbc_decrypt($data, $key) {
$decoded = base64_decode($data);
$iv = substr($decoded, 0, 16);
$hmac = substr($decoded, 16, 32);
$ciphertext = substr($decoded, 48);
// 验证 HMAC(防止篡改)
$calc_hmac = hash_hmac('sha256', $iv . $ciphertext, $key, true);
if (!hash_equals($hmac, $calc_hmac)) {
return false; // 被篡改
}
return openssl_decrypt($ciphertext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
}
?>
密钥管理(比算法更重要)
无论选哪种算法,密钥安全是第一位的:
| 要点 | 具体建议 |
|---|---|
| 不要硬编码 | 不要将密钥写在代码里,使用环境变量或专门的密钥管理服务(如 AWS KMS, Vault)。 |
| 使用二进制密钥 | 直接用 random_bytes(32) 生成,不要用字符串密码直接加密(因为熵不足),若要密码,用 password_hash 或 hash_pbkdf2 派生。 |
| 密钥轮换 | 设计支持密钥版本号(如 k1:密文),定期更换密钥。 |
| 存储 | 将密钥文件权限设为 600,并确保 Web 服务器用户可读。 |
防坑指南(注意事项)
- IV 必须随机且唯一:永远不要重复使用同一个 IV + 同一个密钥,否则可通过 XOR 破解,使用
random_bytes()生成。 - GCM 的 Tag 必须保存:解密时忘记传 Tag 或 Tag 错了,会导致解密失败或返回
false。 - 不要用 ECB 模式:ECB 会泄露明文结构,绝对禁用。
- 不要自己设计协议:除非你是密码学专家,否则使用标准库和标准模式。
- 编码问题:
openssl_encrypt第四个参数为OPENSSL_RAW_DATA时返回二进制,存入数据库或 URL 传输前必须base64_encode。 - PHP 版本:确保
openssl扩展已启用(php -m | grep openssl)。
终极替代方案:现代加密库(libsodium)
PHP 7.2+ 内置了 sodium 扩展,它提供更安全的 crypto_secretbox(基于 XSalsa20-Poly1305),API 更简单,且完全避免误用。
<?php
// libsodium (PHP 7.2+)
$key = sodium_crypto_secretbox_keygen(); // 生成 32 字节密钥
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = sodium_crypto_secretbox('机密数据', $nonce, $key);
// 解密
$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
如果你的 PHP 版本 ≥ 7.2,且不想处理 IV/TAG 拼接的细节,libsodium 是比 OpenSSL 更安全、更易用的选择。
最终决策树
你的 PHP 版本?
├── ≥ 8.0 → 如果安装 sodium 扩展 → 用 libsodium
│ → 否则用 OpenSSL AES-256-GCM ✅
├── 7.2 - 7.4 → 优先用 sodium,或 AES-256-GCM
├── 7.1 → AES-256-GCM(支持)
└── < 7.1 → 用 AES-256-CBC + HMAC(或升级 PHP,不建议在生产使用旧版)
一句话总结:用 openssl_encrypt + aes-256-gcm,密钥用 random_bytes(32) 生成,IV 随机,Tag 妥善保存,一切走标准 API。