セキュリティ●●○○○

APIキー

API Key ・ えーぴーあいきー

API の呼び出し元を示す長いランダム文字列。手軽さと、漏れたら誰でも使える危うさが表裏一体。

✦ 30秒で試す — 航海ヘッダに1行足すだけ。その手軽さが、そのまま弱点になるこのキーは今どうなっている?予想してみる

概要

APIキーは、API の呼び出し元を示すために発行される長いランダム文字列です。サービスの管理画面で「キーを発行」を押すと sk_live_51Hq… のような文字列が一度だけ表示され、以降のリクエストにこれを添えると、サーバは「このリクエストはあのアカウントのものだ」と判別できます。外部サービスの API を使ったことがあれば、ほぼ確実に最初に出会う資格情報です。

最大の魅力は手軽さです。OAuth のようなリダイレクトの往復も、トークンの更新処理も要らず、発行された文字列をヘッダに付けるだけで使い始められます。その手軽さは、そのまま危うさでもあります。キーは「持っている人を本人とみなす」持参人(Bearer)型の資格情報なので、この1本の文字列が漏れれば、誰でもあなたのアカウントとして API を呼べます。しかも多くのキーには有効期限がなく、誰かが止めるまで使え続けます。

APIキーは、利便性を最優先した最も素朴な資格情報です。「漏れる前提で、漏れたときにすぐ止められるように運用する」ことが、この語彙の核心になります。

なぜ生まれたか

Web の API が公開され始めた2000年代、提供者には「誰が呼んでいるかを知りたい」という切実な事情がありました。完全に匿名の API では、利用量の集計も課金もできません。暴走したクライアントを止めるレート制限も「全員まとめて」しかかけられません。かといって、機械同士の通信にユーザー名とパスワードを毎回送らせるのは筋が悪いものでした。ユーザーの代理でアクセスする委譲の仕組み(のちの OAuth)はまだ整っておらず、「開発者が自分のプログラムから呼ぶ」という単純な用途には大げさでもありました。

そこで採られたのが、「開発者ごとにランダムな文字列を1本発行し、それを名札として付けてもらう」という最小限の仕組みです。提供者はキーごとに利用量を集計し、課金し、制限をかけ、悪用があればそのキーだけを無効にできます。Google Maps API などの初期の公開 API がこの方式を広め、「まず API キーを発行してもらう」は開発者体験の定番になりました。

ところが、キーの数が増え、ソースコードが Git で公開される時代になると、新しい問題が表面化します。キーがコードと一緒に公開リポジトリへ流れ出す事故です。これに対応して、2010年代後半からは「キーの形式そのものを見つけやすく設計する」「公開されたキーを提供者に通報して自動で止める」といった、漏えいを前提にした仕組みが整っていきました。

詳細

識別なのか、認証なのか

APIキーを理解するうえで大事なのが、「識別(identification)」と「認証(authentication)」のどちらの役割を担っているか、という区別です。Google Maps の JavaScript 用キーのようにブラウザへ埋め込む前提のキーは、ページのソースを見れば誰でも読めます。秘密として守れないので、実質は「どのプロジェクトの利用かを仕分ける名札」です。このため、使える Web サイト(Referer)やアプリを制限して悪用の幅を狭めます。一方、サーバの中だけで保持する秘密のキーは、「キーを知っていること」をもって呼び出し元を確かめる認証手段として働きます。ただしその場合でも、キーが表すのは通常「アカウントやプロジェクト」で、操作している人間ではありません。

「誰の権限で、何を、いつまで許すか」を細かく制御したい場面では、より重い仕組みが選ばれます。スコープと有効期限を持つ OAuth のアクセストークンや、それを自己完結的に検証できる形にした JWT、通信路の両端で証明書を確かめ合う mTLS です。注意したいのは、JWT の署名が防ぐのは「中身の改ざん」であって、「盗まれて使われること」ではない点です。JWT も持参人型なので、盗まれれば APIキーと同じように使われます。これらの仕組みが強いのは、有効期限が短く、スコープが絞られているので、盗まれても被害が時間と範囲で区切られるからです。例外は mTLS で、秘密鍵そのものは通信に載らないので、盗み見ただけでは使えません。

