現代のWebアプリケーションやサービスにおいて、ユーザーのパスワードを安全に保管することは非常に重要です。平文でパスワードを保存することは論外であり、万が一データベースが漏洩した場合、深刻な被害につながります。そこで重要になるのが「パスワードハッシュ化」です。パスワードハッシュ化とは、元のパスワードから一方向性の関数(ハッシュ関数)を用いて固定長の文字列(ハッシュ値)を生成し、そのハッシュ値を保存する手法です。
数あるハッシュ化アルゴリズムの中でも、特に強力な選択肢として知られているのが scrypt です。scryptは、Colin Percival氏によって2009年3月に考案されたパスワードベース鍵導出関数(Password-Based Key Derivation Function, KDF)です。scryptの最大の特徴は、「メモリハード(memory-hard)」である点です。これは、ハッシュ値の計算に多くのメモリ(RAM)を必要とするように設計されていることを意味します。
なぜメモリハードであることが重要なのでしょうか?従来のハッシュ関数(例えばMD5やSHA-1, SHA-256など)は、CPU計算能力に依存していました。そのため、GPU(グラフィックプロセッシングユニット)やASIC(特定用途向け集積回路)のような専用ハードウェアを使用することで、並列計算による高速な総当たり攻撃(ブルートフォース攻撃)が可能になってしまいました。scryptは、計算に大量のメモリを要求することで、このような専用ハードウェアによる攻撃を困難にし、コストを高めることを目的としています。
Pythonでこの強力なscryptアルゴリズムを利用可能にするのが、scrypt
ライブラリです。このライブラリは、scryptアルゴリズムのPythonバインディングを提供し、パスワードのハッシュ化や検証を簡単に行えるようにします。この記事では、Pythonのscrypt
ライブラリについて、インストール方法から基本的な使い方、パラメータの詳細、セキュリティ上の考慮事項、そして他のアルゴリズムとの比較まで、徹底的に解説していきます。 💪
インストール 💻
scrypt
ライブラリを使用するには、まずインストールが必要です。pipコマンドを使って簡単にインストールできます。
pip install scrypt
ただし、scrypt
ライブラリは内部でC言語のコードを利用しており、OpenSSLライブラリに依存しています。そのため、環境によってはインストール時にビルドエラーが発生することがあります。
注意点:ビルド依存関係
多くの場合、OSにCコンパイラ(GCCなど)とOpenSSLの開発用ヘッダファイルが必要です。
- Debian/Ubuntu系:
sudo apt-get install build-essential libssl-dev python-dev
(Python 2の場合はpython-dev
、Python 3の場合はpython3-dev
) - Fedora/RHEL系:
sudo yum install gcc openssl-devel python-devel
(Python 3の場合はpython3-devel
) - macOS (Homebrew使用):
brew install openssl
を実行後、環境変数を設定する必要がある場合があります。
export CFLAGS="-I$(brew --prefix openssl)/include $CFLAGS"
export LDFLAGS="-L$(brew --prefix openssl)/lib $LDFLAGS"
- Windows: 通常、
pip install scrypt
で配布されているコンパイル済みのホイール(wheel)ファイルが自動的に使用されるため、追加の作業は不要なことが多いです。もしソースからコンパイルする必要がある場合は、OpenSSLのWin64/Win32開発パッケージ(例: SLProWebから入手可能)を特定の場所(例:C:\OpenSSL-Win64
)にインストールする必要があります。 - Anaconda: conda-forgeチャネルからインストールすることも可能です。
conda install -c conda-forge scrypt
最新のインストール手順や依存関係については、PyPIのscryptプロジェクトページを確認することをお勧めします。
もしModuleNotFoundError: No module named 'scrypt'
のようなエラーが出た場合は、インストールが正しく完了していないか、Python環境が正しく認識されていない可能性があります。仮想環境(venvなど)を使用している場合は、その環境に正しくインストールされているか確認してください。
基本的な使い方 🚀
scrypt
ライブラリの使い方は非常にシンプルです。主にhash
関数とverify
関数(またはdecrypt
関数、後述)を使用します。
1. ライブラリのインポート
まず、ライブラリをインポートします。
import scrypt
import os # ソルト生成のため
2. パスワードのハッシュ化
パスワードをハッシュ化するにはscrypt.hash()
関数を使用します。この関数は、パスワード、ソルト(salt)、およびscryptの動作を制御するパラメータ(N, r, p)を引数にとります。
ソルト(Salt)は、ハッシュ化ごとに生成されるランダムなデータです。同じパスワードでもソルトが異なれば生成されるハッシュ値も異なるため、レインボーテーブル攻撃などに対する耐性が向上します。ソルトはハッシュ値と一緒に保存する必要があります。セキュリティ上、os.urandom()
のような暗号学的に安全な乱数生成器を使用してソルトを生成することが強く推奨されます。一般的に16バイト(128ビット)以上の長さが推奨されます。
# ハッシュ化するパスワード(バイト列である必要あり)
password = b"mysecretpassword"
# ソルトを生成 (16バイト推奨)
# 暗号学的に安全な乱数生成器を使用する
salt = os.urandom(16)
# scryptパラメータ (詳細は後述)
# 推奨値はシステムや要件によって異なる
N = 16384 # CPU/メモリコスト (2^14)
r = 8 # ブロックサイズ
p = 1 # 並列化係数
buflen = 64 # 生成されるハッシュの長さ(バイト単位)
try:
# パスワードをハッシュ化
hashed_password = scrypt.hash(password, salt, N=N, r=r, p=p, buflen=buflen)
# 生成されたハッシュ値(バイト列)
print(f"ソルト: {salt.hex()}")
print(f"ハッシュ値: {hashed_password.hex()}")
# 実際には、ソルトとハッシュ値、パラメータ(N, r, p)を
# データベースなどに保存する
# 例: f"{N}:{r}:{p}:{salt.hex()}:{hashed_password.hex()}" のような形式で保存
except scrypt.error as e:
print(f"ハッシュ化中にエラーが発生しました: {e}")
scrypt.hash()
は、計算されたハッシュ値をバイト列として返します。
3. パスワードの検証
ユーザーがログイン時などに入力したパスワードが、保存されているハッシュ値と一致するかどうかを検証するには、いくつかの方法があります。
方法1: 再度ハッシュ化して比較する
入力されたパスワードを、保存されているソルトとパラメータを使って再度ハッシュ化し、その結果が保存されているハッシュ値と一致するかどうかを比較します。この比較は、タイミング攻撃を防ぐために、一定時間で比較を行う関数(例:hmac.compare_digest
)を使用することが推奨されます。
import hmac
# ユーザーが入力したパスワード(検証用)
input_password = b"mysecretpassword"
# --- データベースなどから保存された情報を取得 ---
# 保存形式の例: f"{N}:{r}:{p}:{salt.hex()}:{hashed_password.hex()}"
stored_data = f"{N}:{r}:{p}:{salt.hex()}:{hashed_password.hex()}"
parts = stored_data.split(":")
stored_N = int(parts[0])
stored_r = int(parts[1])
stored_p = int(parts[2])
stored_salt = bytes.fromhex(parts[3])
stored_hash = bytes.fromhex(parts[4])
# ---------------------------------------------
try:
# 入力されたパスワードを同じソルトとパラメータでハッシュ化
derived_key = scrypt.hash(input_password, stored_salt, N=stored_N, r=stored_r, p=stored_p, buflen=len(stored_hash))
# タイミング攻撃耐性のある比較関数で検証
if hmac.compare_digest(derived_key, stored_hash):
print("パスワードは一致しました! 🎉")
else:
print("パスワードが一致しません。😭")
except scrypt.error as e:
print(f"検証中にエラーが発生しました: {e}")
except ValueError:
print("保存されたデータの形式が不正です。")
方法2: scrypt.decrypt()
を使う(非推奨だが歴史的経緯で存在)
scrypt
ライブラリには元々、ファイル暗号化ユーティリティとしての側面があり、encrypt
とdecrypt
という関数も提供されています。これらは内部でscrypt.hash
を呼び出しますが、追加のヘッダー情報(パラメータやソルトを含む)やHMAC検証などを付加した独自のバイナリ形式を扱います。
過去には、このdecrypt
関数をパスワード検証の「副作用」として利用する例も見られましたが、これは本来の使い方ではなく、以下の理由からパスワードハッシュの検証には推奨されません。
encrypt
関数は内部でソルトを自動生成し、パラメータもmaxtime
引数に基づいて自動選択するため、制御が難しい。- ファイル暗号化のための追加処理(HMAC計算やデータ復号)が含まれており、パスワード検証には不要なオーバーヘッドがある。
decrypt
のmaxtime
引数は、検証時間を制限するためのものであり、セキュリティパラメータを直接制御するものではない。
# --- scrypt.encrypt/decrypt を使う例(非推奨)---
# encrypt関数は内部でソルト生成やパラメータ選択を行う
try:
# 何か適当なデータを暗号化 (このデータ自体は検証に関係ない)
encrypted_data = scrypt.encrypt(b'verification_data', password, maxtime=0.1)
# 検証: decryptが成功すればパスワードが正しいとみなす
# maxtimeは計算時間の上限を設定
decrypted_data = scrypt.decrypt(encrypted_data, input_password, maxtime=0.1)
print("パスワードは一致しました! (decrypt使用)")
except scrypt.error as e:
# パスワードが違う場合や、maxtimeを超えた場合にエラー
print(f"パスワードが一致しません、またはエラー: {e} (decrypt使用)")
結論として、パスワードハッシュの検証には、方法1(再ハッシュ化して比較) を使用し、比較にはhmac.compare_digest
などのタイミング攻撃耐性のある関数を使用するのが最も安全で推奨される方法です。
scryptパラメータ詳解 ⚙️
scryptのセキュリティ強度とパフォーマンスは、いくつかのパラメータによって調整されます。これらのパラメータを正しく理解し、適切に設定することが非常に重要です。パラメータの選択は、セキュリティ要件(どれだけ攻撃者に時間をかけさせたいか)と、サーバーのリソース(CPU、メモリ)および許容される応答時間(ユーザーがログイン時に待てる時間)とのトレードオフになります。
scrypt.hash()
関数で使用される主なパラメータは以下の通りです。
パラメータ | 説明 | 役割 | 影響 | 推奨事項・注意点 |
---|---|---|---|---|
N |
CPU/メモリコストパラメータ (costParameter) | 反復計算の回数を制御します。アルゴリズム全体の計算量とメモリ使用量の主要な決定要因です。 | 値を大きくすると、計算時間とメモリ使用量が指数関数的に増加し、セキュリティが向上しますが、パフォーマンスは低下します。 | 2のべき乗である必要があります (例: 214 = 16384, 217 = 131072)。1より大きい必要があります。 RFC7914では 2(128 * r / 8) 未満である必要があります。対話型ログイン(Webサイトなど)では、一般的に 214 (16384) から 217 (131072) 程度が推奨されますが、システム性能に応じて調整が必要です。 |
r |
ブロックサイズパラメータ (blockSize) | 内部で使用されるメモリブロックのサイズを決定します。シーケンシャルメモリアクセスの量を制御します。 | 値を大きくすると、メモリ使用量が増加し、キャッシュ効率が悪くなるため、特定のハードウェア攻撃に対する耐性が向上する可能性があります。パフォーマンスへの影響はNほど大きくありません。 | RFC7914 や多くの実装では r=8 が一般的なデフォルト値であり、良好な結果が得られるとされています。この値は、内部的に 128 * r バイトのブロックサイズを意味します(r=8 の場合、1024バイト)。 |
p |
並列化パラメータ (parallelizationParameter) | 計算を並列に実行する度合いを制御します。独立した計算をいくつ並行して行うかを示します。 | 値を大きくすると、並列処理が可能なハードウェア(マルチコアCPUなど)での計算コストが増加し、セキュリティが向上します。ただし、単一コアのCPUでは、単純に計算時間がp倍になります。メモリ使用量もp倍になります(総メモリ量 ≈ 128 * N * r * p バイト)。 | 対話型ログインでは、多くのリクエストを同時に処理する必要があるため、通常 p=1 が推奨されます。これにより、個々のハッシュ計算が他の計算をブロックするのを防ぎます。ファイル暗号化など、並列性が重要でない場合は、pを増やしてCPUコストを高めることができます。 RFC7914では、p は ((232-1) * 32) / (128 * r) 以下の正の整数である必要があります。 |
buflen |
出力長 (derived key length) | 生成されるハッシュ(派生キー)の長さをバイト単位で指定します。 | 必要な鍵の長さに応じて設定します。 | 一般的に64バイト (512ビット) がよく使用されます。32バイト(256ビット)でも十分な場合が多いです。 RFC7914では、(232 – 1) * 32 以下の正の整数である必要があります。 |
salt |
ソルト | パスワードごとに付加されるランダムな値。 | 同じパスワードでも異なるハッシュ値を生成させ、レインボーテーブル攻撃などを防ぎます。 | 必須です。パスワードごとにユニークで、暗号学的に安全な乱数生成器 (e.g., os.urandom() ) で生成する必要があります。最低16バイト(128ビット)が推奨されます。ハッシュ値と一緒に保存する必要があります。 |
メモリ使用量の計算
scryptのおおよそのメモリ使用量は、以下の式で計算できます。
メモリ使用量 (バイト) ≈ 128 * N * r * p
例えば、推奨されるパラメータの一つである N=16384 (214), r=8, p=1 の場合:
128 * 16384 * 8 * 1 = 16,777,216 バイト = 16 MiB
OWASP (Open Web Application Security Project) の推奨するパラメータの一つである N=131072 (217), r=8, p=1 の場合:
128 * 131072 * 8 * 1 = 134,217,728 バイト = 128 MiB
これらの計算は、アプリケーションが動作するサーバーのメモリ容量を考慮してパラメータを選択する上で重要です。
推奨パラメータ値
推奨されるパラメータ値は、時代やハードウェアの進化、そしてアプリケーションの要件によって変化します。以下は、いくつかの情報源に基づく推奨値の例です。
OWASPの推奨 (Argon2idが利用できない場合)
OWASP Password Storage Cheat Sheetでは、Argon2idを第一推奨としていますが、それが利用できない場合のscryptの最小構成として以下を挙げています。
- N = 217 (131072), r = 8, p = 1 (メモリ約128 MiB)
また、他の選択肢として、メモリ使用量と並列度を調整した組み合わせも提示しています(例:N=216, r=8, p=2 や N=215, r=8, p=3 など)。
一般的な対話型ログインの推奨例
多くのドキュメントや議論で見られる、応答時間を考慮した一般的な推奨値です(通常0.5秒以内を目指す)。
- N = 214 (16384), r = 8, p = 1 (メモリ約16 MiB)
※ただし、これは数年前の推奨である可能性があり、現代のハードウェアではより高い値(例:N=215 or 216)を設定できる場合があります。
libsodiumライブラリの推奨
著名な暗号ライブラリlibsodiumでは、対話型操作のベースラインとして以下の定数を提供しています。
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE
(Nに対応, libsodium v1.0.18では 219=524288)crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE
(メモリ上限に対応, libsodium v1.0.18では 16 MiB)
より機密性の高いデータ向けには、より高い値(例:OPSLIMIT_SENSITIVE (220), MEMLIMIT_SENSITIVE (1 GiB))も提供されています。
※libsodiumのパラメータ(opslimit, memlimit)は、Python scryptライブラリのN, r, pとは直接対応しませんが、計算コストとメモリコストを設定する考え方は共通です。
重要なのは、これらの推奨値を鵜呑みにせず、自身のアプリケーションが動作する環境で実際に時間を計測し、セキュリティとパフォーマンスのバランスが取れた値を見つけることです。 サーバーのCPU性能、メモリ容量、予想される同時アクセス数などを考慮して、ユーザーが許容できる待ち時間(例:0.1秒〜0.5秒程度)で、できるだけ高いコストパラメータ(特にN)を設定することを目指しましょう。また、将来的にハードウェア性能が向上することを見越して、定期的にパラメータを見直し、必要であればより高い値に更新する運用も検討すべきです。
セキュリティに関する考慮事項 🛡️
scrypt
ライブラリを安全に使用するためには、いくつかの重要な点に注意する必要があります。
1. ソルトの適切な管理
- ユニークなソルト: 各パスワードに対して、必ずユニーク(一意)なソルトを生成してください。同じソルトを複数のパスワードで使い回してはいけません。
- 安全な乱数生成器: ソルトの生成には、
os.urandom()
やsecrets.token_bytes()
のような暗号学的に安全な乱数生成器(CSPRNG)を使用してください。random
モジュールは予測可能なため、セキュリティ用途には適していません。 - 十分な長さ: ソルトは最低でも16バイト(128ビット)の長さを持つことが推奨されます。
- 保存: 生成したソルトは、対応するパスワードハッシュと一緒にデータベースなどに保存する必要があります。検証時に同じソルトを使用するためです。
2. パラメータの慎重な選択
- コストとパフォーマンスのバランス: 前述の通り、パラメータ(特にN)は、攻撃者にとって十分に計算コストが高く、かつ正規のユーザーにとっては許容可能な応答時間になるように慎重に選択する必要があります。
- 環境に応じた調整: アプリケーションのサーバー環境(CPU、メモリ)や利用状況(対話型ログインか、バッチ処理かなど)に応じて最適なパラメータは異なります。実際に計測して決定することが重要です。
- 定期的な見直し: コンピュータの処理能力は年々向上するため、過去に設定したパラメータが将来的に不十分になる可能性があります。定期的にパラメータを見直し、必要に応じて値を大きくすることを検討してください。(パラメータ変更時の対応については後述します)
3. タイミング攻撃への対策
パスワード検証時に、正しいパスワードの場合と間違ったパスワードの場合で処理時間が異なると、その時間差から情報を推測される「タイミング攻撃」のリスクがあります。scrypt
アルゴリズム自体は計算量が一定になるように設計されていますが、ハッシュ値の比較処理で注意が必要です。
単純な文字列比較(==
)は、文字列が異なる箇所が見つかった時点で比較を終了するため、比較にかかる時間が入力によって変動する可能性があります。そのため、パスワードハッシュの比較には、比較にかかる時間が常に一定になる定数時間比較関数を使用することが強く推奨されます。Pythonでは、hmac.compare_digest()
がこの目的のために利用できます。
import hmac
# ... derived_key と stored_hash を取得 ...
# 安全な比較
if hmac.compare_digest(derived_key, stored_hash):
print("一致")
else:
print("不一致")
# 比較 (タイミング攻撃のリスクあり - 非推奨)
# if derived_key == stored_hash:
# print("一致")
4. 他のパスワードハッシュアルゴリズムとの比較
パスワードハッシュ化にはscrypt以外にもいくつかの有名なアルゴリズムがあります。それぞれの特徴を理解しておくことは、適切な技術選択に役立ちます。
アルゴリズム | 主な特徴 | 長所 | 短所 | 推奨度 (2025年時点) |
---|---|---|---|---|
Argon2 (特に Argon2id) | Password Hashing Competition (PHC) の勝者 (2015年)。メモリハード、CPUハード、並列化、サイドチャネル攻撃耐性などを考慮した設計。 | 現在最も推奨されるアルゴリズム。高いカスタマイズ性。ASIC/GPU耐性、サイドチャネル攻撃耐性(Argon2i/id)。 | 比較的新しいため、古いシステムでの採用例は少ない。パラメータ設定がbcryptより複雑。 | 最高 ⭐⭐⭐⭐⭐ |
scrypt | メモリハード設計の先駆け。大量のメモリを要求することでASIC/GPU攻撃のコストを高める。 | Argon2が登場するまではメモリハードなKDFとして有力。bcryptよりもASIC耐性が高いとされる。パラメータ(N, r, p)で柔軟に調整可能。 | Argon2ほどの総合的な耐性(特にサイドチャネル攻撃)はない。bcryptよりもメモリ消費が大きい傾向。Python実装はC実装に比べて遅い場合がある。 | 高い ⭐⭐⭐⭐ (Argon2が使えない場合の次善策) |
bcrypt | Blowfish暗号ベース。計算コスト(ワークファクター)を調整可能。広く使われており実績がある。 | 長年の実績があり、多くの言語やフレームワークでサポートされている。実装が比較的容易。メモリ使用量が比較的一定(約4KB)。 | scryptやArgon2ほどのメモリハード性はない。大規模なFPGA攻撃に対して脆弱である可能性が指摘されている。入力長に制限がある(多くの実装で72バイト)。 | 中〜高い ⭐⭐⭐ (レガシーシステムや要件によっては依然有効) |
PBKDF2 (Password-Based Key Derivation Function 2) | 反復ハッシュ(HMAC-SHA256などを使用)により計算コストを高める。RFC 2898で標準化。 | 標準化されており、多くのプラットフォームで利用可能。FIPS準拠が必要な場合に使われることがある。 | メモリハードではないため、GPUやASICによる並列計算に比較的弱い。bcryptやscrypt、Argon2に比べて推奨度は低い。 | 低い ⭐⭐ (特別な理由がない限り、新規開発では避けるべき) |
MD5 / SHA-1 / SHA-256 (単体) | 汎用的なハッシュ関数。高速計算が特徴。 | 計算が高速。 | パスワードハッシュには絶対に使用してはいけない。計算が高速すぎるため、ブルートフォース攻撃に極めて脆弱。ソルトの仕組みも組み込まれていない。衝突耐性の問題も存在する(特にMD5, SHA-1)。 | 不可 ❌ |
結論として、新規開発においては、可能であればArgon2idを使用することが最も推奨されます。それが利用できない、あるいは既存システムとの互換性が必要などの理由がある場合に、scryptは依然として強力で安全な選択肢となります。bcryptも広く使われていますが、メモリハード性の観点からはscryptやArgon2に劣ります。PBKDF2は特別な要件がない限り避けるべきであり、MD5やSHA系単体での使用は論外です。
高度な使い方とヒント 💡
1. Webフレームワークとの統合
DjangoやFlaskなどのPython Webフレームワークでscrypt
を使用する場合、フレームワークの認証システムと連携させる必要があります。
- Django: DjangoはデフォルトでPBKDF2を使用しますが、パスワードハッシャーの設定を変更することでscrypt(やArgon2, bcrypt)を使用できます。
PASSWORD_HASHERS
設定に'django.contrib.auth.hashers.ScryptPasswordHasher'
を追加(または優先順位を変更)します。ただし、Django 3.1以降ではscryptの直接的なサポートは明示されていないようで、サードパーティのパッケージ(例:django-scrypt
)を利用するか、カスタムハッシャーを作成する必要があるかもしれません。最新のDjangoドキュメントを確認してください。 - Flask: Flask自体は特定の認証システムを提供しませんが、
Flask-Login
やFlask-Security
のような拡張機能と組み合わせることが一般的です。これらの拡張機能や、passlib
のような汎用パスワードハッシュライブラリと連携してscrypt
を利用できます。passlib
は多くのハッシュアルゴリズム(scrypt含む)を統一的なインターフェースで扱えるため便利です。
passlib
を使用する場合の例:
# passlibのインストールが必要: pip install "passlib[scrypt]"
from passlib.hash import scrypt
# ハッシュ化 (passlibはソルトとパラメータをハッシュ文字列内に自動で埋め込む)
hashed_password_passlib = scrypt.hash("mysecretpassword")
print(f"Passlib形式のハッシュ: {hashed_password_passlib}")
# 例: $s0$e0801$N9PslXBZdDBu7b6jZk9bWQ$5p... (パラメータN,r,pとSaltがエンコードされている)
# 検証
is_valid = scrypt.verify("mysecretpassword", hashed_password_passlib)
print(f"Passlibでの検証結果: {is_valid}")
is_invalid = scrypt.verify("wrongpassword", hashed_password_passlib)
print(f"Passlibでの検証結果 (誤): {is_invalid}")
passlib
のようなライブラリは、パラメータやソルトの管理を自動化し、異なるアルゴリズムへの移行も容易にするため、フレームワークと組み合わせる際に非常に有用です。
2. ハッシュ、ソルト、パラメータの保存
scrypt.hash()
で生成したハッシュ値を検証するためには、使用したソルトとパラメータ(N, r, p)も一緒に保存する必要があります。passlib
を使わない場合、これらをどのようにデータベースに格納するかが問題になります。
一般的な方法は、これらの情報を特定の区切り文字(例::
や$
)で連結した単一の文字列として保存することです。
例: {アルゴリズム名}${N}${r}${p}${Base64エンコードされたソルト}${Base64エンコードされたハッシュ}
または単純に: {N}:{r}:{p}:{16進数エンコードされたソルト}:{16進数エンコードされたハッシュ}
Base64エンコードはバイナリデータをテキストとして扱いやすくするために便利ですが、16進数エンコードでも問題ありません。重要なのは、保存した文字列から各要素(N, r, p, ソルト, ハッシュ値)を正確に復元できる形式を選択することです。
import base64
# ... password, salt, N, r, p, hashed_password を生成 ...
# 保存用文字列の生成 (例: Base64形式)
# アルゴリズム名も入れておくと将来の移行時に便利
algo = "scrypt"
salt_b64 = base64.b64encode(salt).decode('ascii')
hash_b64 = base64.b64encode(hashed_password).decode('ascii')
storage_string = f"${algo}${N}${r}${p}${salt_b64}${hash_b64}"
print(f"保存用文字列: {storage_string}")
# --- 検証時の復元 ---
parts = storage_string.split('$')
if len(parts) == 7 and parts[1] == "scrypt":
retrieved_N = int(parts[2])
retrieved_r = int(parts[3])
retrieved_p = int(parts[4])
retrieved_salt = base64.b64decode(parts[5])
retrieved_hash = base64.b64decode(parts[6])
print("復元成功!")
# ここで retrieved_N, retrieved_r, ... を使って検証処理を行う
else:
print("不正な形式です。")
3. パラメータ更新への対応
前述の通り、セキュリティを維持するためには、将来的にscryptのパラメータ(特にN)をより高い値に更新する必要が出てくるかもしれません。しかし、既存のユーザーのハッシュ値をすべて一度に再計算するのは現実的ではありません。
一般的なアプローチは、ユーザーが次にログインしたタイミングで、新しいパラメータを使ってパスワードを再ハッシュ化し、データベースの情報を更新するという方法です。
- ユーザーがログインを試みる。
- データベースからユーザーのハッシュ情報(現在のハッシュ値、ソルト、パラメータN, r, p)を取得する。
- 入力されたパスワードと取得した情報を使って検証を行う。
- 検証が成功した場合:
- 現在のパラメータ(N, r, p)が、アプリケーションで設定されている最新の推奨パラメータよりも低いかどうかをチェックする。
- もし低ければ、入力されたパスワードを新しいソルトと新しい推奨パラメータで再ハッシュ化する。
- データベースのハッシュ情報(ハッシュ値、ソルト、パラメータ)を新しいものに更新する。
- ログイン処理を続行する。
この方法により、ユーザーにパスワードの再設定を強制することなく、徐々にシステム全体のパスワードハッシュの強度を高めていくことができます。この処理を実装するためには、ハッシュ情報と一緒に使用されたパラメータ(N, r, p)を保存しておくことが不可欠です。
4. Python実装のパフォーマンス
注意点として、Pythonのscrypt
ライブラリ(特にCバインディングを使用しない純粋なPython実装の場合、例: pyscrypt
)は、C言語などで実装されたものと比較してパフォーマンスが劣る可能性があります。一部の情報源では100倍遅い可能性も指摘されています。
PyPIで配布されているscrypt
パッケージ (py-scrypt
) はCバインディングを使用しているため比較的速いですが、それでもネイティブなC実装には及ばない可能性があります。パフォーマンスが重要なアプリケーションでは、実際に計測を行い、必要であれば他の実装(例: GoやRustで書かれたサービスを呼び出すなど)を検討することも視野に入れるべきかもしれません。
まとめ ✨
Pythonのscrypt
ライブラリは、強力なパスワードベース鍵導出関数であるscryptアルゴリズムを手軽に利用するためのツールです。そのメモリハードな特性により、GPUやASICといった専用ハードウェアを用いた総当たり攻撃に対して高い耐性を持ちます。
主なポイント:
- インストールにはCコンパイラやOpenSSL開発ライブラリが必要な場合があります。
scrypt.hash()
でハッシュ化、再ハッシュ化と比較(hmac.compare_digest
使用)で検証するのが推奨される方法です。- パラメータ(N, r, p)の選択がセキュリティとパフォーマンスの鍵です。環境に合わせて調整し、定期的に見直しましょう。
- ソルトはパスワードごとにユニークで、安全な乱数生成器で生成し、ハッシュ値・パラメータと共に保存する必要があります。
- 現代ではArgon2idが最も推奨されますが、scryptも依然として強力で安全な選択肢です。
パスワードの安全な保管は、ユーザーの信頼を得てサービスを継続するために不可欠な要素です。scrypt
ライブラリや、passlib
のような高レベルなライブラリ、そして最新の推奨アルゴリズムであるArgon2などを適切に活用し、堅牢な認証システムを構築しましょう! 💪🔐
コメント