Loading...

2023-12-05(火) 15:00

🦖 DenoでHMAC-SHA256を使ってリクエストの署名を検証する

Deno
DenoでWeb APIなどリクエスト元の検証のためにHMAC-SHA256を使ってリクエストの署名を検証する方法を解説します。

目次

前提と注意事項

この記事では以下を前提としています。

  • Deno の実行環境が用意されていること。
  • この記事では HMAC-SHA256 自体の説明は含んでいません。

この記事のゴール

HTTP リクエストを受け付ける Deno で作成した Webhook で、リクエスト元の署名検証を行うことがこの記事のゴールです。 リクエスト元の検証のために HMAC-SHA256 を使ってリクエストの署名を検証する方法を解説します。

HMAC-SHA256 を使って署名を生成する

Deno では、Node.js にあるようなcreateHmacが記事公開時点ではないため、同様の処理を行うためのcreateHmac関数を以下のようにして作成します。

HMAC-SHA256を使って署名を生成する関数
// シークレット(秘密鍵)とメッセージを使って署名を生成する関数
async function createHMAC(secret: string, message: string): Promise<string> {
  // シークレットとメッセージをバイナリデータに変換する
  const encoder = new TextEncoder();
  const keyData = encoder.encode(secret);
  const msgUint8 = encoder.encode(message);
 
  // シークレットを使ってキーを生成する
  const key = await crypto.subtle.importKey(
    'raw',
    keyData,
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign'],
  );
 
  // メッセージを署名する
  const signature = await crypto.subtle.sign('HMAC', key, msgUint8);
 
  // バイナリデータをbase64エンコードして返す
  return btoa(String.fromCharCode(...new Uint8Array(signature)));
}

次に上記のcreateHmacを Webhook 内で使って署名を検証し、正しいリクエスト元であるかどうかを確認します。

リクエストの署名を検証する

以下のような Webhook の中で、前節のcreateHmac関数を使って署名を生成し、その値をリクエストヘッダーに含まれる署名と比較することで署名を検証します。

Webhook内で署名を検証する例
// ここでは、シークレットは環境変数に保存しているとします。
const MY_SECRET = Deno.env.get('MY_SECRET');
 
async function createHMAC(secret: string, message: string): Promise<string> {
  const encoder = new TextEncoder();
  const keyData = encoder.encode(secret);
  const msgUint8 = encoder.encode(message);
 
  const key = await crypto.subtle.importKey(
    'raw',
    keyData,
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign'],
  );
 
  const signature = await crypto.subtle.sign('HMAC', key, msgUint8);
 
  return btoa(String.fromCharCode(...new Uint8Array(signature)));
}
 
// Webhook
Deno.serve(async (req) => {
  // リクエストヘッダーの署名を取得
  // 'x-app-signature' は各自の環境に合わせて変更してください。
  const _headerSignature = req.headers.get('x-app-signature');
 
  // リクエストボディを取得
  const _body = await req.text();
 
  // リクエストボディとシークレットを使って署名を生成
  const _bodySignature = await createHMAC(MY_SECRET, _body);
 
  // リクエストヘッダーの署名と比較して一致すれば署名検証成功
  // 署名失敗時は401エラーを返す
  if (_headerSignature !== _bodySignature) {
    console.log('Unauthorized');
    return new Response('Unauthorized', { status: 401 });
  }
 
  // 署名成功後の処理
  // ここではOKを返すだけ
 
  return new Response('OK');
});

上記の Webhook は署名の検証を行うだけのものです。
実際には署名検証が成功した後にはリクエストボディの内容を使って何らかの処理を行う形になると思います。

まとめ

Web API などでよく使われる HMAC SHA256 を使ってリクエストの署名を検証する方法を解説しました。 Deno で Webhook などの署名検証をする際に参考になれば幸いです。