「識別の名札」から「被害を区切れる鍵」までの階段公開キー型利用の仕分け・集計の名札秘密キー型知っていること = 本人アカウント単位の認証漏れたら止めるまで使えるOAuth / JWT / mTLS有効期限が短いスコープで範囲を絞るmTLS は鍵が通信に載らない盗まれても被害が区切られる手軽・保証は弱い仕組みは重い・被害を区切れる
APIキーの位置づけ。右へ行くほど仕組みは重くなるが、盗まれたときの被害を期限・範囲・鍵の所在で小さくできる

発行・保存・照合の仕組み

典型的な流れを追ってみます。(1)開発者が管理画面で「キーを発行」を押すと、サーバは暗号論的に安全な乱数から 128〜256 ビット程度のキーを作り、画面に一度だけ表示します。(2)サーバのデータベースには、キー本体ではなく、その SHA-256 などのハッシュ値を保存します。管理画面で「どのキーか」を見分けられるように、先頭の数文字(sk_live_51Hq…)だけは別に残します。(3)クライアントはリクエストのたびに、Authorization: Bearer や X-API-Key といった HTTP ヘッダにキーを載せます。(4)サーバは受け取ったキーをハッシュし、その値でデータベースを引いて持ち主を特定し、そのアカウントの権限・プラン・レート制限を当てはめます。

保存のハッシュが、パスワードのときと違うことに注意してください。パスワードは人が覚えられる程度の短い文字列なので、ハッシュが漏れると総当たりで元に戻されてしまいます。そこで bcrypt や Argon2 のような「わざと遅い」ハッシュで総当たりを遅くします。APIキーは 128 ビット以上のランダムな値なので、総当たりはそもそも成り立ちません。速い SHA-256 で十分です。しかも同じキーからは必ず同じハッシュが出るので、ハッシュ値をそのままデータベースの検索キーにできます。遅いハッシュを毎リクエストで計算するのは、無駄が大きいだけです。

キーは平文のまま送られるので、HTTPS が絶対の前提です。キーを URL のクエリに載せる方式も昔は多く使われましたが、アクセスログやブラウザの履歴、Referer ヘッダに残ってしまいます。いまはヘッダで送るのが原則です。

見つけやすいキーを設計する

最近のキーは、見ただけで種類が分かる形をしています。Stripe の sk_live_(本番の秘密キー)と pk_live_(公開してよいキー)、GitHub の ghp_(個人のアクセストークン)、AWS の AKIA(長命のアクセスキー)などです。GitHub は2021年にトークンの形式を改め、接頭辞に加えて末尾にチェックサムを付けました。

接頭辞は、漏えいを機械で見つけるための工夫です。正規表現で拾いやすく、チェックサムで本物らしさを確かめられるので、誤検知が減ります。GitHub の secret scanning は、公開リポジトリに push されたキーをこの形で検出し、提供者に通報します。通報を受けた提供者は、キーを自動で失効させたり、持ち主に警告したりします。push そのものを止める push protection もあります。提供者の側から見ると、「自社のキーに固有の接頭辞を付け、スキャンの提携先に登録すること」が、利用者を守る設計の一部になっています。

定番の事故と、本当の対策

APIキーの事故で圧倒的に多いのが、ソースコードへの直書きと、Git リポジトリへのコミットです。「気づいてすぐ消した」は、対策になりません。Git の履歴には過去のコミットがすべて残り、公開リポジトリは攻撃者のボットが常時スキャンしています。クラウド(AWS など)のアクセスキーを push すると、数分以内に不正利用が始まることもあります。暗号通貨の採掘用に大量のサーバが立てられ、数百万円の請求が届いた、という事故は毎年のように報告されています。

漏れたと分かったときにすべきことは、消すことではなく、失効させてローテーションすることです。キーの有効・無効を決めているのは発行元のサーバで、リポジトリではありません。削除コミットを積んでも、履歴を書き換えて force push しても、すでに取られた写しは攻撃者の手元で有効なままです。発行元でキーを失効させて、新しいキーに入れ替えたとき、はじめて写しは使えなくなります。

下の部品で確かめてください。キーをうっかりコミットし、削除コミットを積んだり履歴を書き換えたりしても、不正利用のメーターが止まらないことを見てから、発行元でキーを失効させます。

⚡ 体験: 漏れた API キーを止める
公開リポジトリ — Git の履歴(左が古い → 右が新しい)初期構成画面追加テストHEAD開発者の PCconfig.js を編集中API 提供者キーの有効・無効を決めるスキャンボットsk_live_ を探すsk_live_51Hq… 有効請求額(不正利用)¥0sk_live_51Hq…
不正な呼び出し0 回弾かれた呼び出し0 回履歴のコミット3 個

開発者の手元に、本番の API キーがあります。まずは金の輪のチップを公開リポジトリへドラッグして、うっかりコミットしてみてください(タップでもおまかせで運べます)。

予防の側では、キーをコードに書かず、環境変数やシークレット管理の基盤から実行時に注入します。そのうえで、Gitleaks などのスキャナをコミット前のフックと CI/CD に組み込み、混入を機械で止めます。

ローテーションと運用設計

キーは「いつか漏れる」前提で運用します。核になるのがローテーション(定期的な交換と、疑わしいときの緊急の交換)です。ここで効くのが、1つのアカウントに複数のキーを併存させられる設計です。新しいキーを発行し、利用側を順に切り替え、古いキーの利用がゼロになったのを確かめてから失効させる、という手順なら止めずに交換できます。

アプリ提供者のAPI運用者アプリ提供者のAPI運用者1本しか持てない設計だと交換の瞬間に必ず止まる新しいキーを発行新キー・この時点で旧キーと併存新キーを配布して再起動新キーで呼び出す旧キーの最終利用時刻を確かめる旧キーを失効

逆に1本しか持てない設計だと、交換の瞬間に必ず止まります。その結果、「怖くて何年も替えられない」キーが生まれます。あわせて、キーごとに権限を絞り(読み取り専用のキー、特定のリソースだけのキー)、最小権限の原則に沿わせます。使える接続元の IP アドレスを制限し、利用状況を監視して、ふだんと違う呼び出しを検知します。手軽に始められるのが APIキーの美点ですが、本番運用に載せた瞬間から、それは発行から失効までを管理する「秘密情報」の一つになります。

試問

開発者の手元に本番の API キーがあり、公開リポジトリ(誰でも見られるコードの置き場)の Git 履歴、キーの形を探すスキャンボット、キーを発行した API 提供者が並んでいます。キーのチップを公開リポジトリへ運ぶとうっかり push したことになり、そのあとは道具箱の3つの対策(キーを消してコミット・履歴を書き換える・発行元でキーを失効)を、効きそうな相手へドラッグして使えます(タップでも使えます)。

✦ 命題挑戦中

うっかり公開したキーで積み上がる不正利用を止め、自分のサービスは動き続けるようにせよ

  • キーを公開リポジトリへ push し、ボットに写しを取られる
  • 攻撃者が写しで呼んでも、API に弾かれる(401)ようにする
  • 開発者の手元は、新しいキーで動き続ける
⚡ 体験: 漏れたキーの不正利用を止める
公開リポジトリ — Git の履歴(左が古い → 右が新しい)初期構成画面追加テストHEAD開発者の PCconfig.js を編集中API 提供者キーの有効・無効を決めるスキャンボットsk_live_ を探すsk_live_51Hq… 有効請求額(不正利用)¥0sk_live_51Hq…
不正な呼び出し0 回弾かれた呼び出し0 回履歴のコミット3 個

開発者の手元に、本番の API キーがあります。まずは金の輪のチップを公開リポジトリへドラッグして、うっかりコミットしてみてください(タップでもおまかせで運べます)。