Yavor Atanasov web portfolio

Weblog

2010
07.08

Securing Hidden InputInput fields of type “hidden” are non-interactive form elements that are not rendered by the browser (remaining “hidden” from the user). This type of input element represents a value that is not intended to be examined or manipulated by the user. Its sole purpose is to facilitate data round-trips from and to the web server, thus, trying to solve implications caused by the fact that web servers are stateless. In some respect, the role of the hidden input field intersects with the one of the cookie – trying to maintain a state between http requests.

There are numerous examples and reasons why one would use hidden input fields in a form, but mainly they all boil down to the following – the server sends out data it wants back unchanged.

Hidden input general case

Figure 1. General case of hidden input data round trip

The name “hidden”, however, is very misleading because it creates a false sense of security. Nothing on the user machine can really be hidden and therefore virtually anything can be changed. That is why the upper definition says “not intended to be examined” and not “cannot be examined”.

Bottom line: Hidden input fields are neither hidden, nor unchangeable. Once data has left the realm of the web server, it should be treated as possibly contaminated and potentially dangerous. Hidden fields are no exception.

How to make hidden input fields truly hidden and unchangeable

The simple answer is – you can’t. What you can do, however, is make the hidden input’s value unreadable and check to see if the value remained unchanged after the round trip to the user machine. Although web security is never trivial and requires careful assessment of the possible vulnerabilities , this particular task has a relatively trivial solution.

Encryption and hashing

When it comes to hiding (or protecting) information, we essentially talk about encryption. Encryption takes a piece of readable information and using a key and a special algorithm turns this information into a scrambled unreadable string. Later on, using an appropriate (not necessarily the same) key and algorithm, the scrambled string can be turned back into the original readable message (decryption).

Hashing, also referred to as one-way-encryption, employs algorithms that turn the input into a unique value (digest) and make reversion back to the original input impossible (collisions are theoretically possible but computationally infeasible; that depends on the hashing algorithm and technique used, but that is a whole other discussion). The fact that no two inputs would produce the same digest makes hashing great for verifying data.

So, encryption makes data safe to travel and hashing ensures the data arrives unchanged. All it takes is for both the origin and destination to have the appropriate key and to know the encryption and hashing algorithms. That is what is needed to tackle the security issues with hidden input fields.

encrypt-decrypt

Figure 2. General case of encryption-decryption and hashing correspondence

Applying encryption and hashing to hidden input fields

If we need to translate Figure 2 to our specific case, the web server is both the origin and destination of the data, and the user machine is the untrusted medium that can compromise the data.

Hidden input with encrypt and hash

Figure 3. Applying encryption and hashing to hidden input

As Figure 3 indicates the necessary steps needed to be taken to implement the solution are as follows:

  • Hash the original value
  • Encrypt the original value
  • Send the encrypted value along with the hash digest
  • Upon receiving back the data, decrypt the encryption, hash the result and compare it to the received hash digest to ensure the value remained unchanged

All that is left is the implementation itself…

Implementing the solution in PHP

PHP provides functions and extensions that deal with encryption and hashing. There are many ways one can implement encryption and hashing – numerous algorithms and techniques. This is the time to say that there is not a right or wrong way of doing things. It all depends on the level of security one strives to achieve. The field of cryptography and security is pretty dynamic and one should always investigate current developments and recommendations.

That being said, the proposed PHP solution should not be taken as the only possible one nor the best (it certainly is not, although it is quite decent). It is just for practical illustration of the upper-described concept.

Encryption-decryption in PHP using MCRYPT extension

The MCRYPT extension provides an interface to the mcrypt library, which supports a wide variety of block algorithms including DES, TripleDES, Blowfish and many more. This example will make use of the Blowfish cipher (64-bit block size) with a 128 bit key. MCRYPT provides a function that implements the supported list of ciphers – mcrypt_encrypt(). Here is the function description and parameters:

string mcrypt_encrypt ( string $cipher , string $key , string $data , string $mode [, string $iv ] )

