パスワードのハッシュ化と暗号化の違いは、事故が起きたときに初めて差が出ます。暗号化は鍵で元に戻せる処理なので、鍵と復号の仕組みを盗まれると、保存していた全件が平文に戻ります。ハッシュ化は戻す手段を持たない処理で、攻撃者には推測を1件ずつ試す道しか残りません。
連載「事故から逆算するWebセキュリティ」の第1回は、2026年10月のソースネクストの情報漏洩を入口に、パスワードを何で保存するかを整理します。
STORE, DON'T DECRYPT保存のしかた
鍵で戻せる保存は、鍵と一緒に全件が戻る
暗号化して保存
AES などの可逆な方式
- 鍵と復号の仕組みが盗まれると全件が平文に
- 実測: 10万件の復号に0.07秒
- サーバー側がパスワードを知れてしまう
遅いハッシュ+ソルトで保存
Argon2id / scrypt / bcrypt / PBKDF2
- 元に戻す仕組みがそもそも無い
- 推測を1件ずつ試すしかない
- 1回の計算を重くして試行を遅らせる
30秒でわかる(2026年10月17日時点)
- ・暗号化:鍵があれば戻る。当サイトの実測で、AES-GCMの10万件は0.07秒で全件復号できた
- ・ハッシュ化:照合は入力を同じ条件で計算して比べるだけ。元に戻す必要がない
- ・それでも足りない:SHA-256は1コアで1秒に約210万回計算できる。ソルトとコストを持つArgon2idは約39回
- ・対策:Argon2id(19MiB・2回・並列1)を第一に。暗号化で保存しているなら全件を復号してハッシュ化し、鍵を消す
事案の経緯は ソースネクスト情報漏洩の記事 に、インシデントの読み方は サプライチェーン攻撃とは|手口・防御ツール比較・npm 12の新機構まで実践解説 にまとめています。
ソースネクストの説明とPCI DSSの読み方のタイムライン
| 日付 | 出来事 |
|---|---|
| 2026-10-15 | ソースネクストが会員情報の流出の可能性(最大22,519,960件)を公表。項目にパスワードを含む |
| 10-15以降 | 報道で、同社の説明として「パスワードは暗号化して保存していたが、暗号化を解除するプログラムもハッキングされた」と伝えられる |
| 10-15以降 | X上で、PCI DSS 3.2.1 要件8.2.1の文言が「暗号化」を求めているように読める点が議論になる |
この説明の文言は、当サイトが検索結果に載った報道の本文で確認したもので、同社の発表本文では確認できていません。発表本文は保存形式を説明していないとされます。ここでは「暗号化して保存し、復号の仕組みごと取られた」という説明を前提に、保存方式の側から考えます。
X上の議論では、PCI DSS 3.2.1 の要件8.2.1「強力な暗号化を使用して、すべてのシステムコンポーネントで、送信と保存中に認証情報(パスワードなど)をすべて読み取り不能にする」(当サイト訳)を字面どおり読むと「パスワードを暗号化せよ」に見え、可逆な暗号化を選ぶ理由になったのではないか、という見方が出ていました。
規格の側を確かめると、PCI DSS v4.0 では同じ趣旨が要件8.3.2に移り、対象は「認証要素」に広がっています。PCI SSC の FAQ 1253(2025年7月)は、強力な暗号を使ってパスワードをハッシュ化すれば8.3.2の意図を満たすと答えています。規格が求めるのは「読み取り不能」であって、戻せる暗号化ではありません。
・要件の文言は、規格の対応表を載せた複数のサイトの本文で確認した
・FAQ 1253 の回答は、PCI SSC のページが開けず検索結果の本文で確認した
・v4.0.1 の8.3.2のガイダンスがソルトに触れているかは未検証
なお v4.0 で加わった「鍵付きハッシュ」の要件は、カード番号(PAN)を保存するときのもので、パスワードの要件ではありません(PCI SSC の FAQ を検索結果で確認)。
パスワードのハッシュ化と暗号化の違い
X上では、初心者向けにこんな説明が広く読まれていました。暗号化は鍵で元に戻せるが、ハッシュ化は戻せない。password123 をハッシュ化すると全く違う文字列になる。戻せないのにログインをどう判定するのかといえば、入力されたパスワードを登録時と同じ方法でハッシュ化し、保存値と照合すればよい。
ここまでは正しい出発点です。ただ「ハッシュ化したから安全」で止まると、事故は防げません。足りないのは2点です。
・ソルト:パスワードごとに違う乱数を混ぜる。ないと同じパスワードは同じ値になり、事前計算の表(レインボーテーブル)で一度に引かれる。使い回している会員も値の一致で一度に分かる
・コスト:1回の計算をわざと重くする。SHA-256 のような速いハッシュは、総当たりや辞書攻撃で1秒に何百万回も試せる
HOW LOGIN CHECKS照合のしかた
元に戻さず、同じ計算をして比べる
-
入力を受け取る
ログイン画面から
-
保存値を読む
方式・コスト・ソルト入り
-
同じ条件で計算
登録時と同じ設定
-
一定時間で比べる
verify 関数に任せる
-
一致ならログイン
古いコストは作り直す
「登録時と同じ方法で」の中身も、実装では注意が要ります。Argon2id や bcrypt の保存値には、方式・コスト・ソルト・ハッシュがひと続きの文字列で入っています。照合では、この文字列と入力をライブラリの verify 関数に渡すだけで、ソルトを自分で取り出して計算し == で比べる実装は書きません。比べ終わるまでの時間が一致した位置で変わると、手がかりを与えるからです。argon2-cffi が使う libargon2 の比較関数は、全バイトを最後まで比べる一定時間の実装でした(ソースで確認)。
| 方式 | 鍵やDBを盗まれたとき | 同じパスワードが分かるか | 1回の計算 |
|---|---|---|---|
| 平文 | そのまま読める | 分かる | 不要 |
| 暗号化(AES など) | 鍵と復号の仕組みがあれば全件が戻る | 分かる場合がある | ごく軽い |
| 速いハッシュ(SHA-256 単発) | 推測を高速に試せる | ソルトなしなら分かる | ごく軽い |
| 遅いハッシュ+ソルト | 1件ずつ重い計算で試すしかない | 分からない | 数十ミリ秒〜 |
| 上に加えてペッパー | DBだけでは推測も試せない | 分からない | 同上+鍵付き処理 |
速いハッシュでは足りない理由を実測する
1コアの Python で、パスワード1件のハッシュ計算が1秒に何回できるかを測りました。2026年10月17日、Xeon 2.1GHz・Python 3.13.16・argon2-cffi 25.1.0・bcrypt 5.0.0 です。実際に実行したコードは次のとおりです(pip install argon2-cffi==25.1.0 bcrypt==5.0.0 の後に python3 rate.py)。
import hashlib, time, bcrypt
from argon2 import PasswordHasher
def rate(f, n):
t = time.perf_counter()
for _ in range(n):
f()
return n / (time.perf_counter() - t)
pw = b"password123"
ph = PasswordHasher(time_cost=2, memory_cost=19456, parallelism=1)
print("SHA-256 :", round(rate(lambda: hashlib.sha256(pw).digest(), 1_000_000)), "回/秒")
print("Argon2id :", round(rate(lambda: ph.hash(pw), 20), 1), "回/秒")
print("bcrypt(10) :", round(rate(lambda: bcrypt.hashpw(pw, bcrypt.gensalt(10)), 20), 1), "回/秒")
print(hashlib.sha256(pw).hexdigest() == hashlib.sha256(pw).hexdigest()) # True: 同じ値
print(ph.hash(pw) == ph.hash(pw)) # False: ソルトで毎回違う
MEASURED実測
SHA-256 は 1 秒に約210万回試せる。Argon2id は約39回
210万回/秒
SHA-256(ソルトなし)
速いハッシュ。総当たりに向いてしまう
39回/秒
Argon2id m=19MiB t=2 p=1
OWASP の最小推奨値。約5万分の1
0.07秒
AES-GCM 10万件の復号
鍵があれば暗号化は一瞬で戻る
| 方式(パラメータ) | 1秒あたりの計算回数(2〜3回の実行) |
|---|---|
| SHA-256(ソルトなし) | 約202万〜219万回 |
| Argon2id(m=19MiB, t=2, p=1) | 37.2〜39.4回 |
| bcrypt(cost=10) | 12.7〜13.1回 |
| bcrypt(cost=12) | 3.2〜3.3回 |
| PBKDF2-HMAC-SHA256(60万回) | 5.5〜5.9回 |
| scrypt(N=2^17, r=8, p=1) | 1.9〜2.1回 |
SHA-256 と Argon2id の差は約5万倍でした。これは1コアの CPU の数字で、GPU を並べる攻撃者の速度ではありません。それでも、同じ機械で試せる推測の数が5万分の1になる、という比は変わりません。陽性対照として、SHA-256 は同じ入力で毎回同じ値、Argon2id はソルトで毎回違う値になることも確かめています。
暗号化の側も測りました。AES-256-GCM で10万件を暗号化しておき、同じ鍵で全件を復号すると0.07秒でした。暗号の強さは関係なく、鍵と復号の手順が手に入れば、この速さで全件が戻ります。
パスワードのハッシュ化の推奨値:OWASPとNIST
OWASP Password Storage Cheat Sheet(2026年10月17日時点の公式リポジトリ)は、「ほぼすべての場合、パスワードは暗号化でも平文でもなく、現代的な適応型ハッシュでハッシュ化する」としています。暗号化を使うのは、他のシステムへのログインに元のパスワードが要るような例外だけで、その場合も OpenID Connect などで保存しない設計を先に探すよう書いています。
| 方式 | OWASP の推奨(最小) | 位置づけ |
|---|---|---|
| Argon2id | m=19MiB・t=2・p=1(ほか46MiB・t=1など同等の5通り) | 第一候補 |
| scrypt | N=2^17・r=8・p=1(ほか4通り) | Argon2id が使えないとき |
| bcrypt | cost 10以上、入力は72バイトまで | 既存システム向け |
| PBKDF2 | HMAC-SHA256で60万回(SHA512は22万回) | FIPS-140 が必要なとき |
NIST SP 800-63B-4(2025年8月)の3.1.1.2は、パスワードを「オフライン攻撃に耐える形」で保存することを求めています。公式の原稿リポジトリで確認した要点は次の3つです。
・ソルトを付け、パスワードハッシュ方式でハッシュ化する(SHALL)。ソルトは32ビット以上
・コストは性能を損なわない範囲でできるだけ高くし、計算機の進歩に合わせて上げる(SHOULD)
・検証側だけが知る秘密鍵で鍵付きハッシュか暗号化をもう1段かける(SHOULD)。鍵はハッシュと別に、HSM など保護された領域に置く
3つ目がペッパーです。OWASP も、ペッパーはハッシュと同じ場所に置かず、シークレット保管庫か HSM に置くとしています。DB だけが漏れた場合は、推測を試すことすらできなくなります。ペッパーを替えるには全員のパスワード再設定が要る点も同じく書かれています。鍵をどこに置けば守れるかは、連載の次回で扱います。
対策:照合の実装と、暗号化からの移行
照合と作り直しは、ライブラリの関数に任せます。次のコードは実行して出力を確かめたものです(argon2-cffi 25.1.0)。
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
ph = PasswordHasher(time_cost=2, memory_cost=19456, parallelism=1)
stored = ph.hash("password123") # 登録時:この文字列だけを保存する
print(stored)
try:
ph.verify(stored, "password124") # ログイン時:保存値と入力を渡すだけ
except VerifyMismatchError:
print("不一致")
print(ph.verify(stored, "password123"))
new_ph = PasswordHasher(time_cost=3, memory_cost=19456, parallelism=1)
print(new_ph.check_needs_rehash(stored)) # コストを上げたら True → 次のログインで作り直す
出力は $argon2id$v=19$m=19456,t=2,p=1$<ソルト>$<ハッシュ>・不一致・True・True でした。コストを上げたら、ログインに成功したときに check_needs_rehash を見て、その場でハッシュを作り直して保存します。
今のシステムの保存方式ごとに、移行の手順は変わります。
| 今の保存方式 | 移行の手順 |
|---|---|
| 可逆な暗号化 | 全件を復号して、その場で Argon2id にする(ログインを待たなくてよい)。終わったら暗号文と鍵を、バックアップも含めて消す |
| 速いハッシュ(MD5・SHA-1・SHA-256 単発) | 既存の値を Argon2id で包み、次のログインで入力から直接ハッシュし直す。長く使われていない会員は期限切れにして再設定を求める |
| bcrypt・PBKDF2 の低いコスト | 保存値のコストを見て、ログイン成功時に新しいコストで作り直す |
| すでに漏えいした可能性がある | 上に加えて、全員にパスワードの再設定を求める |
漏えいの告知を受けた利用者の側の備え(使い回しの洗い出し)は、メルカリ情報漏洩の主張の記事 にまとめています。
包む方式(OWASP の言う Layering)は、元のパスワードを知らずに全件を一度に強くできますが、二重のハッシュは単体より割れやすくなることがあるため、次のログインで置き換えるまでのつなぎです。暗号化からの一括変換は、平文が一時的にメモリに載るので、ログを残さない隔離した処理で行うのが無難です(当サイトの判断)。
影響を受けるシステムの見分け方
保存値の形で、影響範囲をおおよそ見分けられます。
| 保存値の形 | 方式 | 判断 |
|---|---|---|
$argon2id$v=19$m=… |
Argon2id | パラメータが推奨値以上か見る |
$2b$10$…($2a$・$2y$) |
bcrypt | cost が10以上か見る |
pbkdf2_sha256$600000$… |
PBKDF2(Django 形式) | 回数が60万以上か見る |
| 32・40・64桁の16進数だけ | MD5・SHA-1・SHA-256 の可能性 | ソルトの有無を確認し、包んで移行 |
| 長さがばらつく Base64 で、復号処理がコードにある | 可逆な暗号化 | 一括変換して鍵を消す |
コード側では、保存処理の周りに encrypt・decrypt・AES・Cipher があるか、パスワードを画面やメールで本人に返す機能があるかを見ます。パスワードを「教えられる」システムは、元に戻せる形で持っています。
参照ソース
- OWASP Password Storage Cheat Sheet(公式リポジトリ OWASP/CheatSheetSeries の原稿で2026-10-17に確認)
- NIST SP 800-63B-4 Digital Identity Guidelines: Authentication and Authenticator Management(2025年8月)3.1.1.2 Password Verifiers(公開ページは開けず、公式原稿リポジトリ usnistgov/800-63-4 の同じ節で確認)
- PCI SSC FAQ 1253「Does hashing of passwords meet the intent of PCI DSS Requirement 8.3.2?」(この環境から開けず、検索結果の本文で確認)
- RFC 9106: Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications(パラメータの定義)
- ソースネクスト「当社サイトへの不正アクセスによるお客様情報流出のお詫びとお知らせ」2026-10-15(当サイトの環境からは開けず、検索結果に載った本文で確認)





