パスワードハッシュ(passlib)¶
passlib は更新が止まってる
passlib は最終リリースが 1.7.4(2020年)で、更新がほぼ止まってる。下の実行結果に出る error reading bcrypt version も、 passlib が bcrypt 4.x のバージョン取得方法に追従できていないのが原因の警告。 Full Stack FastAPI Template も passlib をやめて、 pwdlib を使うように変わってる。新しく開発するなら パスワードハッシュ(pwdlib) のほうを使うのがいい。このページは「passlib だとこう書ける」っていう記録として。
passlib は
- パスワードを DB に保存するときにハッシュ化する
- ユーザーが入力したパスワードが DB 内のハッシュ化されたパスワードと同じか確かめる
ときに使える。
パッケージは uv で入れておく(bcrypt バックエンドも一緒に)。
コード例(bcrypt)¶
- Alice のパスワード(
alice_pw = "kokokoko")をハッシュ化した値を表示 - 正しい平文パスワードとハッシュ化したパスワードで一致するかを表示
- 間違った平文パスワードとハッシュ化したパスワードで一致するかを表示
# passlib==1.7.4, bcrypt==4.3.0
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
alice_pw = "kokokoko"
wrong_alice_pw = "ooookkkk"
def get_password_hash(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
def main():
hashed_alice_pw = get_password_hash(alice_pw)
print(hashed_alice_pw)
pw_match = verify_password(alice_pw, hashed_alice_pw)
pw_mismatch = verify_password(wrong_alice_pw, hashed_alice_pw)
print(pw_match)
print(pw_mismatch)
if __name__ == "__main__":
main()
実行結果( error reading bcrypt version は省略)。
インポートしてるもの: cf. passlib.context.CryptContext
使ってるメソッド:
Argon2id¶
passlib で推奨されてるハッシュ化アルゴリズムは 4 つある(2025年10月9日時点)。その中でも Argon2 が激推しされてる気がする: cf. Recommended hashes
OWASP Password Storage Cheat Sheet でも、まずは Argon2id の使用を検討することが書かれてる。
なので、ベストを目指すなら Argon2id を使うべきでは。 passlib では scheme としてハッシュ化アルゴリズムを指定する(使える値一覧: cf. passlib.hash)。
で Argon2 は使えそうだけど、 Argon2id にするにはどうしたらいいのか。
passlib のドキュメントに下の記述があったので、 argon2_cffi を入れればとりあえず Argon2 は使えるはず。
argon2_cffi, if installed. (this is the recommended option).
— https://passlib.readthedocs.io/en/stable/lib/passlib.hash.argon2.html#argon2-backends より
argon2_cffi のドキュメントを見ると、 type を指定すれば Argon2id にできるみたい。他のパラメータのベストプラクティスも別ページにある: cf. Choosing Parameters
RFC 9106 の 7.4. Recommendations にも推奨される 2 つの設定が書かれていて、その中の SECOND RECOMMENDED option がいい感じ(FIRST RECOMMENDED option はハッシュ値を計算するときに 2GB もメモリを使ってしまう・・・)。 argon2_cffi では、その設定を使える API も用意してくれてる: cf. RFC_9106_LOW_MEMORY
コード例(Argon2id)¶
- Alice のパスワード(
alice_pw = "kokokoko")を Argon2id でハッシュ化した値を表示 - 正しい平文パスワードとハッシュ化したパスワードで一致するかを表示
- 間違った平文パスワードとハッシュ化したパスワードで一致するかを表示
# passlib==1.7.4, argon2-cffi==25.1.0
from passlib.context import CryptContext
from argon2.profiles import RFC_9106_LOW_MEMORY
pwd_context = CryptContext(
schemes=["argon2"],
deprecated="auto",
argon2__time_cost=RFC_9106_LOW_MEMORY.time_cost, # (1)!
argon2__memory_cost=RFC_9106_LOW_MEMORY.memory_cost,
argon2__parallelism=RFC_9106_LOW_MEMORY.parallelism,
)
alice_pw = "kokokoko"
wrong_alice_pw = "ooookkkk"
def get_password_hash(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
def main():
hashed_alice_pw = get_password_hash(alice_pw)
print(hashed_alice_pw)
pw_match = verify_password(alice_pw, hashed_alice_pw)
pw_mismatch = verify_password(wrong_alice_pw, hashed_alice_pw)
print(pw_match)
print(pw_mismatch)
if __name__ == "__main__":
main()
- 以下 3 行(と
RFC_9106_LOW_MEMORYの import)は argon2_cffi のデフォルト値と同じなので、渡さなくても結果は変わらない(最小版は末尾参照)。
実行結果。
% uv run python main.py
$argon2id$v=19$m=65536,t=3,p=4$XGstpdQaozTGWGuNce69tw$g4+10XgiDHiNzoeKKeUcwL+jTgycfo8q/CB0x0UZSKI
True
False
ハッシュ化されたパスワードの先頭に $argon2id$v=19$m=65536,t=3,p=4$ とあって、 フォーマット と見比べると、 RFC 9106 の SECOND RECOMMENDED option のとおりに設定できてる。
argon2id→ Argon2id を使えてるm=65536→ メモリコストは 65536 KiB = 64MiB になってるt=3→ 繰り返し回数は 3 回になってる
ちなみに RFC 9106 の SECOND RECOMMENDED option が argon2_cffi のデフォルトなので、 pwd_context は下記のようにするだけでもいい。