📜 ⬆️ ⬇️

We sign data: HMAC in practice in API and Web-forms

HMAC (short for hash-based message authentication code, message authentication code using one-way hash functions) is in cryptography, one of the mechanisms for verifying the integrity of information that ensures that data transmitted or stored in an unreliable environment is not were changed by outsiders (man-in-the-middle attack).

Such data may include, for example, data transmitted in API requests when the integrity of the information transmitted is critical, or when transmitting data from Web forms.

Why do you need it?


If we deviate from the scientific formulation, what is the signature of the data and how is this implemented in practice?

Suppose we want to send some data to another person, and it is important for both us and the recipient to make sure that the data will not be changed during the transfer.
')
For example, we have the original data array of the form:

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

The simplest thing we can do is somehow serialize an array (bring it into a string representation), add some secret key to the end of the resulting string — a character set known only to us and the recipient of data (let’s be “mysecretkey”), after which apply a hash function to this, say, md5.

What practical solutions can you find? Depending on whether the values ​​of an array or its keys are important, you can find, for example, the following implementations:

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

or

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

What are the pros and cons of these implementations?

The very first and obvious plus, but he is the only one - it is the simplicity of implementation. Minuses? Their weight, at least as key shortcomings, can be cited:


It should be noted that the last drawback in some cases is not a drawback as such - for example, if we want to check data from a web form filled by a person and can check only the integrity of the set of fields, and their values ​​are unknown to us in advance.

In particular, in this way, it is possible to form more advanced CSRF tokens for forms, using some internal identifier associated with a user session as a secret key. Thus, we will solve two problems at once - both protection against CSRF and monitoring the integrity of the set of passed parameters - the user will not be able to “play” with the form fields and try to add or remove something from the parameters.

The remaining three points need to be addressed. With the first one, everything is quite simple - we use more modern and hash agglome racks with a greater length of cipher programs, such as SHA256 or SHA512, and sleep well.

The second item is also solved if it is determined that the elements of the array will be sorted according to some principle, say in alphabetical order.

Add before array serialization:

 ksort($data); 

as a result, we get an array sorted by keys in alphabetical order, and it doesn't matter to us in what sequence the variables in the array were transferred.

The next problem is not so easy to solve. While we were working with flat arrays, everything was quite simple - sorted, folded into a line with some type separator ”;” - and it’s done. But what about the nested (multidimensional) arrays?

First, the ksort function is not recursive, but this is certainly not a big problem and the solution was found fairly quickly:

 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; } 

Secondly, an array with nestings cannot be folded linearly into a line — you need to invent additional rules (that is, reinvent the wheel), or use already “real” serialization, such as JSON, which would take into account all nested structures. Using JSON also solves the fourth problem, since we serialize the entire array at once, not limited to its keys or values ​​separately.

Why JSON and not just serialize PHP? The choice in favor of JSON did not fall by chance, because it is a very popular serialization format, with which it will be easy to work not only in PHP, but also in any other popular programming languages, such as Java. Our implementation should be extremely portable to other platforms and using JSON serialization will be the easiest to do.

In this case, all the above problems are solved, but the question arises with the secret key - of course, you can do a simple concatenation of the key to the right, but this is not very aesthetic, since PHP has an implementation of HMAC with the choice of an arbitrary hash function:

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

HMAC implements an additional XOR data with a key and wraps the specified hash function on top. The algorithm itself inside the HMAC is described in detail in the cryptography literature, or in Wikipedia, and we will not describe here exactly how data is encrypted on the key. Just use this standard agglomerate.

Summarizing everything stated together, we will develop a simple class that would implement all the described actions for obtaining a signature from a multidimensional array, regardless of the sequence in which the keys are inside the array.

So, the following plain code is obtained:

 //   ,    . 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; } } 

Briefly analyze the functionality of the class. In the class properties, two private variables are declared for the key and the algorithm, as well as the variable $ sign_param_name, which contains the name of the parameter with a signature (default is “hmac”), which will be used when checking data by the check_data_hmac method by default.

One mandatory parameter is passed to the constructor - this is the secret key. By default, the sha256 hash algorithm is selected. You can override the algorithm by passing it the second parameter to the constructor. If the transmitted algorithm is not supported by the system, the value of the E_UNSUPPORTED_HASH_ALGO constant (that is, -1) will be returned.

To create a signature method is provided:

 make_data_hmac($data, [$key]) 

Everything is quite simple with it - the required argument is data, you can also use another secret key to generate a signature, passing it by the second parameter.

To verify the previously created signature, we implemented the method

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

The method takes arguments:


The signature itself must be inside $ data in the parameter with the $ sign_param_name key. If the latter is not passed, then the name from the property of the $ this-> sign_param_name object will be used.

The rest of the logic is very simple - we collect the signature, we compare the case-insensitively received signature with the signature passed in the data.

The set_hash_algo method allows changing the hash function algorithm after creating an object instance. The recursive array sorting function is implemented as a static method so that it can be used outside of an object instance elsewhere.

Examples


We illustrate the work of the class with a simple example:

 //      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"; 

At the output we get:

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

Instead of conclusion


We got a simple implementation that allows us to sign any data and validate the transferred signed data. Now you can sign data transmitted via the HTTP / REST API, or create advanced CSRF tokens for the forms and be sure that the received data is original and consistent.

All source codes are available in the repository on GitHub: github.com/idsolutions/HMAC_generator

PS You can fork and modify the class at its discretion, comments and suggestions are welcome.

Source: https://habr.com/ru/post/262341/


All Articles