データに署名します:APIおよびWebフォームでの実際のHMAC

HMAC(英語のハッシュベースのメッセージ認証コード、一方向ハッシュ関数を使用したメッセージ認証コードの略)-暗号化では、信頼されていない環境で送信または保存されたデータがそうでないことを保証するために情報の整合性をチェックするメカニズムの1つ権限のない人によって変更されました(「中間者」のような攻撃)。



このようなデータには、たとえば、送信された情報の整合性が重要な場合、またはWebフォームからデータを転送する場合に、APIリクエストで送信されるデータが含まれます。



なぜこれが必要なのですか?



科学的定式化から離れると、データ署名とは何であり、実際にこれはどのように実装されますか



他の人にデータを送信したい場合、転送中にデータが変更されないことを確認することが重要です。



たとえば、次の形式のデータの初期配列があります。



$data = array( 'param1' => 'value1', 'param2' => 'value2', 'param3' => 'sometext', 'a' => 'b' );
      
      





できる最も簡単な方法は、何らかの方法で配列をシリアル化し(文字列表現に変換)、結果の文字列の最後に秘密キーを追加することです。文字セットは、私たちとデータ受信者だけが知っています(「mysecretkey」)ハッシュ関数をこれに適用します(md5など)。



どのような実用的な解決策が見つかりますか? 配列またはそのキーの値が重要かどうかに応じて、たとえば次のような実装を見つけることができます。



 $hash = md5(implode(";",array_keys($data)).";"."mysecretkey")) = md5("param1;param2;param3;a;mysecretkey")
      
      





または



 $hash = md5(implode(";",array_values($data)).";"."mysecretkey") = md5("value1;value2;sometext;b;secretkey")
      
      





これらの実装の長所と短所は何ですか?



非常に最初の明白なプラスですが、それも唯一のものです-実装が簡単です。 短所? その質量は、少なくとも重要な欠点として、以下につながる可能性があります。





後者の欠点は、場合によってはそのような欠点ではないことに注意する必要があります。たとえば、人が記入したWebフォームからデータを確認したい場合、フィールドのセットの整合性しか確認できず、その値は事前に不明です。



特に、この方法では、ユーザーセッションに関連付けられた内部識別子を秘密キーとして使用して、フォームのより高度なCSRFトークンを生成できます。 したがって、2つの問題を一度に解決します-渡されたパラメーターセットのCSRF保護と整合性制御の両方-ユーザーはフォームフィールドを "再生"できなくなり、パラメーターに何かを追加または削除しようとしなくなります。



残りの3つのポイントに対処する必要があります。 1つ目は、すべてが非常に単純です。SHA256やSHA512など、より長い暗号長を備えた最新のハッシュアルゴリズムを使用し、静かにスリープします。



2番目のポイントは、配列の要素が何らかの原則に従って、たとえばアルファベット順にソートされると判断された場合にも解決されます。



配列をシリアル化する前に追加します。



 ksort($data);
      
      





その結果、アルファベット順にキーで並べ替えられた配列を取得し、配列内の変数がどの順序で転送されたかは関係ありません。



次の問題はそれほど簡単に解決されません。 フラット配列で作業している間は、すべてが非常に単純でした-並べ替えられ、タイプ「;」のセパレーターで行に折り畳まれました-これで完了です。 しかし、ネストされた(多次元)配列はどうでしょうか?



まず、ksort関数は再帰的ではありませんが、これはもちろん大きな問題ではなく、解決策はすぐに見つかりました。



 function ksort_recursive(&$array, $sort_flags = SORT_REGULAR) { if (!is_array($array)) return false; ksort($array, $sort_flags); foreach ($array as &$arr) { ksort_recursive($arr, $sort_flags); } return true; }
      
      





第二に、線に直線的にネストする配列を追加することはできません-追加のルールを考え出す(つまり、車輪を再発明する)か、ネストされたすべての構造を考慮するJSONなどの既に「実際の」シリアル化を使用する必要があります JSONを使用すると、キーまたは値を個別に制限するのではなく、配列全体を一度にシリアル化するため、4番目の問題も解決します。



PHPを単純にシリアライズするのではなく、正確にJSONにするのはなぜですか? JSONを選択した理由は偶然ではありません。これは非常に一般的なシリアル化形式であり、PHPだけでなくJavaなどの他の一般的なプログラミング言語でも簡単に使用できるためです。 実装は、他のプラットフォームへの移植が非常に簡単で、JSONシリアル化を使用するのが最も簡単です。



この場合、上記の問題はすべて解決されますが、秘密鍵で問題が発生します-もちろん、右側の鍵の単純な鍵連結を作成することは可能ですが、PHPは任意のハッシュ関数を選択できるHMAC実装を備えているため、あまり美しくありません:



 hash_hmac(“sha256”,$data,”mysecretkey”);
      
      





HMACは追加のXORデータをキーで実装し、指定されたハッシュ関数を上にラップします。 HMAC内のアルゴリズム自体については、暗号に関する文献またはWikipediaで詳細に説明されています。ここでは、キーのデータの暗号化がどのように発生するかについては正確に説明しません。 この標準的な凝集を使用します。



上記のすべてを組み合わせて、キーが配列内にある順序に関係なく、多次元配列から署名を取得するためのすべての説明された手順を実装する単純なクラスを開発します。



