メインコンテンツへスキップ
友田 陽大
セキュリティエンジニア・キャリア
セキュリティ
暗号
パスワードハッシュ
Argon2
セキュリティエンジニア

実務のための応用暗号ガイド【2026年版】パスワードハッシュ(Argon2id)・暗号化(AES-GCM)・鍵管理を正しく使う

アプリ開発者が暗号を“正しく使う”ための実践ガイド。ハッシュ・暗号化・エンコードの違いから、OWASP推奨パラメータでのArgon2idパスワードハッシュ、NIST 800-63B-4の最新パスワード方針、AES-256-GCMによる認証付き暗号、鍵管理とローテーションまでを、公式情報に忠実な型安全コードで解説します。

公開日
読了時間
11分
著者
友田 陽大
シェア

セキュリティの現場で最も多い事故の一つが、**「暗号の誤用」**です。パスワードをMD5でハッシュする、機密データをBase64しただけで「暗号化した」と思い込む、暗号化に認証が無くて改ざんに気づけない、鍵をソースコードに書く——どれも、個々の技術は知っていても“正しい組み合わせ”を知らないことから起きます。

この記事は、暗号理論の教科書ではありません。アプリ開発者が、暗号を“正しく使う”ための実務ガイドです。大原則は一つ——「暗号を自前で実装するな。枯れたライブラリを、正しいパラメータで使え」。その「正しいパラメータ」を、OWASP Cheat SheetNIST SP 800-63Bという公式の一次情報から、型安全なコードで示します。

この記事の位置づけ: NIST CSF 2.0の**「防御(Protect)」**を担う中核技能です。職種全体はセキュリティエンジニアになるには、入力検証や認可など防御全般はセキュアコーディング実践、JWTなど認証基盤の実装は認証・認可クラスタを参照してください。


0. まず3つの混同を解く — ハッシュ・暗号化・エンコード

事故の半分は、この3つの取り違えから起きます。

操作復号できるか目的典型的な誤用
ハッシュ(一方向)❌ できない同一性の検証(照合)パスワード保存、改ざん検知速いハッシュ(MD5/SHA)でパスワード保存
暗号化(双方向)✅ できる(鍵が必要)機密性の保護個人情報・トークンの保存認証なし暗号、鍵のハードコード
エンコード✅ 誰でも戻せる形式変換(暗号ではない)Base64、URLエンコード「Base64したから安全」

Base64は暗号ではありません。 鍵なしで誰でも元に戻せます。「秘匿のため」にBase64やエンコードを使うのは、最も多い初歩的な誤りです。秘匿が必要なら暗号化、照合だけで十分ならハッシュ——目的から手段を選びます。


1. パスワードは Argon2id でハッシュする

なぜ「速いハッシュ」が危険か

MD5・SHA-1・SHA-256は高速です。それはパスワード保存にとっては致命的な欠点になります。攻撃者は流出したハッシュに対し、GPUで毎秒数十億回の総当たりを試せるからです。だからパスワードには、**意図的に“遅く”作られたパスワード専用のアルゴリズム(KDF:鍵導出関数)**を使います。

OWASP Password Storage Cheat Sheetの推奨順位は明確です。

  1. Argon2id(第一推奨)
  2. scrypt(Argon2が使えない場合)
  3. bcrypt(レガシー。新規でも可だが72バイト制限に注意)
  4. PBKDF2(FIPS準拠が必要な場合)

Argon2id の実装(OWASP推奨パラメータ)

OWASPが推奨するArgon2idの最小構成は メモリ m=19456 KiB(19 MiB)、反復 t=2、並列度 p=1 です。argon2 ライブラリは、ソルト生成・パラメータの埋め込み・照合をすべて安全に行ってくれます。

// password.ts — Argon2id でパスワードを安全にハッシュ・照合する。
// 自前でソルトやパラメータを管理しない。ライブラリに任せるのが正解。
import argon2 from "argon2";

// OWASP推奨パラメータ(2026年時点)。ハードウェアに余裕があれば memoryCost を上げる。
const ARGON2_OPTIONS = {
  type: argon2.argon2id,
  memoryCost: 19456, // 19 MiB(KiB単位)
  timeCost: 2,       // 反復回数
  parallelism: 1,    // 並列度
} as const;

/** パスワードをハッシュする。ソルトはライブラリが自動生成し、結果に埋め込む。 */
export async function hashPassword(plain: string): Promise<string> {
  return argon2.hash(plain, ARGON2_OPTIONS);
}

/**
 * 照合する。argon2.verify はハッシュ文字列からパラメータを読み取るため、
 * 将来パラメータを強化しても、既存ハッシュの照合は壊れない(前方互換)。
 */
export async function verifyPassword(hash: string, plain: string): Promise<boolean> {
  try {
    return await argon2.verify(hash, plain);
  } catch {
    return false; // 不正な形式のハッシュ等は「不一致」に倒す(例外を漏らさない)
  }
}

