本文目录导读:

在 PHP 中加密存储认证信息(如密码、API密钥、令牌等)是安全实践的核心。最核心的原则是:密码用哈希(不可逆),密钥用加密(可逆)。
以下是针对不同类型的认证信息的加密存储方案详解:
用户密码(必须用哈希,不能用加密)
原则:密码绝对不能加密存储(因为加密可逆,一旦密钥泄露,所有密码暴露),必须使用单向哈希函数,并且要加盐。
推荐方案(现在最安全):
使用 PHP 原生的 password_hash() 和 password_verify(),它们自动处理盐值,并使用 bcrypt 或 Argon2 算法。
<?php
// 注册时:存储哈希
$hashedPassword = password_hash($_POST['password'], PASSWORD_DEFAULT);
// 存入数据库时,存 $hashedPassword
// 登录时:验证
$user = // 从数据库取出该用户的哈希值
if (password_verify($_POST['password'], $user['password_hash'])) {
echo "登录成功";
} else {
echo "密码错误";
}
// 如果需要重新计算哈希(例如升级算法)
if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT)) {
$newHash = password_hash($_POST['password'], PASSWORD_DEFAULT);
// 更新数据库中的哈希
}
?>
兜底方案(老系统迁移):
如果你用 crypt() 或 md5(),请立即迁移,迁移方案是:登录时先验证旧哈希,如果通过则替换为新哈希。
API 密钥/Token(必须用加密)
场景:第三方应用需要读取用户数据,或系统需要存储外部服务的密钥(如 Stripe API Key),此时信息需要解密后使用,因此必须使用对称加密。
推荐方案(AES-256-GCM):
使用 PHP 7.2+ 的 sodium 扩展,或者 openssl_encrypt。优先使用 OpenSSL 的 AES-256-GCM,因为它自带认证标签(防止篡改)。
<?php
// 定义密钥(存放到环境变量或配置文件,切勿硬编码在代码中)
// 生成方法:bin2hex(random_bytes(32)) => 64位十六进制字符串
$encryptionKey = getenv('APP_SECRET_KEY'); // 使用 .env 管理
/**
* 加密函数
*/
function encryptData(string $plainText, string $key): string {
// 生成初始化向量(IV)
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-gcm'));
// 加密
$cipherText = openssl_encrypt(
$plainText,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$iv,
$tag, // 认证标签(输出参数)
'',
16 // 标签长度(推荐16字节=128位)
);
// 将 IV 和 Tag 附加到密文上,方便解密
return base64_encode($iv . $tag . $cipherText);
}
/**
* 解密函数
*/
function decryptData(string $encryptedData, string $key): ?string {
$decoded = base64_decode($encryptedData);
// 提取 IV(前16字节)、Tag(中间16字节)、密文(剩余部分)
$ivLength = openssl_cipher_iv_length('aes-256-gcm');
$iv = substr($decoded, 0, $ivLength);
$tag = substr($decoded, $ivLength, 16);
$cipherText = substr($decoded, $ivLength + 16);
$plainText = openssl_decrypt(
$cipherText,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$iv,
$tag
);
return $plainText === false ? null : $plainText;
}
// 使用示例
$apiKey = "sk_live_123456789";
$encrypted = encryptData($apiKey, $encryptionKey);
// 存储 $encrypted 到数据库
// 使用
$decrypted = decryptData($encrypted, $encryptionKey);
echo $decrypted; // 输出原始API Key
?>
会话(Session)与 Cookie 安全
这是认证信息的另一个载体,核心原则是 “服务端存储,客户端只给ID”。
最佳实践:
- Session 文件:不要将敏感信息直接放在
$_SESSION中明文保存,如果服务器被读取了,也照样泄露,建议只存用户id,其他信息每次从数据库查。 - Session ID:使用 PHP 默认的安全设置:
ini_set('session.use_strict_mode', '1'); // 严格模式 ini_set('session.use_only_cookies', '1'); // 禁止URL传递Session ID session_set_cookie_params([ 'httponly' => true, // 禁止JavaScript读取 'secure' => true, // 仅HTTPS传输 'samesite' => 'Lax' // 防止CSRF ]); session_start();
数据库中的其他敏感字段(如身份证号、手机号)
如果必须存储可逆的敏感个人信息(例如客服需要查看),单独拆一张表,并使用 PHP openssl_encrypt 加密,但解密逻辑严格限制权限。
常见致命错误(务必避免)
- 使用
md5()/sha1()存储密码:这些是快速哈希,极其容易被彩虹表或暴力破解。 - 硬编码密钥:密钥必须放在
.env或环境变量中,绝不能提交到 Git 仓库,即使代码泄露,密钥也不泄露。 - 加密与哈希混淆:记住上面的原则,密码哈希,敏感数据加密。
- 没有使用 HTTPS:如果传输层是 HTTP,无论存储多安全,中间人攻击都能直接截获明文。
选择策略
| 数据类型 | 处理方式 | 算法/函数 | 备注 |
|---|---|---|---|
| 用户密码 | 单向哈希 | password_hash() |
绝对不要加密 |
| OAuth Token | 对称加密 | openssl_encrypt() (AES-256-GCM) |
需要时可解密 |
| 第三方 API Key | 对称加密 | openssl_encrypt() (AES-256-GCM) |
需要时可解密 |
| Session ID | 由框架生成 | session_start() |
服务端存储,客户端只存ID |
| 敏感个人信息 | 对称加密 | openssl_encrypt() |
谨慎解密,操作留痕 |
补充一点: 如果使用 Laravel、Symfony 等框架,这些机制已经封装好,Laravel 的 Hash facade 和 Crypt facade 分别对应 “密码哈希” 和 “加密/解密”,直接使用即可,底层已经处理好了这些安全细节。