対策・学習12分で読める

パスワードの安全な保存方法:ハッシュ化とソルトの正しいやり方

ユーザーのパスワードをデータベースにどう保存すべきか。ハッシュ化だけでは足りない理由、ユーザーごとのソルト、Argon2id・bcryptなど低速ハッシュの選び方と設定、既存システムの移行まで開発者向けに解説します。

本文を読む
公開日
更新日
目次

「ユーザーのパスワード、データベースにどう保存するのが正解?」は、サービスを作る人が必ず一度ぶつかる問いです。答えははっきりしています。攻撃手順には触れず、安全な保存のしかただけを順番に解説します。

なぜ平文・暗号化・素のハッシュがダメなのか

データベースはいつか漏れる前提で考えます。そのとき、保存方式ごとに被害がまるで違います。

  • 平文:漏れた瞬間に全パスワードが読まれる。さらに使い回しにより、利用者の他サービスまで連鎖的に乗っ取られる。最悪。
  • 暗号化(可逆):鍵があれば元に戻せる=鍵が一緒に漏れれば平文と同じ。パスワードは読み返す必要がないので、そもそも可逆である意味がない。
  • 素のハッシュ(MD5/SHA-256):速いハッシュは、攻撃者が大量の候補を高速に試せる。よくあるパスワードはレインボーテーブルや総当たりで戻されてしまう。

安全な保存方法までの4段階

考え方は、弱い方式に一手ずつ対策を足していく流れで理解すると早いです。

① 平文:漏れたらすぐ全パスワードが読まれる
↓ 一方向に
② 素のハッシュ:レインボーテーブルに弱い
↓ ユーザーごとのソルトを足す
③ ソルト付きハッシュ:表は無効化、でも総当たりは速いまま
↓ わざと遅くする
④ ソルト付き+低速ハッシュ(Argon2id / bcrypt):これが正解
平文 → 素のハッシュ → ソルト付き → 低速ハッシュ。最後まで行って初めて『安全な保存』になる。

ポイントは、ソルトと低速ハッシュは役割が違うことです。ソルトは「事前計算(レインボーテーブル)と使い回し一括解析」を無効にする。低速ハッシュは「総当たりの速度を現実的でないレベルに落とす」。両方そろって初めて安全になります(→ ハッシュ化とは)。

実践:今やること

1

Argon2id(または bcrypt)を使う

新規なら Argon2id を第一候補に。メモリ消費も要求できるためGPU総当たりに強い。既存の実績ある実装を重視するなら bcrypt でも実用上十分。どちらも標準ライブラリの実装をそのまま使う。

2

ソルトはライブラリに自動生成させる

bcrypt/Argon2 は、利用者ごとのソルトを自動生成し、ハッシュ文字列の中に同梱する。自分でソルト列を持つ必要はない。手で MD5(salt + password) を組むのは、やってはいけない典型。

3

コスト(強度)を環境に合わせて設定する

bcrypt のコスト係数、Argon2 のメモリ・反復・並列度を、正規ログインが体感で気にならない範囲で最大になるよう設定する。サーバーが速くなるほど攻撃者も速くなるので、年単位で見直して引き上げる。

4

検証は標準の照合関数で

ログイン時は、保存済みハッシュと入力をライブラリの照合関数で突き合わせる(多くがタイミング差に配慮した定数時間比較になっている)。自前で文字列一致を書かない。

5

弱いハッシュは“ログイン時に再ハッシュ”で移行

既に MD5/SHA-256 で保存済みなら、ユーザーがログイン成功した瞬間に新方式で再ハッシュして保存し直す。未ログイン分は、既存ハッシュをさらに bcrypt 等でハッシュ化する二重ハッシュで、暫定的に強度を上げられる。

やりがちな失敗 vs 正しい実装

やりがち

  • パスワードを暗号化して保存(鍵が漏れたら終わり)
  • MD5(password) や SHA-256(password) をそのまま保存
  • 全ユーザー共通のソルトを1個だけ使う
  • 自前で hash(salt + password) を手組み

正しい実装

  • Argon2id / bcrypt で一方向ハッシュ
  • ソルトは利用者ごと(ライブラリが自動付与)
  • コストは体感OKな範囲で最大、定期的に引き上げ
  • 標準のハッシュ/照合関数をそのまま使う

ハッシュ化・暗号化・エンコードの違い

3つは混同されやすいですが、元に戻せるかどうかがまったく違います。パスワードの保存に使ってよいのはハッシュ化だけです。

ハッシュ化暗号化エンコード(Base64など)
元に戻せるか戻せない(一方向)鍵があれば戻せる誰でも戻せる
鍵不要必要不要
主な用途パスワードの保存、改ざん検知通信やファイルの秘匿データの形式変換
パスワード保存に使う(ソルト+低速ハッシュ)使わない使わない(平文と同じ)

推奨される設定値(OWASP)

OWASP Password Storage Cheat Sheet が示す最低限の設定です。これより弱くしないことを目安に、サーバーの性能に合わせて引き上げます。

方式最低限の設定向いている場面
Argon2idメモリ19MiB・反復2回・並列度1新規開発の第一候補
scryptN=2^17(128MiB)・r=8・p=1Argon2idが使えないとき
bcryptコスト10以上。入力は72バイトまで既存システム・実績重視
PBKDF2-HMAC-SHA256反復60万回以上FIPS 140の準拠が必要なとき