ポイントは、ソルトを自分で管理しないことです。argon2.hash はランダムなソルトを生成し、アルゴリズム・パラメータ・ソルト・ハッシュをすべて1つの文字列(PHC形式)に詰めて返します。照合時はそこからパラメータを復元するので、後でパラメータを強化しても既存ユーザーの照合は壊れません

NIST 800-63B-4(2025年最終版)の最新パスワード方針

ハッシュの実装と同じくらい重要なのが、**「どんなパスワードを許すか」**のポリシーです。NIST SP 800-63Bの最新改訂(Rev.4、2025年最終化)は、従来の“常識”を覆しました。

項目NIST 800-63B-4 の方針従来のよくある誤り
長さ8文字以上を必須、単一要素なら15文字以上。64文字以上を許可「8文字ちょうど」で頭打ち
文字種の強制してはならない(SHALL NOT)「大小英数記号を必須」
定期変更課してはならない(SHALL NOT)。漏えい時のみ変更「90日ごとに変更」
漏えいチェック既知の漏えいパスワードをブロックリストで弾くチェックなし
文字種空白を含む印字可能な全文字・Unicodeを許可記号を禁止

つまり——「長く・自由に・漏れたものだけ弾く」。複雑な文字種の強制や定期変更は、むしろユーザーに弱いパスワードの使い回しを促すため、現在は非推奨です。漏えいパスワードの照合にはHave I Been Pwnedのk-匿名性APIなどが使えます。


2. 機密データは「認証付き暗号(AEAD)」で暗号化する

パスワードは“戻せなくて”いい(ハッシュ)。しかし、後で復号して使う必要がある機密データ——APIトークン、個人情報、決済関連の識別子など——は、可逆的な暗号化が必要です。

ここでの鉄則は、「認証付き暗号(AEAD)」を使うことです。単に暗号化するだけでは、改ざんを検知できません。AEAD(例:AES-256-GCM、ChaCha20-Poly1305)は、暗号化と同時に「改ざんされていないこと」を保証する認証タグを付けます。

// crypto.ts — AES-256-GCM(AEAD)で機密データを暗号化・復号する。
// Node.js標準の crypto を使う。アルゴリズムは自作しない。
import { randomBytes, createCipheriv, createDecipheriv } from "node:crypto";

const ALGORITHM = "aes-256-gcm";
const IV_LENGTH = 12;  // GCMの推奨IV長は96ビット(12バイト)
const KEY_LENGTH = 32; // AES-256 → 32バイト鍵

/**
 * 暗号化。毎回ランダムなIV(初期化ベクトル)を使うのが必須——
 * 同じ鍵・同じIVの再利用はGCMの安全性を完全に破壊する。
 * 出力は base64( iv | authTag | ciphertext ) を連結して持ち運ぶ。
 */
export function encrypt(plaintext: string, key: Buffer): string {
  if (key.length !== KEY_LENGTH) throw new Error("鍵は32バイト(AES-256)である必要があります");

  const iv = randomBytes(IV_LENGTH); // ① 毎回新しいIV
  const cipher = createCipheriv(ALGORITHM, key, iv);
  const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
  const authTag = cipher.getAuthTag(); // ② 改ざん検知用の認証タグ

  return Buffer.concat([iv, authTag, ciphertext]).toString("base64");
}

/** 復号。認証タグが合わなければ(=改ざんされていれば)例外で失敗する。 */
export function decrypt(payload: string, key: Buffer): string {
  const data = Buffer.from(payload, "base64");
  const iv = data.subarray(0, IV_LENGTH);
  const authTag = data.subarray(IV_LENGTH, IV_LENGTH + 16); // GCMタグは16バイト
  const ciphertext = data.subarray(IV_LENGTH + 16);

  const decipher = createDecipheriv(ALGORITHM, key, iv);
  decipher.setAuthTag(authTag); // ③ タグを検証——改ざんがあればここで失敗する
  return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
}

絶対にやってはいけないのが、IV(初期化ベクトル)の固定や使い回しです。GCMでは、同じ鍵で同じIVを二度使うと安全性が崩壊します。毎回ランダムなIVを生成し、暗号文と一緒に保存する——これが定石です(IVは秘密でなくてよい)。また、ECBモードは絶対に使わない(同じ平文が同じ暗号文になり、パターンが漏れる)。


3. 鍵管理 — 暗号の安全性は「鍵の守り方」で決まる

どれだけ強い暗号を使っても、鍵がソースコードやリポジトリに書かれていたら無意味です。鍵管理が、応用暗号で最も難しく、最も重要な部分です。

  • 鍵をコードに書かない。 環境変数、または専用のKMS(Key Management Service)/Secrets Managerで管理する。秘密情報の型付き境界はセキュアコーディング実践で扱っています。
  • 封筒暗号化(Envelope Encryption)。 データはランダムな「データ鍵」で暗号化し、そのデータ鍵自体をKMSの「マスター鍵」で暗号化して保存する。マスター鍵はKMSから出ない。AWS KMS / Google Cloud KMS / Azure Key Vault の標準パターンです。
  • 鍵のローテーション。 鍵は定期的に、また漏えい疑いがあれば即座に交換できる設計にする。KMSはローテーションを自動化できます。
  • 最小権限。 鍵を使える主体(IAMロール等)を必要最小限に絞る。
