1. 安全性对比
| 特性 | password_verify(原生函数) | 自定义加密(如 md5/sha1) |
|---|---|---|
| 抗彩虹表 | ✅ 自动加随机盐(password_hash 生成的哈希中已含盐,无需额外存储)。 | ❌ 若不手工加盐,直接用 md5($pass) 极易被彩虹表破解。 |
| 抗暴力破解 | ✅ 使用 bcrypt(默认)、Argon2i 或 Argon2id 算法,这些是慢哈希(可调 cost 参数,例如 10 表示 2^10 轮迭代,约 0.1-0.3 秒),大幅增加攻击成本。 | ❌ md5/sha1 是快速哈希,GPU 每秒可计算数十亿次,暴力破解极快。即使手工加盐,攻击者仍可针对常见密码字典快速尝试。 |
| 哈希是否可升级 | ✅ 当硬件性能提升或算法有漏洞时,password_needs_rehash() 可轻松检测并升级哈希算法(例如从 bcrypt 换到 Argon2)。 | ❌ 自定义方案一旦写死,换算法需要处理大量历史哈希,几乎无法平滑过渡。 |
| 边信道攻击 | ✅ 底层实现(如 crypt)经过严格测试,避免时序攻击等。 | ❌ 手写的字符串比较(if(hash===md5(hash === md5(hash===md5(pass)))可能存在时序漏洞,导致密码枚举。 |
2. 易用性与兼容性
| 方面 | password_hash` | 自定义加密 |
|---|---|---|
| 存储长度 | password_verify password_verify 返回固定长度字符串(bcrypt 为 60 字符,Argon2 更长),直接存 VARCHAR(255) 即可。 | md5 是 32 字符,sha1 是 40 字符。看似短,但无随机盐时安全性极低。 |
| 验证代码 | 简单:if (password_verify(input, stored_hash)),一行搞定。 | 需要自行处理盐的提取、拼接、重算、比较,容易出错。 |
| 盐的管理 | 自动生成随机盐并嵌入哈希中,无需单独字段。 | 手工加盐时需额外存储盐字段,或使用固定盐(固定盐无意义)。 |
| 错误率 | 极低,社区广泛使用。 | 容易在编码细节出错(如忘记比较前做 trim、大小写敏感问题等)。 |
3. 性能考量
password_verify 较慢:这正是它的优势(抗暴力破解)。对于正常登录请求(每秒几十到几百次),现代服务器完全能承受(0.2 秒 vs 0.001 秒)。对于高频 API 调用,可考虑将认证结果短期缓存(如 JWT),而不应牺牲安全性。
自定义 md5 极快:但快是缺点,攻击者可轻松高速枚举密码。
如果你认为"慢哈希影响用户体验",可调整 cost 参数(例如 bcrypt cost=10 平衡)。或者对高负载系统采用多因子认证(验证码、设备锁),而非回到快速哈希。## 现实案例
4. 现实案例
错误示例:某知名
CMS 早期使用 md5(password.password.password.salt),盐存储在另一个字段。2013 年后大量数据库泄露,攻击者用 GPU 几小时就破解出大量弱密码。
正确示例:WordPress 从 2.5 开始使用 phpass(基于 bcrypt),现在已迁移到 password_hash;Laravel、Symfony 等框架默认使用 password_hash。
其中用户注册/登录时使用了 password_hash 和 password_verify,
php
//加密,用逻辑代码存入数据库
password_hash($password, PASSWORD_DEFAULT);
//验证
password_verify($password, $user['password_hash'])
4. 使用说明
- 创建数据库并执行建表 SQL。
- 修改
config.php中的数据库账号密码。 - 将所有文件放到网站目录下(如 Apache/Nginx + PHP 环境)。
- 访问
register.php注册一个用户,密码会被password_hash加密存入数据库。 - 登录后即可发布文章,首页会显示所有文章。
关键点总结
- 密码存储 :
password_hash($password, PASSWORD_DEFAULT)自动加盐并生成安全哈希。 - 密码验证 :
password_verify($input, $storedHash)安全比对。 - 防止 SQL 注入:全部使用 PDO 预处理语句。
- XSS 防护 :输出时使用
htmlspecialchars()。