中文密碼打到第 24 個字,bcrypt 就不再讀了
兩個看起來完全不同的密碼可以是同一個密碼。截斷以位元組計算,所以它會切在一個字的中間——而 24 個中文字就已經用完全部額度。

使用者設了一段很長的密碼。幾個月後他把結尾打錯,照樣登入成功。沒有東西壞掉, 沒有人回報 bug,而他以為在保護帳號的那組密碼,比他自己選的那組短。
bcrypt 讀 72 個位元組,其餘丟掉
在 Node v25.9.0 上用 bcrypt 6.0.0 與 bcryptjs 3.0.3 實測:兩者都接受 任意長度的密碼,雜湊前 72 個位元組,把後面全部丟掉。不丟例外、不發警告, 回傳值裡也沒有任何跡象。
邊界很精確。拿一個 71 位元組的密碼加一個字元,它是不同的密碼;拿一個 72 位元組的密碼加一個字元,它是同一個密碼。
第二句話就是這篇文章的全部。
兩個不同的密碼,一個雜湊
下面有兩個輸入框。預設值的前 72 個位元組相同,之後不同。 這裡不做雜湊、不送出、不儲存,計算全部在你的瀏覽器裡。
不要輸入你真的在用的密碼。這裡不做雜湊、不送出、不儲存,計算全部在你的瀏覽器裡——但一個你曾經打進網頁的密碼,就是一個該換掉的密碼。
位元組 75 · 字 25 · 超過 72 位元組——後面的被丟掉
我的密碼是正確的馬電池釘書針正確的馬電池釘書針真密
位元組 75 · 字 25 · 超過 72 位元組——後面的被丟掉
我的密碼是正確的馬電池釘書針正確的馬電池釘書針真寇
對 bcrypt 而言,這兩個是同一個密碼嗎?
同一個密碼。用其中一個註冊,可以用另一個登入。
幾個字會填滿 72 位元組
- a 72 個字
- é 36 個字
- 密 24 個字
- 🔐 18 個字
上限是 72 位元組。行為於 2026-08-07 在 Node v25.9.0 + bcrypt 6.0.0 上實測。
那行判定不是在模擬 bcrypt,它就只是比對前 72 個位元組——因為那正是 bcrypt 對 輸入做的全部事情。當它說「同一個密碼」,意思是你可以用其中一個註冊、用另一個登入。
上限算的是位元組,而文字不是位元組
bcrypt 建立在 Blowfish 的金鑰排程上,它吃的是原始位元組。你的密碼要先編碼才會變成 位元組,而 UTF-8 不是對每個字元一視同仁:
| 每字位元組 | 塞得下幾個 | |
|---|---|---|
| 英文字母 | 1 | 72 |
| 帶重音的拉丁字母(é) | 2 | 36 |
| 常用中文字(密) | 3 | 24 |
| Emoji(🔐) | 4 | 18 |
英文使用者要刻意才撞得到 72。中文使用者用一句完全正常的話就到了—— 二十四個字是一個句子,不是耐力測驗。
同一個上限,在一個語言裡是冷知識,在另一個語言裡是正在發生的問題。而這正是那種 會安然通過一個英語團隊程式碼審查的錯誤。
切點不理會字元邊界
這一段是我看了兩次才相信的。
密 編碼成 e5 af 86,寇 編碼成 e5 af 87,前兩個位元組相同。把它們各自放在
一個切點落在第 72 位元組之後的密碼結尾,bcrypt 從兩者都只留下 e5 af:
"a" × 70 + 密 → 73 位元組
"a" × 70 + 寇 → 73 位元組
前 72 個位元組 → 完全相同
用第一個做雜湊、拿第二個去驗證,回傳 true。對照組 天(e5 a4 a9)在第二個
位元組就不同,正確地驗證失敗。
也就是說,兩個結尾完全不同、任何人都不會說它們一樣的密碼,是同一組憑證。 而被留下的那個尾巴根本不是合法的 UTF-8——它是三分之二個字。
一個在這裡不成立的傳說
你會讀到「bcrypt 會在 NUL 位元組處停下來,像 C 字串那樣」。在 bcrypt 6.0.0 上不會:
把 "secret" + NUL + "ignored-tail" 做成雜湊,再拿 "secret" 去驗證,回傳 false。
寫進來是因為它與上面每一項是同一種形狀的主張——一個在上百串留言裡被自信重複的 位元組層行為——而它對多數 Node 專案真正安裝的那個函式庫來說是錯的。 量你手上的那一個。
該怎麼做
問題不在那個上限,在那份沉默。
- 在註冊時就擋下超長密碼,並且說出來。 被告知「太長了」的使用者會換一組; 被靜默截斷的使用者,會相信一組不存在的密碼。
- 用位元組算長度,不要用字元數。
password.length在任何一個字元需要超過一個 位元組的語言裡都是錯的數字——而且是往「讓壞輸入通過」的方向錯。 - 如果你的使用者寫中文、日文或韓文,你真正的上限是 24 個字。 那不是一個值得 放進註腳的理論值,那是人們真的會打的長度。
如果你要的是一個完全沒有長度上限的密碼欄位,那是換一個 KDF 的理由,不是把 bcrypt 調長的理由。但多數系統需要的不是那個——是不要再對自己存下來的密碼說謊。
常見追問
密碼太長時 bcrypt 會報錯嗎?
在 Node 上不會。實測 bcrypt 6.0.0 與 bcryptjs 3.0.3,兩者都接受任意長度的密碼, 雜湊前 72 個位元組,其餘丟掉,不丟任何例外。其他語言的某些實作確實會拒絕過長輸入, 所以這是一個要逐個函式庫確認的行為,不能假設。
為什麼是 72 個位元組而不是 72 個字?
上限來自 bcrypt 底層的 Blowfish 金鑰排程,它吃的是原始位元組。文字要先編碼才會 變成位元組,而 UTF-8 給英文字母一個位元組、給多數帶重音的拉丁字母兩個、給常用 中文字三個、給 emoji 四個。上限是固定的,塞得下多少文字不是。
切點落在一個字中間會怎樣?
bcrypt 保留塞得下的那幾個位元組,把那個字剩下的部分丟掉。任何編碼開頭與那幾個 位元組相同的其他字,就會產生一模一樣的密碼——密與寇的開頭都是 e5 af, 所以切點落在第二個位元組之後時,兩者會撞在一起。
NUL 位元組會終止密碼嗎?
在這裡實測的版本不會。那個說法來自建立在 C 字串處理上的實作,值得實測而不是照抄: 在 bcrypt 6.0.0 上,含有 NUL 位元組的密碼不會通過「只有前半段」的驗證。
那到底該怎麼做?
在註冊時就擋下超長密碼並且說出來,不要靜默截斷。用位元組算長度,不要用字元數。 如果你的使用者會用中文寫密碼,實際的上限是 24 個字,不是 72。