Python実践セキュリティ:hashlibの誤用によるパスワード漏洩を防ぐ

Python tutorial - IT technology blog
Python tutorial - IT technology blog

なぜハッシュ化(Hash)と乱数生成が重要なのか?

Web開発を始めたばかりの頃、私はユーザーのパスワードをプレーンテキスト(生パスワード)のままデータベースに保存するという, とんでもないミスを犯したことがあります。当時は「まさかハッキングなんてされないだろう」と自分に言い聞かせていました。しかし、小さなSQL Injectionの脆弱性から、顧客データがすべて流出してしまいました。これは、hashlibsecretsの使い方を真剣に学ぶきっかけとなった、非常に高い代償を払った教訓です。

多くの人がハッシュ化(Hashing)暗号化(Encryption)を混同しがちです。暗号化は双方向であり、鍵があれば復号できます。対照的に、ハッシュ化は一方通行です。一度ハッシュ化すると、元の値に戻すことはできません。これは最後の防衛線です。たとえハッカーがデータベースを奪取したとしても、そこにあるのは意味のない文字列の羅列だけになります。

また、パスワードリセット用トークンの生成にも注意が必要です。標準のrandomモジュールを使用すると、ハッカーはシステム時間に基づいて次の値を予測できてしまう可能性があります。secretsモジュールは、この問題を解決するために生まれました。これは、暗号学的に安全な(cryptographically secure)ランダム性を保証します。

環境構築

hashlibsecretsはどちらもPythonの標準ライブラリです。pip installで追加する必要はありません。Python 3.6以上がインストールされていれば、すぐに使い始めることができます。

import hashlib
import secrets
import hmac

print("環境が整いました!")

2024年現在のプロダクション環境では、Python 3.9以降の使用を推奨します。このバージョンは、高負荷システムにおけるハッシュパフォーマンスが最適化されています。

実践的な実装と詳細設定

1. hashlibによるパスワードのハッシュ化(MD5は絶対NG)

古いチュートリアルの多くは、いまだにMD5やSHA-1の使用を教えています。私のアドバイスは、「今すぐやめましょう!」です。現代のGPUは、1秒間に数十億件のMD5ハッシュを試行できます。ハッカーはレインボーテーブル(Rainbow Tables)を使って、わずか数分であなたのパスワードを割り出してしまうでしょう。

現在安全な選択肢は、SHA-256ソルト(Salt)と反復(iterations)メカニズムを組み合わせることです。私は通常、ハッシュ化プロセスを意図的に遅らせるためにpbkdf2_hmacを使用します。これにより、ブルートフォース攻撃(総当たり攻撃)にかかるコストを極端に高め、実行不可能にします。

def hash_password(password: str):
    # 16バイトのランダムなソルトを生成
    salt = secrets.token_bytes(16)
    # 100,000回の反復でPBKDF2を使用
    pw_hash = hashlib.pbkdf2_hmac(
        'sha256', 
        password.encode('utf-8'), 
        salt, 
        100000
    )
    return salt + pw_hash

# テスト実行
raw_pw = "PasswordManh2024!"
stored_value = hash_password(raw_pw)
print(f"DB保存用: {stored_value.hex()}")

コードを書かずにハッシュ結果をすぐに確認したいときは、Hash Generatorをよく使います。このツールは完全にブラウザ側(クライアントサイド)で動作します。データがサーバーに送信されることはないため、機密性の高い文字列のテストや、Fakerで生成したダミーデータの検証にも安全です。

2. secretsによるトークンと一時パスワードの生成

「パスワードを忘れた場合」の機能を実装する際、メールで送信するトークンが必要になります。予測されやすいため、random.randint()は使わないでください。予測不可能な文字列を生成するには、secretsを使用します。

# URLセーフなトークンを生成
reset_token = secrets.token_urlsafe(32)
print(f"メール用トークン: {reset_token}")

# 16文字の一時パスワードを生成
temp_pass = secrets.token_hex(8)
print(f"一時パスワード: {temp_pass}")

管理者が従業員向けに強力なパスワードリストを素早く作成する必要がある場合は、Password Generatorを試してみてください。長さや特殊文字のカスタマイズが可能で、パスワードの強度も視覚的に測定できます。

3. HMACによるデータ認証

HMACを使用すると、システム間(Webhookなど)でデータが転送される際に改ざんされていないことを保証できます。これは、荷物に封印シールを貼るようなものです。特にSocket通信で独自のメッセージをやり取りする場合や、秘密鍵をDynaconfで安全に管理する際に非常に重要となります。

KEY = b'bi-mat-quan-trong'
msg = b"Action: Transfer; Amount: 1000"

# 署名の作成
sig = hmac.new(KEY, msg, hashlib.sha256).hexdigest()

# 整合性のチェック
def verify(message, received_sig):
    expected = hmac.new(KEY, message, hashlib.sha256).hexdigest()
    # compare_digestを使用してタイミング攻撃を防止
    return hmac.compare_digest(expected, received_sig)

検証と監視のベストプラクティス

よくある、しかし危険なミスは、ハッシュの比較に==演算子を使うことです。ハッカーはサーバーの応答時間を測定することで、1文字ずつ値を推測できてしまいます(タイミング攻撃)。処理時間が常に一定になるよう、必ずhmac.compare_digest()を使用してください。また、不正アクセスの兆候を検知するために、Discord Webhookによる通知を導入するのも効果的です。

重要な注意点:

  • ハッシュ化されていないパスワードやソルト(Salt)をシステムログに出力しない。
  • パスワードリセット要求が急増(例:1分間に100回以上)した場合は、攻撃アラートを有効にする。
  • ハードウェアの進化に合わせて、2年ごとにPBKDF2の反復回数を増やす。

APIのデバッグ中に見慣れないBase64文字列に遭遇したときは、Base64 Decoderを使って内容を素早く確認しています。このツールもクライアント側で処理されるため、APIキーやトークンが漏洩する心配がなく安心です。

セキュリティは一度設定して終わりではありません。それは日々の丁寧なコーディング習慣です。適切なデザインパターンを意識し、安全なPythonアプリケーションを構築していきましょう!

Share: