暗号鍵管理の置き場所によって、攻撃者がどこまで届くかは変わります。2026年10月15日に最大22,519,960件の会員情報の流出を発表したソースネクストについて、同社の説明として「パスワードは暗号化して保存していたが、暗号化を解除するプログラムをハッキングされた」という趣旨が報じられました。

この一文は、鍵がどこにあり、攻撃者がどの権限を得たかで意味がまるで変わります。本記事は連載「事故から逆算するWebセキュリティ」の第2回として、鍵の置き場所と攻撃者の権限を分けて、それぞれが守れる範囲と守れない範囲を整理します。

WHO STOPS WHAT乗っ取られたら

鍵の置き場所を変えても、復号の窓口は開いたまま

アプリでコードを動かせる攻撃者から見ると

  • HSM鍵は外に出ない 復号はできる復号の窓口を呼べば平文が返る
  • KMSデータキーが届く 鍵ごと取れるアプリのメモリから読む
  • 直書き鍵がコードの中 鍵ごと持ち出せるソースやリポジトリから読む
  • Hashパスワード 元に戻せない照合はできるが、平文は出ない
X上で示された3分類を当サイトが図にしたもの。ソースネクストがどれに当たるかは公表されていない

30秒でわかる

  • ・分けて考える2つ:鍵素材の持ち出し(HSM・KMSで防げる)と、正規の権限を使った復号の悪用(権限と使用条件の設計で決まる)
  • ・3つの置き場所:HSMで復号する/KMSから受け取ったデータキーでアプリが復号する/鍵をコードに直書きする
  • ・前提:アプリに復号を許していれば、アプリでコードを動かせる攻撃者もその権限で復号を呼べる。止められるかは、復号できる主体・条件・量の絞り方しだい
  • ・鍵を替えても、すでに復号されて持ち出されたデータは戻らない。鍵の入れ替えと漏えいへの対応は別の作業

インシデントの読み方やほかの事案は サプライチェーン攻撃とは|手口・防御ツール比較・npm 12の新機構まで実践解説 にまとめています。

「復号するプログラムをハッキング」は3通りに読める

まず、確かめられたことと報道と推測を分けます。

・同社の発表:会員情報取得 API が悪用され、10月9日10時頃から13日まで不正な取得が続いた(経緯は ソースネクスト情報漏洩|最大2,252万件・パスワードも対象)
・報道:冒頭の「暗号化を解除するプログラムをハッキングされた」という同社の説明。当サイトは公式発表の本文で確かめられていない
・本記事が推測しないこと:鍵がどこにあったか、どの経路で復号されたか

X上では、報じられた説明を次の3つに分けて考える整理がありました。

  鍵の置き場所 「復号するプログラムをハッキング」の中身
① HSM(鍵は取り出せない) HSMで復号するルートを悪用された
② KMS(鍵を保管するサービス) アプリがKMSから受け取ったデータキーを取られた
③ プログラムに直書き 鍵ごとプログラムから読まれた

同社はどれに当たるかを公表していません。この表は判定ではなく、自分のシステムがどれに近いかを点検するための枠組みです。

暗号鍵管理の3つの置き場所と、攻撃者の権限ごとにできること

OWASP の Cryptographic Storage Cheat Sheet は、アプリは復号のために何らかの形で鍵に触れる必要があり、アプリを完全に乗っ取った攻撃者から鍵を守り切るのは難しいかもしれない、と書いています。置き場所の差は、まず「鍵素材が持ち出されるか」と「何が記録に残るか」に出ます。

  ① HSMで復号 ② KMSのデータキーで復号 ③ 直書き
鍵素材は持ち出されるか 持ち出されない KMSの鍵は出ない。平文のデータキーは使用中アプリのメモリにあり、持ち出され得る 持ち出される
ログに残るもの 復号の呼び出し(記録していれば) データキーの受け取り。その後のアプリ内の復号は残らない 何も残らないことが多い
影響範囲を絞るもの 復号できる主体・条件・量の制限 同左に加え、データキーの粒度と暗号化コンテキスト 鍵の作り直しと再暗号化

①は、OWASP の Key Management Cheat Sheet が勧める形です。生成・暗号化・復号をモジュールの中で行い、平文の鍵を外に出しません。正規の権限による復号をどこまで拒めるかは、鍵を「誰が・どの条件で」使えるかをどこまで細かく決めたかによります。HSM が呼び出しの意図を見て拒むわけではありません。③は、Cryptographic Storage Cheat Sheet が「鍵をソースコードに直書きしない」「バージョン管理に入れない」と明記している形です。

置き場所より差が出るのは、攻撃者が何を得たかです。

攻撃者が得たもの ①② HSM・KMS を使う構成 ③ 直書き
リポジトリ・ビルド成果物を読めた 鍵素材は得られない 鍵が手に入る。暗号文も入手すれば全部読める
DB やバックアップの暗号文だけを読めた 復号の権限が無ければ読めない 鍵がコード側にあり、これだけでは読めない
アプリのコードを実行できた アプリの権限で復号を呼べる。②は処理中のデータキーも読める 鍵も平文も読める
タスクロールの一時認証情報を奪った アプリの外からでも、そのロールの権限で復号を呼べる ロールで読める秘密(Secrets Manager など)による
鍵ポリシーを変えられる権限を奪った 自分に復号を許可できる 関係しない

AWS KMSのエンベロープ暗号化で、鍵はどこに出てくるか

AWS KMS の開発者ガイドによると、KMS は FIPS 140-3 のセキュリティレベル3で検証されたHSMで鍵を守り、平文の鍵の素材はHSMの境界の外に出ません(中国リージョンを除く)。違いは、大きなデータの暗号化に使うエンベロープ暗号化にあります。

ENVELOPE ENCRYPTION鍵の通り道

親の鍵はKMSに残るが、データキーはアプリに来る

  1. GenerateDataKey

    平文と暗号化済みを返す

  2. 暗号化

    メモリで平文キーを使う

  3. 保存

    データの横に置く

  4. Decrypt

    平文のデータキーが返る

  5. 復号

    メモリで平文に戻す

出典: AWS KMS Developer Guide(Concepts / Data keys)

・KMS の鍵(KMS key)の素材はアプリに渡らない。アプリに渡るのはデータキー
・GenerateDataKey は平文と暗号化済みの2つのデータキーを返す。アプリは平文のデータキーで暗号化し、暗号化済みのデータキーだけをデータの横に保存する
・読むときは Decrypt に暗号化済みのデータキーを渡し、返ってきた平文のデータキーでアプリが復号する

ガイドは、平文のデータキーを使い終えたらできるだけ早くメモリから消すよう書いています。逆に言えば、データに直接触れる鍵は、使っている間アプリのメモリにある。②はこの瞬間を取られる場合です。KMS の呼び出しは Decrypt を含めて CloudTrail に記録され、暗号化のときに渡した暗号化コンテキストは復号のときに完全に一致しないと失敗し、平文のまま記録に出るので、どのデータを復号したかを追えます。

手元で測る——復号の窓口を握られると、何回の呼び出しで何件開くか

KMS を模した小さなプログラムを Python(cryptography 50.0.2)で書き、10万件のパスワードを暗号化した表を用意しました。親の鍵は外から取り出せないオブジェクトの中に置き、復号の窓口だけを公開しています。攻撃者は「アプリと同じ権限で窓口を呼べる」とします。

MEASURED LOCALLY手元の模擬で測る

復号の呼び出し1回で、表の全件が平文に戻る

1回

復号の呼び出し

表全体を1つのデータキーで暗号化

100,000件

平文に戻った件数

0.07〜0.08秒(2回計測)

100,000回

1件ずつ鍵を分けた場合

ログに件数分の呼び出しが残る

出典: 当サイトの実測(2026-10-16、Python 3.13 / cryptography 50.0.2。KMSを模したローカルの模擬で、ネットワーク往復は含まない)
設計 復号の呼び出し 平文に戻った件数 かかった時間(2回計測)
表全体を1つのデータキーで暗号化 1回 100,000件 0.07〜0.08秒
1件ごとに別のデータキー 100,000回 100,000件 0.31〜0.32秒
別の利用者IDを暗号化コンテキストに渡す 1回 0件(InvalidTag で失敗) —

親の鍵は一度も外に出ていないのに、全件が平文に戻りました。表全体を1つのデータキーで暗号化すると、ログに残るのは復号1回分だけで、量の異常として見つける手がかりがありません。これはローカルの模擬で、実際の KMS で同じ量を呼んだときの時間とスロットリングは未検証です。

暗号鍵管理の対策——復号できる範囲を狭め、呼ばれ方を見る

鍵の置き場所を①に寄せても、復号の窓口が開いていれば読まれます。効くのは、窓口を呼べる範囲・条件・回数を絞ることです。

対策 何を止めるか 注意
復号できる主体を絞る 書き込むだけのサービスに Decrypt を持たせない(会員登録は暗号化だけでよい) IAM ポリシー・鍵ポリシー・グラントの3か所を見る
復号するサービスを分ける 会員情報を返す API に復号の権限を持たせず、平文が要る処理だけ別のサービスで行う 分けた先の認証・認可と通信経路が弱ければ境界にならない
暗号化コンテキストで縛る 利用者IDなどを入れ、鍵ポリシーの条件にする アプリを丸ごと乗っ取った攻撃者が正しい値を渡せば通る。効くのは取り違えと持ち出し後の流用、記録の追跡
Decrypt の量を見る CloudTrail の Decrypt を主体ごとに数え、平常より多ければ通知する 記録するだけでは通知まではされない。データキーの粒度が粗いと1回で終わる

ソースネクストの発表のタイムラインでは、不正な取得の開始から確認まで約4日でした。量の監視が今回効いたかは分かりませんが、「平常の何倍の復号が何時間続いたか」を測れる設計なら、気づくまでの時間を縮める材料になります。Secrets Manager に置いた秘密も同じです。保管場所が安全でも、取り出しを許されたタスクが侵害されれば取られます。

鍵を替えることと、漏えいへの対応は別

問い 鍵の入れ替えで済むか 別に要る作業
鍵の漏えいを封じ込められるか 直書きの鍵は、新しい鍵に替えて古い鍵を使えなくして初めて封じ込められる。KMS の自動ローテーションは古い鍵素材を残すので封じ込めにならない 復号の権限を外す、侵害された認証情報やロールを失効させる
既存の暗号文を引き続き読めるか 自動ローテーションでは古い鍵素材で既存の暗号文をそのまま復号できる。再暗号化はしない 古い鍵の分を ReEncrypt などで新しい鍵へ移してから、古い鍵を止める
すでに復号・流出したデータ 戻らない 影響データの特定、本人への通知、漏えい等報告

AWS の規範ガイダンスも、古い鍵素材が残って復号に使えるため、ローテーションは追加のセキュリティ上の利点をもたらさないとし、主に規制要件のためと位置づけています。

自分のAWS環境で確かめる——復号できる主体と記録

どれも読み取りだけの操作で、設定は変えません。1行目のロールの ARN と2行目の鍵の別名を、自分の環境の値に置き換えてから実行します。

ROLE="arn:aws:iam::111122223333:role/app-task-role"   # 確かめるタスクロール
KARN=$(aws kms describe-key --key-id alias/app-data --query KeyMetadata.Arn --output text)   # 確かめる鍵
# 1. ロールの IAM ポリシーが復号を許すか(鍵ポリシーとグラントは評価しない)
aws iam simulate-principal-policy --policy-source-arn "$ROLE" \
  --action-names kms:Decrypt kms:GenerateDataKey secretsmanager:GetSecretValue \
  --resource-arns "$KARN" --query 'EvaluationResults[].[EvalActionName,EvalDecision]' --output text
# 2. 鍵ポリシーで誰に何を許しているか
aws kms get-key-policy --key-id "$KARN" --policy-name default --output text
# 3. グラントで操作を受け取っている主体(IAM ポリシーに無くても使える)
aws kms list-grants --key-id "$KARN" --query 'Grants[].[GranteePrincipal,join(`,`,Operations)]' --output text
# 4. 直近90日に Decrypt を呼んだ主体と回数(リージョンごと)
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=Decrypt \
  --max-items 1000 --query 'Events[].Username' --output text | tr '\t' '\n' | sort | uniq -c | sort -rn

読み方は次のとおりです。

・1は IAM ポリシーだけの判定。ロールを起点にしたシミュレーションでは鍵ポリシーを評価できないので、2・3と合わせて読む
・1が implicitDeny でも、2の鍵ポリシーか3のグラントで許されていれば使える。グラントは鍵ポリシーや IAM ポリシーを変えずに権限を渡せる
・4で、呼んだ主体と回数が平常の想定と合うかを見る。イベント履歴は直近90日の管理イベントで、トレイルで KMS を除外していても履歴には残る
・90日より前を追うにはトレイルが要る。書き込みのみの記録にしたトレイルには Decrypt が残らない

AWS CLI 1.46.1 と AWS のモック moto 5.2.4 で、2と3の出力の形を確かめました。IAM ポリシーでは Decrypt を許していないロール(signup-writer)に Decrypt のグラントを付けると、3にはそのロールが出ます。1と4は moto が未実装で、リクエストが組み立てられるところまでしか確かめていません。実アカウントでの出力は未検証です。

リポジトリにハードコードされた鍵を探す

③に当てはまるかは、手元のリポジトリで確かめられます。現在のファイルと、過去のコミットで追加された行の両方を探します。

PAT="AKIA[0-9A-Z]{16}|BEGIN [A-Z ]*PRIVATE KEY|[\"'][A-Za-z0-9+/_-]{43}=[\"']|(secret|key|token|passw)[A-Za-z_]*[\"']?[[:space:]]*[:=][[:space:]]*b?[\"'][^\"']{16,}[\"']"
git grep -nIiE "$PAT"
git log -p --all | grep -iE "^\+.*($PAT)"

10月16日に、検体を置いたリポジトリでは現在のファイルで3件・履歴で4件(後で消した SECRET_KEY も出る)、置かないリポジトリでは0件でした。コードから消した鍵も、履歴には残っています。見つかったら、消すのではなく作り直してください。正規表現で拾える形は一部なので、漏れなく探すには専用のスキャナーを併用します。

パスワードはそもそも暗号化しない——秘密の種類で保存の仕方を分ける

ここまでの話は、元に戻す必要があるデータの話です。何を守るかで、保存の仕方は変わります。

対象 保存の仕方
利用者のログインパスワード ソルト付きの、計算に時間のかかるハッシュ(Argon2id・bcrypt・PBKDF2)
外部 API の認証情報・DB の接続パスワード 元の値が要るので、Secrets Manager などに暗号化して保管し、取り出せる主体を絞る
DB に置く個人情報 用途と脅威に応じて暗号化し、復号できる主体を絞る
暗号化に使う鍵 KMS・HSM などで管理し、データと分けて置く

OWASP の Password Storage Cheat Sheet は、ログインパスワードは暗号化ではなくハッシュで保存すべきで、暗号化を使うのは別のシステムへの認証に元のパスワードそのものが要る例外のときだけ、としています。

ハッシュなら「復号するプログラム」が存在しない

  • ・ログインの照合は、入力をハッシュして比べるだけで済む。平文に戻す窓口が無い
  • ・3通りのどれでも、暗号化したパスワードは復号の手段を取られた時点で平文になる
  • ・ハッシュでも、弱いパスワードは総当たりで当てられる余地は残る。使い回しの変更は必要

保存の仕方の詳細は連載第1回の パスワードのハッシュ化と暗号化の違い で扱いました。

参照ソース