The list of parameters that we are going to use is as follows:

  • $cipher – this takes one of the predefined constants MCRYPT_ciphername, in our case MCRYPT_BLOWFISH
  • $key – in order to generate a 128-bit key, we can use any string and hash it with the 128-bit md5 functionmd5(‘ESP_guitars_rock’). This would produce the following key: 6cf50d50aa1174778a0c24fffa558593
  • $data – this the message/value that needs to be encrypted, in our case we will encrypt the super secret message – ‘Bing is actually famous, check in Google’
  • $mode – one of the MCRYPT_MODE_modename constants, in our case MCRYPT_MODE_CFB
  • $ivinitialisation vector that needs to be the same size as the block size of the cipher. Blowfish has a 64-bit block size. However, we do not have to remember that. We can use the mcrypt_get_iv_size() function to get the necessary size.

This is how the final implementation of mcrypt_encrypt() looks like:

<?php
        $key = md5(‘ESP_guitars_rock’);
        $original_value = ‘Bing is actually famous, check in Google’;
        $iv = substr(md5(‘Metallica’),0,mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CFB));
        $encrypted_value = base64_encode(mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $original_value, MCRYPT_MODE_CFB, $iv));

Note: We need to encode the output of the mcrypt_encrypt() function with base64_encode(). This encodes the binary data with MIME base64 so it survives transportation when it travels from and to the web server. Otherwise, the data might (most probably will) arrive corrupted and this would break the decryption process. When the data arrives back at the server base64_decode() is used to decode the data back to the original output of mcrypt_encrypt().

The decryption process is carried out in a similar way, this time using the mcrypt_decrypt() function, which takes the same parameters as mcrypt_encrypt(). Note that the $encrypted_value has to be decoded first with base64_decode().

$decrypted_value = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, base64_decode($encrypted_value), MCRYPT_MODE_CFB, $iv);
Hashing in PHP

PHP provides several hashing options, most common (and strongest) of which are md5() and sha1(). The former generates a 128-bit hash, whereas the latter returns a 160-bit hash. Although that might sound like a lot of bits, modern hardware can generate hashes of this length pretty quickly (hundreds and even thousands per second). This way, using a dictionary (not only words, but also alpha-numeric combinations), an attacker can keep generating hashes until a collision appears – meaning he can find the original value that generated your hash.

One solution to this weakness is simply using stronger hashing functions (more bits) in order to make brute force collision detection computationally infeasible. However, those functions will also put computational pressure on web servers and ultimately make web pages slow. On the other hand, as of this moment md5() and sha1() are the strongest functions implemented for PHP.

Therefore, a technique can be applied to those functions that makes them stronger – adding salts. Salts are random bits of string with sufficient length that are added to the hash. This results in a different hash generated each time from the same input value. Consequently, we need to know the salt value later in order to regenerate the same hash for the data verification.

This might sound a bit confusing at first, however, looking at an example should clear the confusion. For our hashing process, we will use the 160-bit sha1() function. This is how a plain normal hashing in PHP looks like:

//this would produce the followin hash – 3251d0991b55202abe9ede887efab662d3d57399
sha1(‘my_string’);

Now let’s fortify this hash mixing it with a salt:

$original_value = ‘my_string’;
$salt = substr(md5(uniqid(rand(), true)), 0, 10);
$hashed_value = $salt . substr(sha1($salt . $original_value),-10);

Figure 4 illustrates the algorithm used for creating the $salt. The algorithm uses the functions rand(), uniqid(), md5() and substr() to create a unique salt with length of 10. The illustration breaks the process into steps for better understanding of what is going on.

Creating a salt for hashing

Figure 4. Creating a salt for hashing

Then comes the algorithm for hashing – we concatenate the salt to the original string, create a 160-bit hash, take 10 characters of the resulting string (just so the final hash is not too long) and attach the salt to the beginning. This way, later we can extract the value of the salt again knowing it is the first 10 characters.

Creating a hash with salts

Figure 5. Creating a hash with salts

Mixing it all together with hidden input fields

Now it is time to put everything together. The starting point is just a very simple mark-up page with a form containing one hidden field and a submit button. We will create two php documents. One is form.php where the encryption and the initial hashing will take place. The other is process_form.php where the form will post to and where decryption and verification will take place.

This is the mark-up we are going to start with:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8"/>
        <title>Simple Form</title>
    </head>
    <body>
        <form action="process_form.php" method="post">
            <input type="hidden" name="my_value" value="Bing is actually famous, check in Google"/>
            <input type="submit" value="Submit form"/>
        </form>
    </body>
