Blowfish密钥不可轮换,其哈希值单向不可逆;真正可控的是cost参数和每次自动生成的随机盐,应通过password_needs_rehash()定期升级哈希强度,而非手动轮换密钥。
Blowfish密钥不能“轮换”——它根本不是设计来复用的
blowfish 本身没有密钥轮换机制,crypt() 函数生成的哈希值(如 $2y$10$...)是单向、不可逆、带盐的固定输出。所谓“当天刷新密钥”,实际是误把「加密密钥」和「哈希盐/成本因子」混为一谈。
真正可控的是:password_hash() 的 cost 参数(影响计算强度),以及每次调用时自动生成的新盐——后者才是安全关键,不需要你手动轮换。
- Blowfish 在 PHP 中通过
password_hash($pass, PASSWORD_BCRYPT)使用,底层调用的是 OpenBSD 的 bcrypt 实现,盐已硬编码进哈希字符串里(比如$2y$10$abc...$...中的abc...部分) - 试图“每天换一个主密钥”去重算旧密码哈希?不行——旧哈希无法解密,也没法用新密钥重新加密;用户登录时仍得用原哈希比对
- 真要提升当天安全性?只能强制所有用户改密(不现实),或升级
cost值后等下次修改密码时自动生效
想让密码哈希“随日期变化”?别硬改算法,改策略
如果你的需求本质是:「今天注册的用户用今天的盐规则,明天的用另一套」,那这不是密码学问题,是业务策略问题——而 bcrypt 的盐必须随机,不能按日期构造。
可行替代思路只有两个:
- 在应用层加一层封装:比如用
hash_hmac('sha256', $user_id, date('Y-m-d'))生成当日动态 salt,再拼到明文密码前,最后喂给password_hash()——但注意:这不增强安全性,反而可能削弱(盐不再完全随机),且必须持久化存储该 salt,否则无法验证 - 更合理做法:放弃日期绑定,专注确保
password_hash()调用时没禁用默认盐(即不传['salt' => ...]),并定期检查password_needs_rehash($hash, PASSWORD_BCRYPT, ['cost' => 12]),在用户下次登录时平滑升级哈希强度 - 若必须审计级可追溯性,记录每条哈希生成时间戳到数据库字段,而非编码进哈希本身
常见错误:手写 Blowfish 加密脚本 + 定时任务 = 白忙活
很多人写个 shell 脚本,用 openssl enc -bf-cbc 或 Python 的 pycryptodome + Blowfish 模块,每天生成新密钥、加密配置文件——但这和密码存储完全无关,属于对称加密范畴,容易踩一堆坑:
-
openssl enc -bf-cbc默认不加盐,且 IV 若固定(如全零),相同明文永远产出相同密文,极易被重放或模式分析 - 密钥存在哪?写进 crontab?藏在脚本里?都等于没加密
- 没做完整性校验(比如没配
-md sha256或 HMAC),密文被篡改也无法发现 - Blowfish 已被更安全的 AES-CBC/GCM 取代,PHP 8.1+ 更是直接弃用
mcrypt扩展
如果真要定时更新加密凭据,请用 libsodium:sodium_crypto_secretbox()(需随机 nonce)或 sodium_crypto_box()(非对称),密钥走系统密钥管理服务(如 HashiCorp Vault),而不是自己写 cron 刷密钥。
真正该做的:盯住 password\_needs\_rehash() 和 cost 升级节奏
PHP 官方推荐的密码演进路径,就藏在这一行函数里——它才是你脚本里唯一值得定时跑的逻辑:
if (password_needs_rehash($stored_hash, PASSWORD_BCRYPT, ['cost' => 12])) {
$new_hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
// 更新数据库
}
这意味着:
- 不要写“每日轮换密钥”的脚本,改成“每日扫描 100 条旧哈希,检查是否需要升级”
- cost 值不是越高越好:
cost=12在现代 CPU 上约耗时 200ms,cost=14就到 800ms,可能拖慢登录接口 - 升级后旧哈希仍能验证(bcrypt 向下兼容),无需用户重输密码
- 数据库字段必须支持至少 255 字符(
password_hash()输出长度可达 60+)
复杂点在于:你需要在用户登录、改密、重置等所有涉及密码验证的路径里,统一插入这段 rehash 检查——漏掉任何一处,那些哈希就永远卡在低强度上。










