ログインのある Web アプリを作るときは、パスワードを「受け取って、保存する側」になります。保存のしかたの結論は、パスワードをそのまま(平文で)も、元に戻せる暗号化でも、速いハッシュ(SHA-256 や MD5)でも保存せず、ソルトを付けて、パスワード用の遅いハッシュ方式で保存することです。ハッシュとは、入力から決まった形の値を作る計算で、作った値から元の入力には戻せません。ソルトとは、パスワードごとに違う、ランダムな値のことです。
受け取る側の決まりは、NIST(米国の標準を決める機関)の SP 800-63B-4 に新しい形でまとまっています。パスワードだけで認証するなら 15 文字以上を求め(SHALL)、定期的な変更を求めず(SHALL NOT)、文字の種類の組み合わせを強いない(SHALL NOT)、という内容です。ただし、この文書は米国の政府の情報システム向けの指針で、日本の法律の決まりではありません。
この記事は、パスワードを受け取り、保存する側になる初心者〜若手の人に向けて、保存のしかたと NIST の決まりを書いたものです。使う側の「強いパスワードの作り方」は主題にしていません。NIST SP 800-63B-4、OWASP の Password Storage Cheat Sheet、Python の文書、デジタル庁と個人情報保護委員会の資料を、2026年10月20日に開いて確かめています。コードは Python 3.12.3(macOS)で流した手元の出力です。Argon2id・bcrypt・PBKDF2 は流していません(OWASP の文書を読んだだけです)。
- パスワードを平文・暗号化・速いハッシュ(SHA-256 など)で保存してはいけない理由
- ソルトとペッパーとは何か。ソルトが違うと、同じパスワードでも結果が変わること(Python で確認)
- 2026年10月20日に開いた時点の OWASP の文書が書く、ハッシュ方式の選び方と設定の目安
- NIST SP 800-63B-4 が、パスワードを受け取る側に求めること(15 文字・定期変更しない・漏れたパスワードとの照合など)と、SHALL と SHOULD の違い
- 試行回数の制限と多要素認証。日本の資料(個人情報保護委員会)との読み比べ
パスワードはどう保存する?平文・暗号化・速いハッシュがだめな理由
OWASP の Password Storage Cheat Sheet は、冒頭で次のように書いています。
Passwords should never be stored in plain text. Instead, they must be protected using strong, slow hashing algorithms such as Argon2id, bcrypt, or PBKDF2. A unique salt must be added to each password to prevent attackers from using precomputed lookup tables like rainbow tables. Fast hashing algorithms such as SHA‑256 are not suitable for password storage because they allow attackers to perform large numbers of guesses quickly.
訳すと、「パスワードを平文で保存してはいけない。強くて遅いハッシュ方式(Argon2id・bcrypt・PBKDF2 など)で守る。パスワードごとに違うソルトを付ける。SHA-256 のような速いハッシュは、多くの推測を速く回されるので向かない」という意味です。OWASP とは、Web の安全に取り組む団体です。
保存のしかたを、4つに分けて比べます。
| 保存のしかた | どうなるか | OWASP の評価 |
|---|---|---|
| 平文 | パスワードがそのまま入っている | 保存してはいけない |
| 暗号化 | 双方向の計算なので、元に戻せる。攻撃者が平文を取り戻せる | ハッシュにする。暗号化するのは、他のシステムにそのパスワードで入る必要があるような例外だけ |
| 速いハッシュ(MD5・SHA-1・SHA-256) | 一方向だが、計算が速い。推測を大量に回せる | パスワードの保存には向かない |
| 遅いハッシュ(パスワード用の方式) | 一方向で、計算も遅くできる | これを使う |
ハッシュと暗号化は何が違う?
OWASP は、ハッシュと暗号化の違いを次のように書いています。
- ハッシュは一方向の計算で、「復号」して元の値を得ることはできません。だからパスワードの照合に最も向いていて、ハッシュを盗まれても、そのハッシュでは本人としてログインできません
- 暗号化は双方向の計算なので、攻撃者は暗号化されたデータから元の平文を取り出せます
ログインのときは、入力されたパスワードに保存と同じ計算をして、保存した値と比べます。保存した値から元のパスワードを取り出す必要はありません。
「元に戻せない」だけでは足りない理由
ハッシュが破られるしくみを、OWASP は次の繰り返しで説明しています。
- 相手が選んだと思うパスワードを推測する(例:
password1!) - そのハッシュを計算する
- 盗んだハッシュと比べる。同じなら当たり
推測の元になるのは、他のサイトから漏れたパスワードの一覧、総当たり、よくあるパスワードの辞書です。GPU(画像用の計算装置)や、借りられるクラウドを使えば、攻撃者の費用は比較的小さい(特に、ハッシュのよいやり方が守られていないとき)、と OWASP は書いています。
NIST も、同じ状況を「オフラインの攻撃」として書いています。データベースの漏えいでハッシュが盗まれると、試行回数の制限が効かない場所で、毎秒何十億ものハッシュを計算できます(原文は "many billions of hashes per second")。その場合、オンラインの攻撃だけを防ぐ場合よりも、はるかに複雑なパスワードが要る、という説明です。なお、この「何十億」は NIST の書き方で、この記事で手元で測った数とは別のものです。
だから、パスワードのハッシュには、わざと遅く作られた方式を使います。OWASP は、「パスワード用のハッシュは遅いべきで(MD5 や SHA-1 は速く作られたのと違って)、work factor(コスト。計算の重さの設定)で遅さを変えられる」と書いています。Python の文書も、sha1(password) のような素朴な方法は総当たりに弱く、よいパスワード用のハッシュ関数は「調整でき、遅く、ソルトを含む」ものだと書いています。
ソルトとペッパーとは?
ソルト:パスワードごとに違う、ランダムな値
OWASP は、ソルトを「ハッシュの計算の一部として、パスワードごとに足す、ユニークでランダムに作った文字列」と説明しています。ソルトは利用者ごとに違うので、攻撃者は、1回計算したハッシュを全員の保存分と比べる、という近道が使えず、ソルトごとに1つずつ破るしかありません。事前に計算した表(レインボーテーブル)も使えなくなります。
もう1つの効果もあります。同じパスワードの人がいても、ソルトが違えばハッシュが違うので、ハッシュを破らない限り、2人のパスワードが同じかどうかを見分けられません。
OWASP は、多くのライブラリがソルトを自動で作って管理するので、正しく使えば、開発者が自分で作る必要はふつうない、とも書いています。ただし、次の節で使う Python の hashlib.scrypt は、salt を自分で渡す関数です。その場合について、Python の文書は、os.urandom() のような適切な乱数から作るよう書いています。手元の台本では secrets.token_bytes(16) を使いました。secrets は、パスワードやトークン向けの、暗号に使える強さの乱数を作るモジュールです(random の代わりに使います)。
ソルトの長さは、NIST と Python の文書で、書き方が違います。
| 資料 | 書いてあること |
|---|---|
| NIST SP 800-63B-4 | ソルトは 32 ビット以上で、保存したハッシュの間でぶつかりが少なくなるように選ぶ(SHALL)。パスワードごとに、ソルトと結果の両方を保存する(SHALL)。方式とコストの記録も保存すべき(SHOULD。あとで方式やコストを変えるため) |
| Python の文書(3.12) | ソルトは 16 バイトくらい以上を、os.urandom() のような適切な乱数から |
16 バイトは 128 ビットなので、NIST の最低の線(32 ビット)を満たします。この記事の手元のコードは 16 バイトにしました。
ペッパー:全員で共通の秘密
ペッパーは、OWASP の説明では、保存されたすべてのパスワードで共通の秘密です。ソルトは1つのパスワードだけのもの、ペッパーは全員で共通のもの、という違いがあります。
- ハッシュと一緒に保存せず、別の場所(秘密の保管庫や、鍵を守る専用の装置)に置く
- データベースだけを取られたとき(SQL インジェクションやバックアップの流出)に、どのハッシュも破らせない効果がある
- 変えるには、全員のパスワードの再設定が要る(ペッパーは、利用者のパスワードを知らないと変えられないため)
ただし OWASP は、ペッパーを考えるのは「多層の守り」のためで、それだけでは安全にならない("alone, it provides no additional secure characteristics")とも書いています。
NIST も似たことを書いています。ハッシュに加えて、照合する側だけが知る秘密の鍵で、もう一段の鍵付きハッシュか暗号化をすべき(SHOULD)。使うなら、鍵は認められた乱数で作り(SHALL)、ハッシュとは別の場所に置き(SHALL)、ハードウェアで守られた領域(HSM や TEE)に置くべき(SHOULD)という内容です。NIST の文書には「ペッパー」という語は出てきません。OWASP の「ペッパー」に当たる話として読めます。
秘密の値をコードやリポジトリに入れず、別の場所に置く考え方は、.env と環境変数の記事にまとめています。
どのハッシュ方式を選ぶ?OWASP の勧め(2026年10月時点)
OWASP の Cheat Sheet は、2026年10月20日に開いたものを読んでいます。元の Markdown の最後の変更は、2026年10月4日です。書き換わることがある文書なので、設定の数字を決めるときは、最新の文書をもう一度開いてください。
OWASP のまとめは、次の5つです。
| 順 | 場面 | 方式と、OWASP が書く最小の設定 |
|---|---|---|
| 1 | まず選ぶもの | Argon2id。メモリ 19 MiB・反復 2・並列 1 以上 |
| 2 | Argon2id が使えないとき | scrypt。N=2^17(128 MiB)・r=8・p=1 以上 |
| 3 | 古いシステムで、Argon2 も scrypt も使えないとき | bcrypt。work factor 10 以上。パスワードは 72 バイトまで |
| 4 | FIPS-140(暗号の部品を検証する規格)が要るとき | PBKDF2。HMAC-SHA-256 で 600,000 回以上 |
| 5 | 多層の守りとして | ペッパーを考える(それだけでは安全にならない) |
方式の説明も、OWASP の原文の範囲で書いておきます。
- Argon2id:2015年の Password Hashing Competition(パスワード用のハッシュ方式の公募)の勝者 Argon2 の型の1つです。3つの型のうち id を使います。側面からの攻撃(サイドチャネル)と GPU の攻撃の両方に、バランスよく強いためです。最小の組は、
m=47104(46 MiB)t=1 p=1、m=19456(19 MiB)t=2 p=1、m=12288(12 MiB)t=3 p=1、m=9216(9 MiB)t=4 p=1、m=7168(7 MiB)t=5 p=1の5つで、どれも同じ強さです。違いは CPU とメモリの配分だけです。並列(p)を上げても、メモリは増えず、反復の代わりにもなりません - scrypt:Argon2id が使えないときに使います。同じくらいの守りの組は、
N=2^17(128 MiB)r=8 p=1、N=2^16(64 MiB)r=8 p=2、N=2^15(32 MiB)r=8 p=3、N=2^14(16 MiB)r=8 p=5、N=2^13(8 MiB)r=8 p=10です - bcrypt:Argon2 と scrypt が使えない古いシステムだけに使います。入力はたいていの実装で 72 バイトまでです。速いハッシュで先にハッシュしてから bcrypt に渡すのは、null バイトや password shucking の問題があり、危ないと書かれています(OWASP は、どうしても先にハッシュするなら、HMAC を使い、ペッパーはデータベースに置かない形を示しています)
- PBKDF2:OWASP は、NIST SP 800-63B-4 が認めていて、FIPS-140 の検証済みの実装があるので、それが要るときの第一候補、と書いています。回数は、HMAC-SHA256 で 600,000(推奨)、HMAC-SHA512 で 220,000 です。HMAC-SHA1 の 1,400,000 回は古いものだけで、新しく選ばないとされています
OWASP の中でも、並べ方は少し違います。冒頭の文と、ハッシュと暗号化の節の例は「Argon2id, bcrypt, or PBKDF2」で、scrypt が入っていません。まとめと本文は、scrypt を Argon2id の次に置き、bcrypt は古いシステムだけとしています。順位を考えるときは、上の表(まとめの5行)に合わせます。
NIST は、ハッシュの方式を何と書いている?
NIST SP 800-63B-4 は、パスワードを「ソルトを付けて、適したパスワード用のハッシュ方式でハッシュする」(SHALL)と書いています。方式は「パスワード・ソルト・コストを入れると、ハッシュを出すもの」で、狙いは、ハッシュの一覧を盗んだ攻撃者にとって、1回の推測を高くつかせることです。コストは、性能を損なわない範囲で、できるだけ高く(SHOULD)、計算機が速くなるのに合わせて、上げていく(SHOULD)としています。方式は、SP 800-132 の最新版などで認められたものを使うべき(SHOULD)です。
ここで注意があります。Argon2id を勧めているのは OWASP の文書で、NIST の原文ではありません。NIST の原文が挙げているのは「適した方式」と SP 800-132 までです。SP 800-132 そのものは開いていません。NIST の本文には、Argon2・scrypt・bcrypt・PBKDF2 の語は出てきません(2026年10月20日に数えました)。OWASP が書いているのは、PBKDF2 は NIST SP 800-63B-4 が認めている、という点です。
work factor(コスト)はどう決める?
work factor は、ハッシュの計算を、どれだけ重くするかの設定です。OWASP は、次のように書いています。
- 高いほど破りにくくなりますが、ログインも遅くなります。高すぎると、ログインの試行を大量に送られて、サーバーの CPU を使い切らされる(DoS)ことに使われかねません
- 理想の値に決まった正解はなく、サーバーの性能と利用者の数で決まります。自分のサーバーで試します。一般的な目安は、1回のハッシュ計算が1秒未満です
- 上げるときは、利用者が次にログインしたときに、新しい work factor でハッシュし直すのがふつうです
使っている方式を隠す必要はない、とも書かれています。
Python の scrypt で、ハッシュを作って照合する(手元の出力つき)
Python の標準ライブラリで流せるのは、hashlib.scrypt(Python 3.6 で追加)です。Argon2 は標準ライブラリになく、手元の hashlib にもありませんでした。使うには外のパッケージが要りますが、パッケージは入れていないので、Argon2id は流していません。bcrypt も流していません。PBKDF2 は標準ライブラリの hashlib.pbkdf2_hmac で流せますが、今回は流していません。
手元の環境は、Python 3.12.3・OpenSSL 3.6.0・macOS 14.4.1(Apple M2 Max)です。Linux や Windows では流しておらず、そちらで同じ出力になるかは確かめていません。
例のパスワードには、correct horse battery staple を使います。これは xkcd の 936「Password Strength」に出てくる、広く知られた例の文字列(28 文字)です。公開の例なので、まさに漏れたパスワードの一覧に載る類いの文字列です。強いパスワードの例として勧めるものではありません。この記事では、動作を見るためだけに使います。
1. 既定の設定のままだと失敗する
OWASP の scrypt の最小の組(N=2^17・r=8・p=1)は、メモリを 128 MiB 使います。一方、hashlib.scrypt の maxmem(使ってよいメモリの上限)の既定は、Python の文書によると、OpenSSL 1.1.0 で 32 MiB と書かれています。手元(OpenSSL 3.6.0)でも、既定のまま渡すと次のように失敗しました。
先に、台本の頭の部分です。以降のコードは、台本から、時間を測る行と版を表示する行(と、ここで使わない import)を除いて抜粋しています。出力の番号は台本の通し番号で、3)・4) の行は省きました。
import hashlib, hmac, secrets
PW = "correct horse battery staple" # xkcd 936 の公開の例
N, R, P = 2**17, 8, 1 # OWASP の scrypt の最小の組の 1 つ
MAXMEM = 256 * 1024 * 1024 # 既定 (32 MiB) では足りないので広げる
def make(password, salt):
return hashlib.scrypt(password.encode("utf-8"), salt=salt, n=N, r=R, p=P,
maxmem=MAXMEM, dklen=32)
maxmem を広げた make を使えば通りますが、maxmem を渡さずに呼ぶと、次のように失敗しました。
try:
hashlib.scrypt(PW.encode(), salt=b"x" * 16, n=N, r=R, p=P)
except Exception as e:
print("1) default maxmem:", type(e).__name__, e)
1) default maxmem: ValueError [digital envelope routines] memory limit exceeded
dklen=32 は、出力の長さ(32 バイト)です。この値は手元の台本で選んだもので、OWASP や NIST に scrypt の出力の長さの指定は見つけていません。
2. ソルトが違えば、同じパスワードでも結果が違う
salt_a = secrets.token_bytes(16)
salt_b = secrets.token_bytes(16)
h_a = make(PW, salt_a)
h_b = make(PW, salt_b)
print("2) salt_a =", salt_a.hex())
print(" hash_a =", h_a.hex())
print(" salt_b =", salt_b.hex())
print(" hash_b =", h_b.hex())
print(" hash_a == hash_b:", h_a == h_b)
2) salt_a = e954bca1ae1f2631cef4d7ff814ef45a
hash_a = 3c72c93cd967633ba4476d87ccc69b5757e063bdcc083f1402c9aa859a97a85f
salt_b = d4b5734165b04d9b0e300abf084c6a7d
hash_b = 862f2077f3a0fc8ad5878f348bd1d1c1ee43f3a74ada5e215f2c87b96f81dfaf
hash_a == hash_b: False
ソルトとハッシュの値は、流すたびに変わります。上は、ある1回の出力です。同じパスワードに同じソルトを使えば、同じ結果になります。手元では make(PW, salt_a) == hash_a が True でした。これが、あとで照合に使う性質です。
3. 保存するものと、照合のしかた
保存するのは、ソルト・結果(ハッシュ)・方式とコストです(NIST の SHALL と SHOULD に当たる部分。上の表のとおり)。手元の台本では、次のような辞書にしました。形は台本の例で、決まった形ではありません。
record = {"scheme": "scrypt", "n": N, "r": R, "p": P, "salt": salt_a.hex(), "hash": h_a.hex()}
照合は、保存したソルトと方式・コストで計算し直し、hmac.compare_digest で比べます。
def verify(password, rec):
h = hashlib.scrypt(password.encode("utf-8"), salt=bytes.fromhex(rec["salt"]),
n=rec["n"], r=rec["r"], p=rec["p"], maxmem=MAXMEM, dklen=32)
return hmac.compare_digest(h, bytes.fromhex(rec["hash"]))
print("5) verify(correct horse battery staple):", verify("correct horse battery staple", record))
print(" verify(Tr0ub4dor&3):", verify("Tr0ub4dor&3", record))
5) verify(correct horse battery staple): True
verify(Tr0ub4dor&3): False
hmac.compare_digest(a, b) は a == b を返しますが、中身を見て途中で打ち切ることがないので、比べるのにかかった時間から中身を推し量られにくい、と Python の文書は書いています(タイミング攻撃への対策)。長さが違うと、長さと型は漏れうるが、値は漏れない、とも書かれています。Tr0ub4dor&3 は、同じ xkcd の漫画に出てくる別の例で、ここでは「違うパスワード」の例にしました。
4. scrypt の時間と、SHA-256 の速さ
手元の Mac で、scrypt(N=2^17・r=8・p=1・dklen 32)1回の時間を5回測ると、0.295・0.273・0.267・0.270・0.273 秒でした。OWASP の目安の「1回のハッシュ計算が1秒未満」には入っています。ただし、これはこの Mac の値で、サーバーの値ではありません。
同じ Mac で、Python のループで SHA-256 を回すと、1秒に約 190 万回(1,928,908 回)でした。ソルトがないので、同じパスワードは毎回同じ値になります(sha256(PW) を2回計算して True)。
この2つの数を割り算して、「scrypt は何倍遅い」と比べることはできません。条件が違うからです。SHA-256 の数は Python のループの重さを含んだもので、GPU を使う攻撃の速さ(NIST の「毎秒何十億」)とも別のものです。数を並べて見せているのは、手元で動かした事実としてです。
5. 日本語などは、Unicode の正規化にも注意
NIST は、Unicode の文字を受け付けるなら、ハッシュの前に NFC(正規化の形の1つ)に正規化すべき(SHOULD)と書いています。あわせて、端末によって同じ文字の表し方が違うことがあると、利用者に伝えるべき(SHOULD)とも書いています。
手元で、見た目が同じ「café」を2つの形で確かめました。e に結合アクセントを重ねた形は 5 コードポイント・6 バイト、NFC の形は 4 コードポイント・5 バイトで、scrypt の結果も違いました(False)。どちらも NFC にしてからハッシュすると、同じになりました(True)。
NIST SP 800-63B-4 は、パスワードを受け取る側に何を決めている?
先に知っておくこと:米国の政府向けの指針、SHALL と SHOULD
NIST SP 800-63B-4(Digital Identity Guidelines: Authentication and Authenticator Management)は、2025年7月に公開された最終版で、2020年3月の SP 800-63B を置き換えました。対象は、ネットワーク越しに米国の政府の情報システムを使う人の認証です。日本の法律の決まりではなく、米国の政府向けの指針です。NIST も、この目的の外の標準を縛るつもりはない、と書いています。民間のサービスが参考にすることはできますが、「守らないと違法」という意味ではありません。
もう1つ大事なのが、大文字の SHALL と SHOULD の区別です。NIST は、大文字の語を決まり(規範)として使い、小文字なら決まりではないと書いています。
| 原文の語 | 意味 |
|---|---|
| SHALL・SHALL NOT | 必ず守る。外れてはいけない(〜しなければならない・〜してはならない) |
| SHOULD・SHOULD NOT | 勧める。必須ではない(〜すべき・〜すべきでない) |
| MAY | してよい |
また、第4版で、これまで「memorized secret(記憶する秘密)」と呼んでいたものを、「password」と呼び直しました。
この指針の対象は、サーバー側で照合する、独立した認証の要素としてのパスワードです。端末のロックを外す PIN のようなものは、別の扱い(activation secrets)です。NIST は、パスワードはフィッシング(偽のサイトで入力させる手口)に耐えない、とも書いています。
受け取る側の決まりの一覧
§3.1.1.2(Password Verifiers)の決まりを、重要なものから並べます。verifier とは、パスワードを受け取って照合する側(Web アプリのサーバー)のことです。
| 項目 | NIST の決まり | 語 |
|---|---|---|
| 最短の長さ | パスワードだけで認証する(単一要素)なら、15 文字以上を求める | SHALL |
| 最短の長さ(多要素の一部) | 多要素認証の一部としてだけ使うなら、短くしてよい。ただし8 文字以上は求める | 短くしてよい:MAY/8 文字以上を求める:SHALL |
| 最長 | 少なくとも 64 文字までは受け付ける | SHOULD |
| 使える文字 | 印字できる ASCII の文字と空白を受け付ける。Unicode も受け付ける | SHOULD |
| 長さの数え方 | Unicode のコードポイント1つを1文字として数える | SHALL |
| 文字の種類の組み合わせ | 「大文字・数字・記号を混ぜる」のような決まりを課さない | SHALL NOT |
| 定期的な変更 | 定期的に変えさせない。ただし、漏れた証拠があれば変えさせる | 変えさせない:SHALL NOT/漏れた証拠があれば変えさせる:SHALL |
| ヒント・秘密の質問 | ログインしていない人が見られるヒントを保存させない。「最初のペットの名前は?」のような質問(知識ベース認証)を促さない | SHALL NOT |
| 全体の照合 | パスワードを全部受け取り、全体を照合する。一部だけを入れさせない・切り詰めない | SHALL |
| 漏れたパスワードとの照合 | 登録・変更のときに、よくある・予想できる・漏れたパスワードの一覧(ブロックリスト)と照らす。載っていたら別のものを選ばせ、理由を示す | SHALL |
| 案内 | 強いパスワードを選ぶための案内を出す | SHALL |
| 管理ソフトと自動入力 | パスワード管理ソフトと自動入力を許す。貼り付けも許す | 管理ソフトと自動入力:SHALL/貼り付け:SHOULD |
| 見せる選択肢 | 入力中のパスワードを、「●」でなく文字で見せる選択肢を出す | SHOULD |
| 通り道 | パスワードを求めるときは、認められた暗号と、相手を確かめた守られた通り道を使う | SHALL |
最後の行の通り道は、HTTPS のようなものを指すと読めますが、この決まりの文には、HTTPS という語は出てきません。
表の行で、読み方に注意が要るものを補います。
- 15 文字は、単一要素のときです。多要素認証の一部として使うなら、8 文字以上でよい、という決まりです。第4版で、単一要素の最短を引き上げました
- 最長の「64 文字」は SHOULD です。「64 文字まで受け付けなければならない」ではありません
- ブロックリストの照合は、パスワード全体で行います。含まれる語ではありません。リストの例として、過去の漏えいで出たパスワード、辞書の語、サービス名・ユーザー名のようなその場に合わせた語が挙げられています。一覧は、試行の上限までに当てられそうなものを防げる大きさにし、大きすぎる一覧は効き目が小さい、とされています
- 打ち間違いへの小さな許し(前後の空白を取る、先頭の1文字の大文字小文字を問わない、など)は、してよい(MAY)とされています。ただし、処理のあとも最短の長さを満たし、強さが大きく落ちないときだけです
なぜ「複雑さ」を強いず、定期変更もさせないのか
NIST の付録(Appendix A。決まりではなく説明)は、理由を次のように書いています。
- 利用者は、決まりに、とても読みやすい形で応じます。
passwordと書く人は、大文字と数字を求められるとPassword1、記号も求められるとPassword1!にしやすい、という例です。漏えいしたデータベースの分析では、決まりの効き目は思われていたより小さく、使いにくさ・覚えにくさへの影響は大きい、とされています - 長さや複雑さの決まりを、ここで勧める以上に課すと、利用者を困らせ、回避を招きます。ブロックリスト・ハッシュでの保存・機械で作るランダムなパスワード・試行の制限のほうが、いまの総当たりに効果的だ、という書き方です
- ハッシュの大きさはパスワードの長さに関係ないので、長いパスワードを禁じる理由はありません。ただし、メガバイト級のものは処理に時間がかかるので、何かしらの上限を置くのは妥当です
- SQL インジェクション(SQL に不正な文を混ぜる攻撃)を避けるために記号を拒む必要もありません。ハッシュしていないパスワードが、そのままデータベースに送られることはないためです
定期変更については、§8(使いやすさ。説明の節)にも、利用者の求めか、漏れた証拠が無いのに、変えさせないように、という書き方があります。
「切り詰めない」と bcrypt の 72 バイトは、どう読む?
ここは、2つの原文が、そのまま並べると食い違って見える所です。
NIST SP 800-63B-4 は次のように書いています。
Verifiers SHALL request the password to be provided in full (not a subset of it) and SHALL verify the entire submitted password (e.g., not truncate it).
(パスワードは全部を受け取り、出されたパスワード全体を照合しなければならない。例:切り詰めない)
一方、OWASP は bcrypt について、次のように書いています。
bcrypt has a maximum length input length of 72 bytes for most implementations, so you should enforce a maximum password length of 72 bytes (or less if the bcrypt implementation in use has smaller limits).
(bcrypt の入力はたいていの実装で 72 バイトまでなので、パスワードの最長を 72 バイト〔実装がもっと短ければそれ以下〕にするべき)
矛盾ではない、と読めます。OWASP の書き方は、72 バイトを超えるものを受け付けておいて、黙って切り詰める、という状態を避ける形だからです。ただし、NIST の「64 文字以上を受け付けるべき(SHOULD)」と並べると、ずれが見えます。日本語の多くの文字は、UTF-8 で 1 文字が 3 バイトなので、64 文字は 192 バイトで、bcrypt の 72 バイトを超えます(この換算は、この記事での計算です)。bcrypt を選ぶときは、この点を考える必要があります。なお、NIST が bcrypt に触れているわけではありません。
試行回数の制限・多要素認証・日本の資料はどう言っている?
試行回数を制限する
NIST は、パスワードを受け取る側に、失敗できる回数の制限を求めています(SHALL)。§3.2.2 では、1つのアカウントで続けて失敗できるのは 100 回までとされ、超えたら、その認証の手段を止めます(SHALL)。100 は上限で、もっと少なくしてよい(MAY)とされているので、「100 回にする」という意味ではありません。
締め出しを減らす工夫として、ボットの判定、失敗のたびに待ち時間を延ばす方法(例として「30 秒から 1 時間まで」)、ふだんと違う振る舞いの判定を使ってよい(MAY)とされています。認証に成功したら、それまでの失敗は数えないようにすべき(SHOULD)です。
多要素認証
多要素認証とは、パスワードに加えて、スマートフォンのアプリが出す数字のような、別の種類のものを組み合わせて本人を確かめる方法です。NIST は、認証の強さを段階(AAL)に分けています。
- いちばん低い段(AAL1)でも、多要素認証の選択肢を用意して、使うよう勧めるべき(SHOULD)です
- 2つの違う要素が要る段(AAL2)では、パスワードと組むものとして、「持っているもの」(ルックアップシークレット・別経路の端末・ワンタイムパスワード・暗号の認証器)を1つ含めます(SHALL)。さらに、フィッシングに耐える方法を少なくとも1つ用意し(SHALL)、できるだけ勧めるべき(SHOULD)です。「フィッシングに耐える方法」の中身は、この記事では確かめていません
なお、米国の連邦機関は、個人情報をオンラインで見せるなら AAL2 以上を選ぶ(SHALL)とされています。これは連邦機関への決まりで、日本の事業者の義務ではありません。
デジタル庁の大臣会見(2026年10月6日)では、国民への3つの対策として、「パスワードの使いまわしをやめる」「多要素認証を利用する」「不審なメールや SMS に注意」が挙がりました。多要素認証については、ID とパスワードが漏えいしても、スマートフォン認証やワンタイムパスワードなどを設定することで、不正なログインを防止できる可能性が高くなる、という説明でした。これは利用者への呼びかけで、作る側の決まりではありません。
個人情報保護委員会の資料と、NIST を読み比べる
日本では、個人情報保護委員会が注意喚起の資料(WARNING。2024年12月11日、2026年10月7日改訂)を出していて、その事例 3 が不正ログインの事例です。原因として、次の4つが挙がっています。
- 推測されやすい ID・パスワードを使っていた
- 複数回の入力ミスでアカウントを一定時間ロックするなど、ブルートフォース攻撃(ランダムな ID・パスワードを総当たりで試す手法)への対策を行えていなかった
- 過去の脆弱性で ID・パスワードが漏えいしていたのに、初期化などの措置を行えていなかった
- ログの保存・監視のルールがなく、不正アクセスの検知が遅れた
対策例としては、多要素認証等の高い強度の認証方式の採用、アカウントロック等の不正ログイン対策、漏えいした可能性がある場合の初期化か変更の案内、ログの保存期間を定めた定期的な分析などが挙がっています。
この対策例の最初には、次の1行があります。
推測されにくい複雑なパスワードを利用するよう従業者を教育する。
一方、NIST は次のように書いています。
Verifiers and CSPs SHALL NOT impose other composition rules (e.g., requiring mixtures of different character types) for passwords.
(照合する側は、文字の種類を混ぜることを求めるような、ほかの組み合わせの決まりを課してはならない)
この2つは、言っている場面が違います。個人情報保護委員会の文は、従業者を「教育する」という対策例で、システムが強いる決まりではありません。NIST は、システムが課す決まりについて、課さないと書いています。そのうえで NIST の付録は、決まりが予想しやすい形を生むことを理由に挙げています(前の節)。この記事は、どちらかを誤りとは決めず、両方の原文を並べるにとどめます。NIST は米国の政府向けの指針でもあります。
個人情報保護委員会の注意喚起(2026年10月7日)の別紙にも、技術的な安全管理措置の例があります。ID とパスワードを使う場合には、使い回しの原則禁止、最低パスワード文字数の設定、一定回数以上ログインに失敗した ID の停止などが挙がっていて、組織の外からのアクセス・管理者権限・重要な個人データには、フィッシングに耐性のあるものを含む多要素認証を入れる、とされています。この別紙は、ガイドラインの見直し予定の内容を先取りして示したもので、見直しの確定は 2027年4月の予定とされています(2026年10月7日の資料。原文は「来年4月」)。最低文字数の具体の値は、この資料の中で見つかりませんでした。
ログインのあとと、ほかの記事
この記事は、ログインのとき(パスワードを受け取って確かめる所)の話です。つながる記事は次のとおりです。
- ログインに成功したあと、本人であることをどう覚えておくか(セッションや JWT)は、JWT の記事にまとめています
- 本人確認(認証)と、何をしてよいかの確認(認可)は別の話です。違いは認証と認可の記事にあります
- パスワードを送る代わりに、鍵のペアで接続する方法の例は、GitHub の SSH 設定の記事にあります
- Web 開発のセキュリティ全体の点検は、セキュリティの基本の記事にあります
よくある質問
NIST は Argon2id を勧めている?
NIST の本文には、Argon2・scrypt・bcrypt・PBKDF2 の語は出てきません(2026年10月20日に数えました)。NIST SP 800-63B-4 が書いているのは、ソルトを付けて「適したパスワード用のハッシュ方式」でハッシュすること(SHALL)と、SP 800-132 の最新版などで認められた方式を使うべき(SHOULD)という点までです。Argon2id を第一に勧めているのは OWASP の Cheat Sheet です。SP 800-132 そのものは開いていないので、scrypt や Argon2id が NIST に認められているかは、この記事では確かめていません。
漏れたパスワードの一覧は、どこで手に入る?
NIST が書いているのは、一覧に載せる例(過去の漏えいで出たパスワード・辞書の語・サービス名やユーザー名のようなその場に合わせた語)までです。どの一覧を使うかは書かれていません。入れたパスワードを送る形の外部サービスも含めて、この記事では具体的な一覧やサービスを確かめていないので、紹介しません。
日本の決まりでは、パスワードは最低何文字?
個人情報保護委員会の注意喚起の別紙は、「最低パスワード文字数の設定」を例に挙げていますが、具体の文字数は書かれていませんでした(この資料を開いて確かめた範囲です)。この別紙は、ガイドラインの見直し予定の内容を先取りしたもので、確定は 2027年4月の予定とされています(2026年10月7日の資料。原文は「来年4月」)。ガイドライン本体の現行の別添は、開いていません。
Python で Argon2id を使うには?
Argon2 は Python の標準ライブラリにありません(手元の hashlib にも無く、文書にも出てきません)。使うには外のパッケージが要りますが、どのパッケージを使うか、その既定値がいくつかは、この記事では調べていません。標準ライブラリで流せる hashlib.scrypt は、前の節のとおりです。
- パスワードを、平文・元に戻せる暗号化・速いハッシュ(MD5・SHA-1・SHA-256)で保存していないか(→「パスワードはどう保存する?平文・暗号化・速いハッシュがだめな理由」)
- パスワードごとに違うソルトを、暗号に使える強さの乱数から作って付けているか(→「ソルト:パスワードごとに違う、ランダムな値」)
- ソルトと結果、方式とコストの記録を保存し、照合は保存したソルトと方式・コストで計算し直して比べているか(Python の
hashlibで自分で照合するならhmac.compare_digest)(→「3. 保存するものと、照合のしかた」) - ハッシュの方式と work factor を、OWASP の Cheat Sheet の最新の文書で確かめて選び、自分のサーバーで試しているか(この記事の読みは2026年10月20日のもの。目安は 1 回のハッシュ計算が 1 秒未満)(→「どのハッシュ方式を選ぶ?OWASP の勧め(2026年10月時点)」)
- パスワードだけでログインさせるなら、15 文字以上を求めているか。多要素認証の一部としてだけ使うなら 8 文字以上を求めているか(NIST SP 800-63B-4 を参考にするとき。米国の政府向けの指針で、日本の決まりではない)(→「受け取る側の決まりの一覧」)
- パスワードの全体を照合し(切り詰めない)、登録・変更のときに漏れたパスワードなどの一覧と照らしているか。文字の種類を混ぜる決まりや、定期的な変更を強いていないか(漏れた証拠があるときは変えさせる。NIST SP 800-63B-4 を参考にするとき)(→「受け取る側の決まりの一覧」)
- パスワードの入力に失敗できる回数を、アカウントごとに制限しているか(NIST は、続けて失敗できるのは 100 回までを上限とし、少なくしてよいとしている)(→「試行回数を制限する」)
- 多要素認証の選択肢を用意しているか(NIST は、最も低い段でも用意して使うよう勧めるべき〔SHOULD〕としている)(→「多要素認証」)
まとめ
- パスワードは、平文でも、元に戻せる暗号化でも、速いハッシュ(MD5・SHA-1・SHA-256)でも保存しない。OWASP は、パスワードごとに違うソルトを付けて、遅いハッシュ方式で保存すると書いている
- ソルトは、パスワードごとに違うランダムな値。同じパスワードでも結果が変わる(手元の Python で確認)。NIST は 32 ビット以上と書き、ソルトと結果の両方の保存を求める(SHALL)。ペッパーは全員で共通の秘密で、ハッシュとは別の場所に置く
- OWASP のまとめは、Argon2id(19 MiB・反復 2・並列 1 以上)、使えなければ scrypt、古いシステムなら bcrypt(72 バイトまで)、FIPS-140 が要れば PBKDF2。Argon2id・bcrypt・PBKDF2 は手元で流していない。work factor は自分のサーバーで試す。目安は1回1秒未満
- 照合は、保存したソルトと方式・コストで計算し直し、
hmac.compare_digestで比べる。hashlib.scryptはmaxmemの既定が小さく、手元では、OWASP の最小の組は広げないと失敗した - NIST SP 800-63B-4 は米国の政府の情報システム向けの指針。単一要素なら 15 文字以上(SHALL)、多要素の一部なら 8 文字以上(SHALL)。定期変更は求めず(SHALL NOT)、文字の種類の組み合わせも強いず(SHALL NOT)、漏れたパスワードの一覧と照らす(SHALL)。最長 64 文字は SHOULD
- 試行回数は制限する(SHALL)。100 回は上限で、少なくしてよい。多要素認証は、最も低い段でも選べるように勧めるべき(SHOULD)
- 「切り詰めない」(NIST)と bcrypt の 72 バイト(OWASP)、「複雑な」パスワードの教育(個人情報保護委員会)と NIST の SHALL NOT は、場面が違う。片方を誤りとは決めず、原文を読んで自分のシステムに当てはめる
- 例の
correct horse battery stapleは公開の例で、漏れたパスワードの一覧に載る類い。強い例としては勧めない - この記事は2026年10月20日時点。OWASP の Cheat Sheet は書き換わることがあるので、設定を決めるときは最新の文書を開く
参考資料
この記事の内容は、2026年10月20日に開いた文書をもとにしています。OWASP の Cheat Sheet は書き換わることがあります。
- NIST SP 800-63B-4: Digital Identity Guidelines: Authentication and Authenticator Management(NIST CSRC の公開ページ)
- NIST SP 800-63B-4 の本文(pages.nist.gov)
- OWASP: Password Storage Cheat Sheet
- Python 3.12: hashlib(key derivation)・secrets・hmac.compare_digest
- xkcd 936: Password Strength(例のパスワードの出どころ)
- デジタル庁: 古川大臣記者会見(令和8年10月6日)
- 個人情報保護委員会: WARNING(事例3 不正ログインの事例。PDF)
- 個人情報保護委員会: 注意喚起(令和8年10月7日。別紙つき。PDF)・掲載ページ
※この記事は、公式の資料をもとに AI(Claude)で下書きし、2026年10月20日時点の公式の資料と照らして確かめてから公開しています。誤りに気づいたらお問い合わせからお知らせください。



