ハッシュ(HMAC)を使ってパスワード付きURL/URIを作る方法

(Last Updated On: 2018年8月18日)

より高度なCSRF対策 – URL/URI個別にバリデーションする方法でハッシュ(HMAC)を使えばデータベースを使わずに有効期限付きのURLを作れる、と紹介しました。今回はパスワード付きURL(URI)の作り方を紹介します。

まず、安全性についてはハッシュ(HMAC)を使って有効期限付きURLを作る方法を参照してください。

ハッシュ(HMAC)を使ってパスワード付きURL/URIを作る方法

ハッシュ(HMAC)を使って有効期限付きURL/URIを作る方法とほとんど同じです。以下の方法でデータベースを使わずにパスワード付き(この例の場合は有効期限付き)のURIを作れます。

<?php
// 有効期限付きURIの作成
 
// 有効期限付きURIにするURI 
$uri = 'http://example.com/some_path';
// 秘密鍵 - 'secure random data'はrandom_bytes()などで生成した安全な値
define('SUPER_SECRET_MASTER_KEY', 'secure random data');
 
$salt = bin2hex(randome_bytes(32)); // 事前共有鍵
$expire = time() + 3600; // 有効期限
$context = serialize([$uri, $expire]); // コンテクスト - $uriと$expire
$prk = hash_hmac('sha256', SUPER_SECRET_MASTER_KEY, $salt);
$derived_key = hash_hmac('sha256', $prk, $context); // 導出鍵(パスワード) - これをユーザーに渡す
 
$params = http_build_query(['key2' => $salt, 'context' => $context]);
 
// 要パスワード+有効期限付きURI - これをユーザーに渡す
$uri_with_expiration = $uri . '?' . $params);
?>
 
<?php
// 要パスワード+有効期限付きURIのチェック

// 導出鍵(パスワード)が無い場合、導出鍵(パスワード)を入力する
// フォームなどを開いてユーザーに送信させる。
// ユーザーから送られてきた導出鍵(パスワード)
$derived_key_entered = $_GET['key1'];

// 送られてきた事前共有鍵 - ユーザーに渡したURL
$salt_from_request = $_GET['key2'];
// コンテクスト - $uriとexpire
$uri_requested = get_uri_without_keys($_SERVER['REQUEST_URI']); // この関数でkey2とexpireを削除
$context = serialize([$uri_requested, $_GET['expire']);
 
$prk = hash_hmac('sha256', SUPER_SECRET_MASTER_KEY, $salt_from_request);
$derived_key = hash_hmac('sha256', $prk, $context);
 
// 暗号学的に安全な導出鍵が一致すればアクセスOK
if (!hash_equals($derived_key_entered, $derived_key)) {
    throw new Exception('不正なリクエスト');
}
if ($_GET['expire'] < time()) {
    throw new Exception('有効期限切れ');
}
// アクセスOK
?>

 

まとめ

有効期限付きURL/URIと異る部分は

  • HMACによって導出された鍵をURLに付けず、ユーザーに入力させる

だけです。

確実に正しい(到達可能な)メールアドレスを入力したユーザーにだけ参照/ダウンロードを許可する、といった場合に便利です。

  1. ページにアクセスした際に「このページの参照にはメールアドレスの確認が必要です。メールアドレスを入力してください」といったフォームを表示する。
  2. ユーザーがメールアドレスフォームを使ってパスワードをリクエストする。
  3. サーバーは生成した鍵をメールを使ってユーザーに通知すると同時に「メールで送信したパスワードを入力してください」といったフォームを表示する。
  4. ユーザーがパスワードを送信し、サーバーはパスワードが正しければアクセスを許可する。

例えばセッションと組み合わせて使えば1、そのセッションだけ参照/ダウンロード可能とすることも簡単です。ユーザーID/グループIDでも同様なことが可能です。

ユーザーに入力させるパスワード(導出鍵)は最初の10文字程度でも十分な場合が多いでしょう。この場合、HMACハッシュ値の必要文字数分だけ同じかチェックします。


  1. セッション限りとするにはsalt(事前共有鍵)を$_SESSIONに保存し、saltが一致しているか確認するだけです。 

投稿者: yohgaki