こんにちは!ネットワークやAPIの裏側を覗くのが大好きなインフラエンジニアです。
皆さんは、日々の開発やアプリの利用で「API(Web API)」という言葉を当たり前のように使っていることと思います。スマホの天気アプリが最新の気温を表示したり、ECサイトで決済処理を行ったりするとき、裏側ではクライアント(アプリなど)とサーバーの間でHTTPリクエストとレスポンスの激しいラリーが行われていますよね。
さて、このAPIのセキュリティを語る上で避けて通れないのが「認証」です。特に、大規模なシステムや金融・決済系のAPIでよく使われる「APIキーの署名認証」において、極めて重要な役割を果たすのが今回取り上げる「Nonce(ナンス)」という仕組みです。
「名前からして何やら難しそう…」と感じてしまうかもしれませんが、大丈夫です!一歩ずつ、私たちの身近な世界に置き換えながら優しく紐解いていきましょう。
—
1. 郵便配達で考えてみよう!「一度きりの合い言葉」の必要性
まず、Nonceがなぜ必要なのかをイメージするために、少し昔の「手紙のやり取り」を想像してみてください。
あなたは遠くに住む大親友に、自分の全財産の暗証番号が書かれた超機密の手紙を送ろうとしています。そのままでは郵便配達員や途中の人に中身を見られてしまうかもしれないので、手紙の最後に「特定の計算式で導き出した特殊なサイン(署名)」をつけて送ることにしました。
ここで、ずる賢い「悪意ある第三者(攻撃者)」が登場します。
攻撃者は、あなたがポストに投函した手紙の封筒(通信データ)をこっそり盗み見して、中に書かれていた「暗証番号」と「特殊なサイン」をまるごとコピーしてしまいました。
そして、攻撃者は全く同じ手紙を何通も何通も銀行の窓口に送りつけ、「私に金をくれ!」と要求し始めたのです。
…恐ろしい話ですよね。署名によって「確かに本人からの手紙だ」と証明できたとしても、「全く同じ手紙が何回もコピーされて送られてきた場合」、銀行側はそれが本物の最新の依頼なのか、それとも過去の使い回し(リプレイ攻撃)なのかを判別できません。
そこで登場するのが Nonce(ナンス) です。
Nonceとは、英語の “Number used once”(1回だけ使われる数字) の頭文字をとったもので、「リクエストごとに毎回ランダムに変わる、絶対に使い回せない一回限りの値(お札のシリアルナンバーのようなもの)」を指します。
手紙の例で言えば、手紙を出すたびに「今回のシリアルナンバー:748291」というランダムな数字を必ず書き込み、その数字も含めてサインを作り直すルールにします。
銀行側は、「あ、このシリアルナンバー748291の手紙は、さっき処理したばかりだからもう無効だよ!」と見抜いて、二重に処理するのを防ぐことができるというわけです。
—
2. APIにおけるNonceの具体的な仕組み
Web APIの世界でも、この仕組みはまったく同じように動いています。
クライアントがサーバーに対してリクエストを送るとき、通常のデータに加えて、以下の3つの要素をセットにして送るのが一般的です。
1. タイムスタンプ (timestamp): リクエストが作られた日時(例: 1711929600)
2. ナンス (nonce): ランダムな文字列やUUID(例: f81d4fae-7dec-11d0-a765-00a0c91e6bf6)
3. 署名 (signature): APIキーの秘密鍵、リクエストボディ、タイムスタンプ、ナンスを混ぜ合わせて暗号化したハッシュ値
サーバー側は、受け取ったリクエストを検証する際、次の「2つのチェック」を必ず行います。
- チェック1: 時間は古すぎないか?
(例:「タイムスタンプが現在時刻から5分以上ズレているリクエストは、時空を超えた古いデータだとして即座に拒否しよう」)
- チェック2: このNonceは過去に使われていないか?
(例:「サーバーの記憶領域(キャッシュ)を調べて、このNonceが最近使われていなかったか確認しよう」)
もしチェック2で「あ、このNonce、さっき受け取ったばかりのやつだ!」と分かった場合、サーバーは処理を中断し、400 Bad Request などのエラーを返します。これが、悪意ある再送攻撃(リプレイアタック)を防ぐ鉄壁のガードになります。
—
3. サーバー側でのキャッシュ管理と検証ロジック
「なるほど、サーバーが過去のNonceを全部覚えておけばいいんだね。じゃあ、データベースに今までのNonceを無限に保存しなきゃいけないの?」
鋭い疑問ですね!もし過去に受信したすべてのNonceを永久に保存し続けたら、サーバーのデータベースはあっという間にパンクしてしまいます。
ここで実務上よく使われるのが、「Redis(レディス)」などのインメモリキャッシュデータベースを使った有効期限(TTL: Time To Live)付きの管理です。
具体的な検証の流れを、Pythonの疑似コードを見て確認してみましょう。
import time
import redis
# Redisの接続設定(ローカルのキャッシュサーバーを想定)
redis_client = redis.Redis(host='localhost', port=6379, db=0)
def verify_api_request(nonce, timestamp, signature):
current_time = int(time.time())
# 1. タイムスタンプの鮮度チェック(例: 許容範囲は前後の300秒=5分以内)
ALLOWED_TIME_WINDOW = 300
if abs(current_time - timestamp) > ALLOWED_TIME_WINDOW:
return False, "タイムスタンプの有効期限が切れています。"
# 2. Redisを使ってNonceの重複チェック
# キーが存在するか確認しつつ、同時に「処理済み」として登録する
# EX=300 は「300秒(5分)経ったらこのデータは自動的に消去してね」という設定です
is_new_nonce = redis_client.set(f"nonce:{nonce}", "processed", ex=300, nx=True)
if not is_new_nonce:
# nx=True なのにキーが既に存在していた場合、Noneが返るため「再送(リプレイ攻撃)」と判定
return False, "このリクエストは既に処理されています(Nonceの重複)。"
# 3. 署名の検証(API秘密鍵を使って正当なリクエストか計算し直す)
if not validate_signature(nonce, timestamp, signature):
return False, "署名が無効です。"
return True, "検証成功!"
このコードのポイントは、Redisの nx=True(Not eXists)という機能と、ex=300(有効期限300秒)の組み合わせです。
これにより、「一度使われたNonceは5分間だけサーバーに記憶され、その間に同じNonceが来たら秒速で弾き返す。5分過ぎたら自動的に消えるからメモリも圧迫しない」という、非常にスマートで効率的なインフラ設計が成り立ちます。
—
4. まとめ:美しいAPI設計は「安全の積み重ね」から
今回は、APIキーの署名認証における Nonce(ナンス) の役割について、郵便配達の例や実際のコードを交えて解説しました。
- Nonceとは:「1回限りの数字」であり、リクエストごとにランダムな値を付与するもの。
- 役割:通信内容を盗み見られても、同じリクエストの再送(リプレイ攻撃)を完全にシャットアウトする。
- 実務での工夫:Redisなどの高速なキャッシュを使い、有効期限(TTL)を設けてスマートに管理する。
一見すると難解なセキュリティの仕様も、私たちが日常生活で行っている「本人確認の工夫」と本質は同じです。こうした地道で確実な仕組みの積み重ねが、安全で信頼できる美しいWeb APIアーキテクチャを支えています。
実務でAPIを設計・実装する際は、ぜひこのNonceの仕組みを取り入れて、堅牢なシステムを構築してみてくださいね。それでは、また次回の技術解説でお会いしましょう!
コメント