本文目录导读:

- 核心原则
- 场景一:密码存储(不可逆,绝对不要用加密)
- 场景二:数据对称加密(AES-256-GCM,可逆)
- 场景三:现代加密:使用 libsodium (Sodium)
- 场景四:非对称加密(公钥/私钥)
- 总结:我应该用哪个?
- 永远要避开的坑
在 PHP 中进行加解密的最佳实践,需要根据你的具体需求(是保护静态数据、传输数据还是做身份验证)来选择不同的方案,以下是经过安全验证的推荐做法:
核心原则
- 不要自己写加密算法:使用 PHP 内置的
openssl或sodium扩展。 - 永远不要使用
mcrypt:该扩展已废弃且不再安全。 - 区分加密(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_encrypt 和 openssl_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_seal 或 crypto_aead |
或使用 HTTPS + JWT |
| 前端→后端加密 | Web Crypto API (前端) + PHP Sodium (后端) | 使用非对称加密或混合加密 |
| 防止CSRF/伪造 | 使用随机令牌 + HMAC 或 JWT | 不直接属于加解密,是签名 |
永远要避开的坑
- EBC 模式:只加密,不认证,易被重放和篡改。
- 使用
sha1/md5做密码哈希:秒破。 - 把
key和iv写死在代码里。 - 对密文不做完整性校验(如不使用 GCM / CCM)。
- 使用
serialize()或json_encode()加密:结构信息泄露。 - 使用自定义 xxtea、异或 等“花式”算法。
如果你能提供更具体的场景(加密用户手机号”、“与第三方接口加密通信”),我可以给出更精确的代码示例。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播