</html>

Below is the modified version (document form.php) with added encryption and hashing. Note that there is an additional hidden field just for the hashed value. That is not necessary and is done just for clarity. The hashed value can be concatenated to the encrypted value.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8"/>
        <title>Simple Form</title>
    </head>
    <body>
        <?php
        $key = md5(‘ESP_guitars_rock’);
        $original_value = ‘Bing is actually famous, I checked in Google’;
        $iv = substr(md5(‘Metallica’),0,mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CFB));
        $encrypted_value = base64_encode(mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $original_value, MCRYPT_MODE_CFB, $iv));
        //Now create a decent hash with a 10 character salt
        $salt = substr(md5(uniqid(rand(), true)), 0, 10);
        $hashed_value = $salt . substr(sha1($salt . $original_value),-10);
        ?>
        <form action="process_form.php" method="post">
            <input type="hidden" name="encrypted_value" value="<?php echo $encrypted_value; ?>"/>
            <input type="hidden" name="hashed_value" value="<?php echo $hashed_value; ?>"/>
            <input type="submit" value="Submit form"/>
        </form>
    </body>
</html>

And finally, below is the processing script (document process_form.php). First a check is made to make sure that the form has been posted properly. Then the encrypted value is decrypted using the same $key and $iv. Then the $salt is extracted from the posted hashed value (the first 10 characters). A new hash is created from the decrypted value and the salt using the same algorithm. Finally this new hash is compared to the original one posted by the form. If they match, this means the data came back valid and unchanged and further processing can continue (in this case just print). Otherwise, something went wrong and the data was corrupted, either intentionally or not.

<?php
if (!isset($_POST[‘encrypted_value’]) || !isset($_POST[‘hashed_value’])) {
    die(‘Submission failed’);
} else {
    $key = md5(‘ESP_guitars_rock’);
    $iv = substr(md5(‘Metallica’),0,mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CFB));
   $decrypted_value = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, base64_decode($_POST[‘encrypted_value’]), MCRYPT_MODE_CFB, $iv);
    //Now let’s recreate the hash
    //We can extract the salt from the original hash
    $salt = substr($_POST[‘hashed_value’], 0, 10);
    $new_hashed_value = $salt . substr(sha1($salt . $decrypted_value), -10);
    if($new_hashed_value == $_POST[‘hashed_value’]){
        //This is where you process further the validated value. In our case just print
        print $decrypted_value;
    }
    else{
        //The hidden value has been changed and is therefore invalid
        print ‘The message is corrupted’;
    }
}

Final words

How far should a developer go with security depends on the situation at hand. Sometimes encryption of hidden fields may not be necessary – in case the developer does not care if the user will see the value or not. In any case, however, I strongly believe that hash verification should always be implemented. This would give the developer a piece of mind that data came back to the server unchanged and that no discrepancies would break his/her application.

Resources and further readings

9 comments so far

Add Your Comment
  1. Oddly enough, I’ve never thought of using encryption on hidden fields…makes sense though

  2. Well, encryption is not always necessary. I myself have never had to put anything too classified in an input field and haven’t used encryption that much. However, hashing validation is always a good idea, especially if wrong data can impede further processing.

  3. I just stumbled upon this article. Quite nice, I’ll def use this technique next time I work with forms. thanks! (cool website too)

  4. Thanks! I’m a web newbie and this was a helpful read. Greetings from Finland!

  5. I will implement this in my own form handling scripts, thanks a bunch!

  6. In all the websites I’ve made that require users to login, I always store their passwords in my database in a md5(‘salt’.'password’) form and then use the same method to verify their password when logging in. Good article.

  7. Its exactly what I was looking for. However, I am facing a problem decrypting it. If you could help me figure out the problem please.

    On the form page the encrypted value of number 10 is coming as ‘bVE=’. But on form submission if I try decoding it, its not happening. So, I manually encrypted the value 10 on the submission page and realized that the encrypted value is not the same. Its coming as ‘fOk=’. I checked if $iv is the problem, but its not.

  8. Very useful, nicely written and explained article! I was looking for an encryption-decryption method for my web development using PHP, this article is a great help for me. This is exactly the thing I was looking for. Thanks for the contribution. Great Article!

  9. nice stuff

Your Comment