そのため、次の簡単なコードが判明しました。



 //   ,    . define("E_UNSUPPORTED_HASH_ALGO",-1); class HMAC_Generator{ private $key, $algo; private $sign_param_name = "hmac"; function __construct($key, $algo = "sha256"){ $this->key = $key; $this->algo = $algo; } function make_data_hmac($data, $key = NULL){ //       -    if(empty($key)) $key = $this->key; //        - . if(isset($data[$this->sign_param_name])) unset($data[$this->sign_param_name]); //       - //  ,     // ,    GET-  POST-. HMAC_Generator::ksort_recursive($data); //  JSON (   -    encode_string) $data_enc = $this->serialize_array($data); //     return $this->make_signature($data_enc, $key); } function check_data_hmac($data, $key = NULL, $sign_param_name = NULL){ //       -    if(empty($key)) $key = $this->key; //           -    if(empty($sign_param_name)) $sign_param_name = $this->sign_param_name; //      -   false if(empty($data[$sign_param_name])) return false; //  HMAC      ,   , //          $hmac = $data[$sign_param_name]; unset($data[$sign_param_name]); //   HMAC $orig_hmap = $this->make_data_hmac($data, $key); //    if(strtolower($orig_hmap) != strtolower($hmac)) return false; else return true; } //    function set_hash_algo($algo){ //     $algo = strtolower($algo); // ,      if(in_array($algo, hash_algos())) $this->algo = $algo; else return E_UNSUPPORTED_HASH_ALGO; } // //    -    ,      // private function serialize_array($data){ //    json,           PHP, //    -  $data_enc = json_encode($data, JSON_UNESCAPED_UNICODE); return $data_enc; } // ,      ,  HASH HMAC private function make_signature($data_enc, $key){ //   HMAC     $hmac = hash_hmac($this->algo, $data_enc, $key); return $hmac; } //          public static function ksort_recursive(&$array, $sort_flags = SORT_REGULAR) { //     -   false if (!is_array($array)) return false; ksort($array, $sort_flags); foreach ($array as &$arr) { HMAC_Generator::ksort_recursive($arr, $sort_flags); } return true; } }
      
      





クラスの機能を簡単に調べます。 キーとアルゴリズムの2つのプライベート変数は、クラスプロパティで宣言されます。また、$ sign_param_name変数には、署名付きのパラメーター名(デフォルトでは「hmac」)が含まれます。これは、デフォルトでcheck_data_hmacメソッドでデータをチェックするときに使用されます



1つの必須パラメーターがコンストラクターに渡されます-これは秘密鍵です。 デフォルトでは、sha256ハッシュアルゴリズムが選択されています。 コンストラクターに2番目のパラメーターを渡すことで、アルゴリズムをオーバーライドできます。 送信されたアルゴリズムがシステムでサポートされていない場合、定数E_UNSUPPORTED_HASH_ALGOの値(つまり、-1)が返されます。



署名を作成するためのメソッドが提供されます:



 make_data_hmac($data, [$key])
      
      





すべてが非常に簡単です-必要な引数はデータです。別の秘密鍵を使用して、2番目のパラメーターを渡すことで署名を生成することもできます。



以前に作成した署名を検証するために、メソッドを実装しました



 check_data_hmac($data, [$key], [$sign_param_name])
      
      





メソッドは引数を取ります:





署名自体は、キー$ sign_param_nameを持つパラメーターの$ data内にある必要があります。 後者が渡されない場合、オブジェクトのプロパティからの名前$ this-> sign_param_nameが使用されます。



残りのロジックは非常に単純です。署名を収集し、大文字と小文字を区別せずに受信した署名をデータで送信された署名と比較します。



set_hash_algoメソッドを使用すると、オブジェクトのインスタンスを作成した後、ハッシュ関数アルゴリズムを変更できます。 再帰的配列ソート関数は静的メソッドとして実装されているため、オブジェクトインスタンスの外部で使用できます。





簡単な例を使用して、クラスの操作を説明します。



 //      param1, param2, param3    ksort $data = array( 'param3' => 'sometext', 'param1' => 'value1', 'param2' => 'value2', ); //  , - - SHA256 $hmac_generator = new HMAC_Generator("myprivatekey"); $hmac_generator_md5 = new HMAC_Generator("myprivatekey","md5"); $hmac_generator_sha1 = new HMAC_Generator("myprivatekey","sha1"); echo "SHA256: ".$hmac_generator->make_data_hmac($data)."\n"; echo "MD5: ".$hmac_generator_md5->make_data_hmac($data)."\n"; echo "SHA1: ".$hmac_generator_sha1->make_data_hmac($data)."\n";
      
      





出力では次のようになります。



 SHA256: 7f0a656e00d3a17ab0d04170dfcb4583b4e29e184b9a24d7fed869979d0bf7e8 MD5: 4f91a268c5a8fc4eaa19d7d7cf329583 SHA1: 8c4a7288be7a76fa2c1bd7d481718d1c49d6bca0
      
      





結論の代わりに



データに署名し、送信された署名済みデータを検証できるようにする簡単な実装を取得しました。 これで、HTTP / REST APIを介して送信されたデータに署名したり、フォーム用の高度なCSRFトークンを作成して、受信したデータが元の一貫したものであることを確認できます。



すべてのソースコードはGitHubリポジトリで入手できます: github.com/idsolutions/HMAC_generator



PSあなたはあなたの裁量でクラスを分岐して修正することができます、コメントと提案は大歓迎です。



All Articles