より高度な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、そのセッションだけ参照/ダウンロード可能とすることも簡単です。ユーザーID/グループIDでも同様なことが可能です。
ユーザーに入力させるパスワード(導出鍵)は最初の10文字程度でも十分な場合が多いでしょう。この場合、HMACハッシュ値の必要文字数分だけ同じかチェックします。
- セッション限りとするにはsalt(事前共有鍵)を$_SESSIONに保存し、saltが一致しているか確認するだけです。 ↩