在PHP 中, password_needs_rehash函數用於判斷一個密碼哈希值是否需要使用當前指定的算法和選項重新哈希。當系統的哈希策略發生變化(例如從bcrypt升級為argon2id ,或者更改了成本因子)時,它可以幫助我們在用戶登錄時悄無聲息地將舊的哈希升級到新的標準。然而,開發者在使用這個函數時,可能會遇到“看起來沒有效果”的情況。下面我們分析一下為什麼password_needs_rehash可能不生效,並重點說明其中最常見的原因:哈希算法設置錯誤。
$hash = password_hash('mypassword', PASSWORD_DEFAULT);
$options = ['cost' => 12];
if (password_needs_rehash($hash, PASSWORD_DEFAULT, $options)) {
$hash = password_hash('mypassword', PASSWORD_DEFAULT, $options);
}
這個邏輯意味著:如果原先生成的哈希不符合當前算法或參數,就重新哈希。
password_needs_rehash的核心依據是你傳進去的算法和參數。如果你錯誤地設置了這些參數,就會導致它無法判斷出差異,從而錯誤地返回false ,以為無需更新。
許多開發者以為只要使用PASSWORD_DEFAULT ,PHP 就會自動幫你升級算法。例如:
$oldHash = password_hash('mypassword', PASSWORD_BCRYPT); // 顯式使用 bcrypt
然後後來你改成:
$options = ['cost' => 12];
$needsRehash = password_needs_rehash($oldHash, PASSWORD_DEFAULT, $options);
問題來了: PASSWORD_DEFAULT當前(截至PHP 8.3)仍然是PASSWORD_BCRYPT ,那麼password_needs_rehash並不會發現算法不一致,因為它認為算法仍是bcrypt。此時你可能期望它返回true ,但它返回了false 。
很多人會傳遞成本參數(如cost ),但如果原始哈希值中已經使用了相同的成本,那麼函數也不會判定為“需要重哈希”。即使你手動寫了['cost' => 10] ,而原先也是10,這種情況下函數會返回false 。
$hash = password_hash('mypassword', PASSWORD_BCRYPT, ['cost' => 10]);
$needsRehash = password_needs_rehash($hash, PASSWORD_BCRYPT, ['cost' => 10]);
// 結果仍是 false,因為沒變化
為了確保你的哈希策略發生變化時能被正確感知,以下做法是推薦的:
明確設置新的算法(如從PASSWORD_BCRYPT升級為PASSWORD_ARGON2ID );
顯式更新成本參數,例如將cost從10 提升到12;
如果可能,將原始的哈希策略作為配置項維護,以便統一升級。
$hash = password_hash('mypassword', PASSWORD_ARGON2ID);
$needsRehash = password_needs_rehash($hash, PASSWORD_ARGON2ID, [
'memory_cost' => 1<<17,
'time_cost' => 4,
'threads' => 2
]);
如果你懷疑password_needs_rehash沒有按預期工作,可以在調試過程中輸出哈希字符串進行檢查。哈希前綴通常表示使用的算法,例如:
$2y$表示bcrypt
$argon2id$表示argon2id
你可以通過觀察這些前綴來判斷是否真正使用了新的算法。
此外,也可以將當前的配置通過日誌記錄輸出,或者建立一個後台管理頁面用於檢查當前使用的哈希策略。
password_needs_rehash是一個非常實用的函數,能幫助我們平滑過渡密碼哈希策略,但它並不是“魔法”。它依賴於你提供的參數來判斷差異。如果設置錯誤,哪怕哈希已經過時,它也可能認為無需更新。因此,保持哈希策略的配置清晰、可控,是確保函數有效性的關鍵。
確保你的代碼能正確判斷何時需要重哈希,是保障密碼安全性進化的第一步。對於線上項目,建議將哈希策略作為集中配置,並定期評估升級計劃,確保安全策略不會停留在過去。