結論先講
同一個算法在不同語言裡跑出不同速度,因為「實作方式」比「算法」影響更大。 Native C 實作 vs 純語言實作,差距可以到 2-5 倍。選框架的時候,大部分人不會去看它用哪個 bcrypt library——但這個選擇直接影響你 API 的 RPS 上限。
六種語言的 bcrypt 實作
Node.js:兩種選擇,天差地遠
| Library | 實作 | 阻塞行為 | 單次 hash 時間 |
|---|---|---|---|
bcryptjs | 純 JavaScript | 阻塞 event loop | ~120ms |
bcrypt | C++ native binding (libuv) | 不阻塞 event loop | ~80ms |
bcryptjs 是純 JS 寫的——好處是不需要 node-gyp 編譯,壞處是在 event loop 上跑,80ms 內所有其他 request 都在排隊。
bcrypt(npm 上的那個)是 C++ 寫的,透過 Node.js 的 native addon 呼叫。它把 hash 計算丟到 libuv 的 thread pool,不阻塞 event loop。速度快 30% 而且不卡住其他 request。
壓測中的 Express-TS 和 NestJS 用的是 native bcrypt。如果換成 bcryptjs,CRUD 場景的 RPS 會再掉 30%。
Python:被 GIL 困住
| Library | 實作 | 特性 |
|---|---|---|
bcrypt (pyca) | C binding (libffi) | 最快,但 GIL 下仍阻塞 |
passlib[bcrypt] | 包裝 pyca/bcrypt | 多一層 overhead |
django.contrib.auth | 用 passlib 或 pyca | Django 預設 |
Python 的 bcrypt 底層是 C library,速度本身不慢(~100ms)。但 CPython 的 GIL 讓你即使開了 4 個 thread,CPU-bound 的 bcrypt 也無法真正並行。
Django 和 FastAPI 的差異不在 bcrypt library(都是 C binding),而在 worker 模型:
- Django:gunicorn + UvicornWorker × 4
- FastAPI:uvicorn × 4
兩者都是 multi-process,所以都能繞過 GIL。差異在 Django ORM vs SQLAlchemy 的 overhead,以及 Django middleware 的層數。
Go:最接近裸金屬
import "golang.org/x/crypto/bcrypt"
hash, _ := bcrypt.GenerateFromPassword([]byte(password), 10)Go 的 bcrypt 是 golang.org/x/crypto 的純 Go 實作。沒有 C binding,沒有 FFI,沒有 VM——直接編譯成 machine code。
Go 的 bcrypt 是所有語言中最快的之一(~80ms),因為:
- 沒有 runtime/interpreter overhead
- goroutine 的 scheduling overhead 極低
- 記憶體分配用 stack(不是 heap),GC 壓力小
Java:JVM 的 warm-up 效果
// Spring Security 預設
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10);
String hash = encoder.encode(password);Spring Security 的 BCrypt 是 Java 實作(jBCrypt 的變體)。第一次 hash 可能要 150ms(JIT 還沒 compile),warm-up 後降到 ~100ms。
JVM 的特性是「慢啟動,後來越來越快」。 這就是為什麼 Spring Boot 在 10 VU 時看起來慢(127ms),但在長時間運行的混合場景中表現最好——JIT 把 hot path 優化到接近 native 速度。
C#:BCrypt.Net 的效率問題
// BCrypt.Net-Next
string hash = BCrypt.Net.BCrypt.HashPassword(password, 10);BCrypt.Net 是純 C# 實作。沒有 native binding,所有計算都在 CLR 上跑。
C# 的 bcrypt 是所有語言中最慢的(~150ms)。原因:
- BCrypt.Net 沒有用
Span<T>或stackalloc優化記憶體 - CLR 的 array bounds checking 在內層迴圈有 overhead
- 沒有 SIMD 優化
這直接反映在壓測數據上:.NET Core 在 10 VU 時 avg 216ms(比其他框架都高),500 VU 時 avg 51 秒(最差)。
PHP:password_hash 是 C extension
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 10]);PHP 的 password_hash 底層是 C library(libbcrypt),速度很快(~80ms)。而且 PHP-FPM 的 process-per-request 模型天然不會互相阻塞——每個 PHP process 獨立計算自己的 hash。
Laravel 在 10 VU 時 avg 64ms(全場最快之一),就是因為 PHP 的 bcrypt 夠快,而且 PHP-FPM 的隔離性好。
實作差異的量化影響
把各語言的 bcrypt 單次 hash 時間和壓測 RPS 放一起看:
| 語言 | Library | 單次 Hash | 10 VU RPS | 理論上限 (2核) |
|---|---|---|---|---|
| Go | x/crypto | ~80ms | 22 | 25 |
| Node.js | bcrypt (native) | ~80ms | 24 | 25 |
| PHP | password_hash (C) | ~80ms | 24 | 25 |
| Python | pyca/bcrypt (C) | ~100ms | 24 | 20 |
| Java | jBCrypt | ~100ms | 15 | 20 |
| C# | BCrypt.Net | ~150ms | 17 | 13 |
Go、Node.js (native)、PHP 的 hash 時間都在 80ms 左右,10 VU RPS 也都在 22-24。
C# 的 hash 時間 150ms,理論上限只有 13 RPS。實際 17 RPS 是因為 CRUD 流程中有些操作不需要 bcrypt(Get Profile、Update、Delete),所以比純 hash 的理論值高。
選框架時要注意的事
- 確認框架用的是哪個 bcrypt library。Node.js 的
bcryptjsvsbcrypt差 30% - 如果是 C#/.NET,考慮替代品。
Isopoh.Cryptography.Argon2或Konscious.Security.Cryptography可能比 BCrypt.Net 快 - Go 和 PHP 在 bcrypt 這個維度沒什麼好優化的——它們的實作已經很接近理論極限
- Java 要給足 warm-up 時間。不要拿第一次 hash 的時間當基準
下一篇
bcrypt vs argon2 vs scrypt:該用哪個 — bcrypt 不是唯一選擇。Argon2 是 2015 年的密碼 hash 冠軍,scrypt 是 Litecoin 用的。三者各有優缺點,選錯的代價是「安全不夠」或「效能太差」。
本系列文章
完整 68 篇目錄見 系列首頁
← 上一篇:Bcrypt 是萬惡之源:密碼 Hash 為什麼這麼慢 → 下一篇:bcrypt vs argon2 vs scrypt:密碼 Hash 該用哪個