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

HMACの応用的な使い方をここ数本のブログで書いてきましたが、HMACの基本的な使い方を紹介していませんでした。リクエストパラメーターを安全に検証/バリデーションする方法を例に紹介します。unserialize()を安全に利用する利用例にもなります。

参考:

HMACハッシュの使い方のまとめ

HMACが考案された背景

HMACをAPIキーのバリデーションに使う方法を紹介する前に、何故HAMCが考案されたのか紹介します。

HMACはRFC 2104で標準化されています。HMACは基本的に暗号学的なハッシュ関数をより安全に利用するために考案されました。暗号学的なハッシュ関数といってもコンピュータの高速化やハッシュアルゴリズム自体の脆弱性の発見などにより、時間と共に危殆化します。

ハッシュ関数が「暗号学的なハッシュ関数」とされる条件を満たしていれば

<?php
$massage = 'I want to verify this text is the original one'; // サイン対象のデータ
$key = 'kV3lFtSK/AiAB2dK94dImJl+q2HMbX6ChopSEhP3MrdA'; // CSPRNG(random_bytes)で作った強く安全な鍵
$secure_signature = hash('sha256', $message . $key); // $secure_signatureは暗号学的に安全なサイン(指紋)
?>

と、文字列連結($message . $key)しても大丈夫なはずです。

しかし、実際には暗号学的なハッシュ関数と言われていたMD5は使い物になりませんし、SHA-1でもコストさえ気にしなければ攻撃できることが実証されてしまいました。

現在は「暗号学的ハッシュ関数」であっても、単純に”文字列連結”をしているとハッシュ値の衝突を意図的に作ることが可能になる可能性があります。

暗号学的ハッシュ関数の攻撃方法に誕生日攻撃と呼ばれる手法があります。こうした攻撃に対して「暗号学的ハッシュ関数」をより安全に利用するためHMACは考案されました。ハッシュ値の衝突を効率的に作り出せる脆弱性がない限り、論理的にHMACは安全なハッシュ値を生成します。詳しくはRFC 2104やHMACの論文で解説されています。

つまり、サイン用にあるデータのハッシュ値を取りたい場合に、データと鍵を文字列連結するより、以下のようにHMACを使ってハッシュ値を取得する方が安全です。

<?php
$massage = 'I want to verify this text is the original one'; // サイン対象のデータ
$key = 'kV3lFtSK/AiAB2dK94dImJl+q2HMbX6ChopSEhP3MrdA'; // CSPRNGで作った強く安全な鍵
$secure_signature = hash_hmac('sha256', $message, $key); // $secure_signatureは暗号学的に安全なサイン(指紋)
?>

 

HMACを使った安全なリクエストパラメーターのバリデーション

HMACを使うと安全にデータにサインしたハッシュ値を取得できることを利用して、安全にリクエストパラメーターをバリデーションします。

HTMLフォームの場合、入力したデータを確認用に一度表示してから送信することがあります。  1 素直に実装すると、

  1. ユーザー入力値の文字エンコーディング、長さ、形式などをバリデーションする。入力エラーがあった場合は修正用のフォームを再度表示する。
  2. 確認用ページを表示し、バリデーション済みのデータをフォームの隠しフィールドとして保存する。
  3. 最終に記入したデータを「送信しますか?」と聞いてデータを送信する。

といった処理になります。2の一時的にデータを保存する場所をセッション変数にすることも可能ですが、フォームを送信しなかった場合は無駄に大きなセッション変数を保持することになります。これを避けるために普通は隠しフィールドを使うと思います。

1でバリデーションしているのに、3で送信した際にもう一度バリデーションすることは出来れば避けたいです。検証済みの入力データの改ざんを検出できれば、再度バリデーションしなくても済みます。HMACを利用すると簡単に改ざん検出を行えます。

  1. サイン用の鍵となる強い鍵をセッション変数に保存する。例)$_SESSION[‘hmac_key’]など
  2. バリデーションした入力値のキーと値、1の鍵でHMACハッシュ値を取得し、確認ページの隠しフィールドとして保存する。例)$inputs =serialize($_POST); $inputs_hash = hash_hmac(‘sha256’, $inputs , $_SESSION[‘hmac_key’])
  3. $inputs_hashと$inputsの内容を次のページに隠しフィールドとして保存する。
  4. 送信したら、各ページの$inputs_hashと$inputsを$_SESSION[‘hmac_key’]を使ってバリデーションする。例)hash_equals($_POST[‘inputs_hash’], hash_hmac(‘sha256’, $_POST[‘inputs’], $_SESSION[‘hmac_key’])) が真ならOK

注意:$_SESSION[‘hmac_key’]はrandom_bytes()などで生成した暗号学的に強い鍵であること。

この手順でバリデーションすると、一度バリデーションしたデータを再度バリデーションしなくても、暗号学的に安全性が保証できます。

まとめ

HMACは色々便利に使えます。PHP 7.3からHMACを拡張したHKDFも使えるようになるので、より便利にハッシュが使えるようになります。

この例ではユーザー(Webブラウザ)にserialize()したデータを渡し、送ってきたデータをunserialize()していますが、HMACでバリデーションしているので安全性に問題は発生しません。serialize()したデータをユーザー(Webブラウザなど)に渡して使う場合、このようにしてバリデーションしてから使わなければなりません。2

 

 


  1. 今時のWebアプリなら画面遷移せずクライアント上だけで確認ページを表示し、最終的にサーバーにデータ送信する場合にだけPOSTする、という方法も使えます。ここでは昔ながらのHTMLフォームを想定します。 
  2. 信頼できないデータはunserialize()してはいけません。これはPHPに限らず、RubyやPythonでもマニュアルに「信頼できないデータはアンシリアライズしてはならない」と記載されています。 

投稿者: yohgaki