「使用者的密碼,到底該怎麼存進資料庫才對?」——這是每個做服務的人都必然會撞上一次的問題。答案很明確。我們不涉及攻擊手法,只按順序講清楚安全的儲存方法。
為什麼明文、加密、裸雜湊都不行
我們按資料庫遲早會外洩這個前提來考慮。一旦外洩,不同的儲存方式造成的損失天差地別。
- 明文:外洩的瞬間所有密碼一覽無遺。再加上重複使用同一密碼,使用者在其他服務上的帳號也會被連鎖攻陷。最糟。
- 加密(可逆):有金鑰就能還原=金鑰一起外洩就等同於明文。密碼本就沒有讀回來的必要,因此可逆這件事本身毫無意義。
- 裸雜湊(MD5/SHA-256):快速雜湊讓攻擊者能高速嘗試海量候選。常見密碼會被彩虹表或暴力破解還原出來。
達到安全儲存的四個階段
把它理解成給弱方式一步步疊加對策的過程,會很快上手。
要點在於,鹽和慢雜湊的職責是不同的。鹽負責讓「預計算(彩虹表)和批次破解重複密碼」失效。慢雜湊負責把「暴力破解的速度壓到不現實的水準」。兩者齊備,才第一次稱得上安全(→ 什麼是雜湊化)。
實務:現在就該做的事
使用 Argon2id(或 bcrypt)
新專案把 Argon2id 作為首選。它還能要求消耗記憶體,因此對 GPU 暴力破解很有抵抗力。如果更看重經過充分驗證的實作,bcrypt 在實務中也完全夠用。兩者都直接使用標準函式庫的實作。
讓函式庫產生鹽
bcrypt/Argon2 會為每個使用者自動產生鹽,並把它一併塞進雜湊字串裡。你不需要自己另存一欄鹽。手動去拼 MD5(salt + password),正是不該犯的典型錯誤。
按執行環境設定成本(強度)
把 bcrypt 的成本係數、Argon2 的記憶體・迭代・並行度,設到正常登入在體感上察覺不到延遲的範圍內的最大值。伺服器越快,攻擊者也越快,所以要以年為週期複核並上調。
驗證用標準的比對函式
登入時,把已存雜湊與輸入用函式庫提供的比對函式進行比對(很多實作已經做成了對時序差有所防範的常數時間比較)。不要自己寫字串相等判斷。
弱雜湊用「登入時重新雜湊」來遷移
如果已經用 MD5/SHA-256 存了,就在使用者登入成功的那一刻用新方式重新雜湊並覆寫保存。對於尚未登入的部分,可以用 bcrypt 等對既有雜湊再做一次雜湊(雙重雜湊),在過渡期間先提升強度。
常見的錯誤做法 vs 正確的實作
常見錯誤
- 把密碼加密後儲存(金鑰一外洩就全完)
- 直接存
MD5(password)或SHA-256(password) - 所有使用者共用同一個鹽
- 自己手動拼
hash(salt + password)
正確的實作
- 用 Argon2id / bcrypt 做單向雜湊
- 鹽按每個使用者分配(由函式庫自動附加)
- 成本設在體感可接受範圍內的最大值,並定期上調
- 直接使用標準的雜湊/比對函式
雜湊、加密、編碼的差別
這三者常被混淆,但在能否還原這一點上完全不同。儲存密碼時,只有雜湊是可以接受的。
| 雜湊 | 加密 | 編碼(Base64 等) | |
|---|---|---|---|
| 能還原嗎? | 不能(單向) | 有金鑰就能 | 任何人都能 |
| 金鑰 | 無 | 需要 | 無 |
| 典型用途 | 密碼儲存、竄改偵測 | 保護資料或通訊的機密性 | 轉換資料格式 |
| 用於密碼儲存 | 使用(加鹽+慢雜湊) | 不要用 | 不要用(等同明文) |
建議設定(OWASP)
以下是 OWASP Password Storage Cheat Sheet 中的最低設定。請把它當作下限,再依你的伺服器往上調。
| 演算法 | 最低設定 | 使用時機 |
|---|---|---|
| Argon2id | 記憶體 19 MiB、迭代 2 次、並行度 1 | 新系統的首選 |
| scrypt | N=2^17(128 MiB)、r=8、p=1 | 無法使用 Argon2id 時 |
| bcrypt | 成本 10 以上。輸入上限 72 位元組 | 既有系統、有長期實績 |
| PBKDF2-HMAC-SHA256 | 迭代 600,000 次以上 | 需要符合 FIPS 140 時 |
也請確認你所用平台的內建功能。PHP 的 password_hash() 預設使用 bcrypt(PASSWORD_DEFAULT),PHP 8.4 把預設成本從 10 提高到了 12。Django 預設使用 PBKDF2;要切換的話,安裝 django[argon2],並把 Argon2 放在 PASSWORD_HASHERS 的第一位。使用者登入時,Django 會自動把用舊演算法儲存的密碼重新雜湊。
如何檢查自己的服務
通常從已儲存雜湊字串的開頭,就能看出採用的方式。請在你能管控的環境中看幾筆資料,不要把正式環境的資料複製到別處。
| 雜湊的樣子 | 方式 | 處置 |
|---|---|---|
$argon2id$v=19$m=…,t=…,p=…$ | Argon2id | 確認 m、t、p 達到 OWASP 的最低設定 |
$2y$12$… / $2b$10$… | bcrypt(數字是成本) | 低於 10 就調高 |
pbkdf2_sha256$1000000$… | PBKDF2(數字是迭代次數) | 確認至少 600,000 |
| 32 個十六進位字元 | 很可能是 MD5 | 用登入時重新雜湊來遷移 |
| 40 / 64 個十六進位字元 | 很可能是 SHA-1 / SHA-256 | 同上 |
| 和登入時輸入的字串一模一樣 | 明文 | 最優先遷移。並評估外洩時的影響 |
也請確認以下幾點:
- 輸入的密碼沒有被記錄在應用程式或 Web 伺服器的日誌、錯誤報告或分析工具中
- 如果使用 bcrypt,已經決定好如何處理超過 72 位元組的輸入
- 備份以及為測試而複製的資料庫,裡面也只有同樣形式的雜湊
真實案例:明文儲存擴大了損害
在 宅ふぁいる便事件(2019) 中,伺服器遭到未經授權的存取,外洩了約 480 萬筆資料,其中的登入密碼是以明文儲存的。如果當時是用加鹽的慢雜湊儲存,外洩的密碼被直接拿到其他服務上使用的風險會低得多。即使無法防止每一次入侵,密碼的儲存方式也會改變外洩造成的損害程度。
本站的觀點:密碼儲存請使用經過驗證的標準實作
密碼儲存不是一個可以彰顯獨創性的領域。使用經過廣泛驗證的 Argon2/bcrypt 標準實作,既最安全,維運也最省心。本站的立場是「在這裡不要搞創意發揮」。真正該投入精力的,是從根本上減少對密碼的依賴——把多因素驗證(MFA)做到位,以及未來向通行密鑰(無密碼)遷移。再強的雜湊,也兜不住弱密碼和重複使用。
延伸閱讀
- 術語:什麼是密碼雜湊化 / 什麼是鹽
- 入門:密碼的正確保管(使用者側) / 如何挑選密碼管理器
- 對策:如何挑選兩步驟驗證(MFA)(減少對密碼的依賴)
- 術語:什麼是通行密鑰(向無密碼遷移)
- 案例:宅ふぁいる便 資料外洩事件(2019)(密碼明文保存的真實案例)
出處
- OWASP「Password Storage Cheat Sheet」:cheatsheetseries.owasp.org
- NIST「SP 800-63B-4」(2025 年 8 月),密碼的儲存(鹽與雜湊):pages.nist.gov
- RFC 9106「Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications」:rfc-editor.org
- PHP 手冊「password_hash」:php.net
- Django 文件「Password management in Django」(5.2):docs.djangoproject.com