Skip to content

パスワードハッシュ(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 バックエンドも一緒に)。

uv add passlib 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 は省略)。

% uv run python main.py
$2b$12$fRybfKUgkFlYCsxoIKFLs.C9Jc4jtQsKPRu741Hod3vmpcFP7tFHe
True
False

インポートしてるもの: 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)。

pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")

で 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 より

uv add argon2-cffi

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()
  1. 以下 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 は下記のようにするだけでもいい。

pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")