[平文] ──データ鍵で暗号化──▶ [暗号文](DBに保存)
  │
  └─[データ鍵] ──KMSマスター鍵で暗号化──▶ [暗号化済みデータ鍵](一緒に保存)
                                              ▲
                          マスター鍵はKMSの中から出ない(漏えい面を最小化)

4. やってはいけないこと(アンチパターン集)

アンチパターンなぜ危険か正しい対応
MD5/SHA-1/SHA-256単体でパスワード保存速すぎて総当たりされるArgon2id(KDF)を使う
暗号化のつもりでBase64鍵なしで誰でも復号できる本物の暗号化(AES-GCM)
認証なし暗号(AES-CBCのみ等)改ざんを検知できないAEAD(AES-GCM / ChaCha20-Poly1305)
IVの固定・使い回しGCMの安全性が崩壊毎回ランダムIV
鍵をコード/リポジトリに記述漏えいで全データが復号されるKMS / Secrets Manager + ローテーション
独自の暗号アルゴリズム実装ほぼ確実に脆弱になる枯れた標準ライブラリ
alg: none を許すJWT検証署名なしトークンを受理してしまうアルゴリズムを固定して検証(JWT検証

最後のJWTのalg: noneは、認証基盤で頻出の事故です。詳細はCognito JWT(RS256)検証の実装にまとめています。


5. よくある質問(FAQ)

Q. bcryptを使っている既存システムは、Argon2に移行すべきですか? A. 急ぐ必要はありません。bcryptはcost≥10(推奨12)なら今も安全です。移行する場合は、ユーザーが次にログインした時に再ハッシュする段階移行が定石です。新規システムならArgon2idを選びましょう。

Q. ソルトとペッパーの違いは? A. ソルトはハッシュごとにランダムな値で、ライブラリが自動管理します(個別の使い回しを防ぐ)。ペッパーは全体共通の秘密値で、ハッシュとは別にKMS等で管理し追加します。ソルトは必須、ペッパーは任意の追加防御です。

Q. パスワードに文字数・記号の制限を課すのは間違い? A. NIST 800-63B-4は文字種の強制を非推奨としています。ユーザーに弱い使い回しを促すためです。代わりに「長さ(8文字以上、できれば15文字以上)」と「漏えいパスワードのブロック」で守るのが現在の正解です。

Q. クライアント側で暗号化すれば安全ですか? A. クライアントは信頼境界の外です。クライアント側の暗号化は補助にはなりますが、鍵をクライアントに置けば容易に抽出されます。機密性はサーバー側の暗号化とアクセス制御で担保するのが基本です。

Q. 暗号は結局どのライブラリを使えばいい? A. 言語標準(Node.jsならcrypto、Pythonならcryptography)か、Argon2/libsodiumなど広く監査された実装。「自分で書いたコードでビット演算している」なら、それは赤信号です。


6. まとめ

応用暗号の実務は、難しい数学ではなく、「正しい道具を、正しく使う」という規律です。

  • 自前実装しない。 枯れたライブラリを、公式推奨のパラメータで使う。
  • 目的から手段を選ぶ。 照合だけならハッシュ、秘匿なら暗号化、Base64は暗号ではない。
  • パスワードはArgon2id。 OWASP推奨 m=19456, t=2, p=1。NIST 800-63B-4に沿い「長く・自由に・漏れたものを弾く」。
  • 機密データはAEAD。 AES-256-GCMで暗号化と改ざん検知を同時に。IVは毎回ランダム。
  • 鍵管理がすべて。 コードに鍵を書かず、KMS・封筒暗号化・ローテーション・最小権限。

暗号の誤用は、見た目には動いてしまうため、事故が起きるまで気づけないのが恐ろしいところです。だからこそ、リリース前に「暗号の使い方は正しいか」を専門家の目で点検する価値があります。あなたのプロダクトのパスワード保存・データ暗号化・鍵管理を一度棚卸ししたい場合は、お気軽にご相談ください。


参考(公式一次情報)

友田

友田 陽大

経済産業大臣賞 受賞プロダクト開発者。TypeScript + Python + AWS で、SaaS・業界DX・ 実用レベルの生成AI(RAG)を、要件定義からインフラ・運用まで一人で完遂します。

この記事の実装を、案件として承ります

セキュリティエンジニアリングを、設計から実装・運用まで承ります

脅威モデリングによる設計レビュー、暗号・認証認可の正しい実装、ログ設計と検知(検知エンジニアリング)、インシデント対応の体制づくりまで。一人 × 生成AIで経済産業大臣賞のB2B SaaSや本番二重課金0件の決済基盤を作ってきた知見で、御社のプロダクトを“速く作り、かつ守れる”状態に伴走します。設計でしか守れない縦のリスクは監査としても承ります。

プロジェクト単位(請負)・技術顧問のどちらにも対応可能です。まずは30分の無料技術相談から。

あわせて読みたい