主な言語・フレームワークの標準機能も確認しておきます。PHPの password_hash() は既定(PASSWORD_DEFAULT)でbcryptを使い、PHP 8.4から既定のコストが10から12に上がりました。Djangoは既定でPBKDF2を使い、django[argon2] を入れて PASSWORD_HASHERS の先頭をArgon2にすると切り替えられます。Djangoは、ログイン時に古い方式のハッシュを自動で新しい方式に作り直します。

自分のサービスで確認する方法

保存済みのハッシュは、文字列の先頭を見れば方式がおおよそ分かります。本番データを外に持ち出さず、自分の管理する環境で数件だけ確認してください。

ハッシュの見た目方式対応
$argon2id$v=19$m=…,t=…,p=…$Argon2idm・t・pがOWASPの最低限以上か確認
$2y$12$… / $2b$10$…bcrypt(数字がコスト)10未満なら引き上げ
pbkdf2_sha256$1000000$…PBKDF2(数字が反復回数)60万回以上か確認
16進数32文字MD5の可能性が高いログイン時の再ハッシュで移行
16進数40文字/64文字SHA-1/SHA-256の可能性が高い同上
ログインに使う文字列そのまま平文最優先で移行。漏えい時の影響を確認

あわせて、次の点も確かめます。

  • アプリやWebサーバーのログ、エラー通知、分析ツールに、入力されたパスワードが記録されていないか
  • bcryptを使っている場合、72バイトを超える入力をどう扱うか決めてあるか
  • バックアップやテスト用に複製したデータベースにも、同じ方式のハッシュしか入っていないか

具体例:平文保管が被害を広げた事件

宅ふぁいる便(2019)では、サーバーへの不正アクセスで約481万件の個人情報が流出し、そのなかのログインパスワードは平文で保管されていました。ソルト付きの低速ハッシュで保存していれば、流出したパスワードをそのまま他サービスで使われる危険は大きく下がっていたはずです。侵入を完全に防げなくても、保存方法で被害の大きさは変えられることを示す例です。

当サイトの視点:パスワード保存は実績ある標準実装をそのまま使う

パスワード保存は、独自性を出してよい領域ではありません。世界中で検証された Argon2/bcrypt の標準実装をそのまま使うのが、最も安全で、運用も楽です。当サイトの立場は「ここで創意工夫しない」。むしろ力を入れるべきは、そもそもパスワード依存を減らすことです。具体的には多要素認証(MFA)の徹底や、将来的なパスキー(パスワードレス)への移行です。最強のハッシュでも、弱いパスワードと使い回しは守り切れません。

次に読む

出典

  • OWASP「Password Storage Cheat Sheet」: cheatsheetseries.owasp.org
  • NIST「SP 800-63B-4(2025年8月)」パスワードの保存(ソルトとハッシュ): pages.nist.gov
  • RFC 9106「Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications」: rfc-editor.org
  • PHP マニュアル「password_hash」: php.net
  • Django ドキュメント「Password management in Django」(5.2): docs.djangoproject.com

よくある質問

Qパスワードは暗号化して保存すればいいですか?
A

いいえ、暗号化は不向きです。暗号化は鍵があれば元に戻せる(復号できる)ので、鍵が漏れれば全パスワードが平文に戻ります。パスワードは運営側でも読み返す必要がないため、元に戻せない『ハッシュ化』が正解です。ただし素のハッシュでは不十分で、ユーザーごとのソルトと低速ハッシュ(bcrypt/Argon2)を組み合わせます。

Qbcrypt と Argon2、どちらを使うべきですか?
A

新規開発なら Argon2(特に Argon2id)が第一候補です。メモリと計算量の両方を調整でき、GPUでの総当たりに強いためです。既存資産や実績のある実装を重視するなら bcrypt も十分実用的です。重要なのは、どちらも『標準ライブラリの実装をそのまま使い、自前で組まない』ことです。

QすでにMD5やSHA-256で保存してしまっています。どう移行しますか?
A

一括で平文に戻すことはできない(それが利点)ので、ログイン時に移行します。ユーザーがログインに成功した瞬間、入力された正しいパスワードを新方式(Argon2id等)で再ハッシュして保存し直します。未ログインのユーザーは、既存ハッシュをさらに bcrypt 等でハッシュ化する二重ハッシュで、暫定的に強度を上げる方法もあります。

Qソルトとペッパーの違いは何ですか?
A

ソルトは利用者ごとに変えるランダムな値で、ハッシュと一緒にデータベースへ保存します。秘密にする必要はありません。ペッパーは全利用者に共通の秘密の値で、データベースとは別の場所(シークレット管理サービスやHSMなど)に置きます。OWASPはペッパーを、データベースだけが漏れた場合の追加の防御として位置づけています。必須なのはソルトで、ペッパーは任意の上乗せです。

Qbcryptの72バイト制限とは何ですか?
A

bcryptは入力の先頭72バイトまでしか使わない仕様です。それより長いパスワードは、73バイト目以降が無視されます。日本語などのマルチバイト文字は1文字で3バイト程度になるため、思ったより早く上限に達します。新規ならこの制限がないArgon2idを選ぶか、bcryptを使う場合は入力の長さの上限を決めておきます(OWASP)。

Qソルトは秘密にしておく必要がありますか?
A

いいえ。ソルトの役割は、同じパスワードでも利用者ごとに違うハッシュにして、事前に計算した表や一括での解析を使えなくすることです。秘密である必要はなく、bcryptやArgon2はハッシュ文字列の中にソルトを含めて保存します。NIST SP 800-63B-4は、ソルトを32ビット以上にするよう求めています。