結論先講

同一個算法在不同語言裡跑出不同速度,因為「實作方式」比「算法」影響更大。 Native C 實作 vs 純語言實作,差距可以到 2-5 倍。選框架的時候,大部分人不會去看它用哪個 bcrypt library——但這個選擇直接影響你 API 的 RPS 上限。


六種語言的 bcrypt 實作

Node.js:兩種選擇,天差地遠

Library實作阻塞行為單次 hash 時間
bcryptjs純 JavaScript阻塞 event loop~120ms
bcryptC++ 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 或 pycaDjango 預設

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),因為:

  1. 沒有 runtime/interpreter overhead
  2. goroutine 的 scheduling overhead 極低
  3. 記憶體分配用 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)。原因:

  1. BCrypt.Net 沒有用 Span<T>stackalloc 優化記憶體
  2. CLR 的 array bounds checking 在內層迴圈有 overhead
  3. 沒有 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單次 Hash10 VU RPS理論上限 (2核)
Gox/crypto~80ms2225
Node.jsbcrypt (native)~80ms2425
PHPpassword_hash (C)~80ms2425
Pythonpyca/bcrypt (C)~100ms2420
JavajBCrypt~100ms1520
C#BCrypt.Net~150ms1713

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 的理論值高。


選框架時要注意的事

  1. 確認框架用的是哪個 bcrypt library。Node.js 的 bcryptjs vs bcrypt 差 30%
  2. 如果是 C#/.NET,考慮替代品Isopoh.Cryptography.Argon2Konscious.Security.Cryptography 可能比 BCrypt.Net 快
  3. Go 和 PHP 在 bcrypt 這個維度沒什麼好優化的——它們的實作已經很接近理論極限
  4. Java 要給足 warm-up 時間。不要拿第一次 hash 的時間當基準

下一篇

bcrypt vs argon2 vs scrypt:該用哪個 — bcrypt 不是唯一選擇。Argon2 是 2015 年的密碼 hash 冠軍,scrypt 是 Litecoin 用的。三者各有優缺點,選錯的代價是「安全不夠」或「效能太差」。


本系列文章

完整 68 篇目錄見 系列首頁

← 上一篇:Bcrypt 是萬惡之源:密碼 Hash 為什麼這麼慢 → 下一篇:bcrypt vs argon2 vs scrypt:密碼 Hash 該用哪個