DEV Community

Cover image for How To Create A Simple Nonce in PHP
Simon Ugorji
Simon Ugorji

Posted on

How To Create A Simple Nonce in PHP

A Nonce is a number or a token used only once.

You can use Nonce in your pages or forms to add an extra layer of security to your App and one of its features is to differentiate humans from bots.

Today, I will show you how to create a simple Nonce in PHP, and how you can validate it.

LET'S BEGIN

First, we will create a class that will have private and public methods that will generate and validate our Nonce based on the secret hash which we will define.

OUR CLASS

session_start();

define('NONCE_SECRET', 'CEIUHET745T$^&%&%^gFGBF$^');

class Nonce {
/**
     * Generate a Nonce. 
     * 
     * The generated string contains four tokens, separated by a colon.
     * The first part is the salt. 
     * The second part is the form id.
     * The third part is the time until the nonce is invalid.
     * The fourth part is a hash of the three tokens above.
     * 
     * @param $length: Required (Integer). The length of characters to generate
     * for the salt.
     * 
     * @param $form_id: Required (String). form identifier.
     * 
     * @param $expiry_time: Required (Integer). The time in minutes until the nonce 
     * becomes invalid. 
     * 
     * @return string the generated Nonce.
     *
     */

    /**
     * Verify a Nonce. 
     * 
     * This method validates a nonce
     *
     * @param $nonce: Required (String). This is passed into the verifyNonce
     * method to validate the nonce.
     *  
     * @return boolean: Check whether or not a nonce is valid.
     * 
     */
}
Enter fullscreen mode Exit fullscreen mode

Now we need to set up a private function (method) that will generate random characters which is our salt, up to the characters that we will specify in its length parameter.

Let us make this method a public function, as we test run our code.

public function generateSalt($length = 10){
    //set up random characters
    $chars='1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM';
    //get the length of the random characters
    $char_len = strlen($chars)-1;
    //store output
    $output = '';
    //iterate over $chars
    while (strlen($output) < $length) {
         /* get random characters and append to output 
               till the length of the output is greater than the length provided */
        $output .= $chars[ rand(0, $char_len) ];
    }
    //return the result
    return $output;
}
Enter fullscreen mode Exit fullscreen mode

Here's the full code

session_start();

define('NONCE_SECRET', 'CEIUHET745T$^&%&%^gFGBF$^');

class Nonce {

    public function generateSalt($length = 10){
        //set up random characters
        $chars='1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM';
        //get the length of the random characters
        $char_len = strlen($chars)-1;
        //store output
        $output = '';
        //iterate over $chars
        while (strlen($output) < $length) {
            /* get random characters and append to output till the length of the output 
             is greater than the length provided */
            $output .= $chars[ rand(0, $char_len) ];
        }
        //return the result
        return $output;
    }
}
Enter fullscreen mode Exit fullscreen mode

Let us use the new keyword to create an instance of the class, and invoke the generateSalt() method.

$nonce = new Nonce();

var_dump($nonce->generateSalt(22));

Enter fullscreen mode Exit fullscreen mode

This is our result, notice that the random characters were generated up to the length we specified.

nonce

Now we need another private method that will take advantage of $_SESSION, to store the generated Nonces using the form ID.

private function storeNonce($form_id, $nonce){
    //Argument must be a string
    if (is_string($form_id) == false) {
        throw new InvalidArgumentException("A valid Form ID is required");
    }
    //group Generated Nonces and store with md5 Hash
    $_SESSION['nonce'][$form_id] = md5($nonce);
    return true;
}

Enter fullscreen mode Exit fullscreen mode

Next, we need to create another method that will hash our salt, our secret, and a specific time of expiry as tokens. Then we will generate a nonce by separating each token with a colon and store it in a session variable using the storeNonce method.

public function generateNonce($length = 10, $form_id, $expiry_time){
    //our secret
    $secret = NONCE_SECRET;
    //secret must be valid. You can add your regExp here
    if (is_string($secret) == false || strlen($secret) < 10) {
            throw new InvalidArgumentException("A valid Nonce Secret is required");
    }
    //generate our salt
    $salt = self::generateSalt($length);
    //convert the time to seconds
    $time = time() + (60 * intval($expiry_time));
    //concatenate tokens to hash
    $toHash = $secret.$salt.$time;
    //send this to the user with the hashed tokens
    $nonce = $salt .':'.$form_id.':'.$time.':'.hash('sha256', $toHash);
    //store Nonce
    self::storeNonce($form_id, $nonce);
    //return nonce
    return $nonce;
}
Enter fullscreen mode Exit fullscreen mode

Here's the full code

session_start();

define('NONCE_SECRET', 'CEIUHET745T$^&%&%^gFGBF$^');

class Nonce {
    //generate salt
    public function generateSalt($length = 10){
        //set up random characters
        $chars='1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM';
        //get the length of the random characters
        $char_len = strlen($chars)-1;
        //store output
        $output = '';
        //iterate over $chars
        while (strlen($output) < $length) {
            /* get random characters and append to output till the length of the output 
             is greater than the length provided */
            $output .= $chars[ rand(0, $char_len) ];
        }
        //return the result
        return $output;
    }
    //store Nonce
    private function storeNonce($form_id, $nonce){
        //Argument must be a string
        if (is_string($form_id) == false) {
            throw new InvalidArgumentException("A valid Form ID is required");
        }
        //group Generated Nonces and store with md5 Hash
        $_SESSION['nonce'][$form_id] = md5($nonce);
        return true;
    }
    //hash tokens and return nonce
    public function generateNonce($length = 10, $form_id, $expiry_time){
        //our secret
        $secret = NONCE_SECRET;

        //secret must be valid. You can add your regExp here
        if (is_string($secret) == false || strlen($secret) < 10) {
            throw new InvalidArgumentException("A valid Nonce Secret is required");
        }
        //generate our salt
        $salt = self::generateSalt($length);
        //convert the time to seconds
        $time = time() + (60 * intval($expiry_time));
        //concatenate tokens to hash
        $toHash = $secret.$salt.$time;
        //send this to the user with the hashed tokens
        $nonce = $salt .':'.$form_id.':'.$time.':'.hash('sha256', $toHash);
        //store Nonce
        self::storeNonce($form_id, $nonce);
        //return nonce
        return $nonce;
    }
}

Enter fullscreen mode Exit fullscreen mode

So let us invoke the method generateNonce(), and pass in 5 as the first argument (this will be used to generate our salt), form_login as the second argument (this will be the form we wish to use the nonce on), and 10 as the third or last argument (this will be how long our nonce will last for in minutes).

$nonce = generateNonce();

var_dump($nonce->generateNonce(5, "form_login", 10));
var_dump($_SESSION);
Enter fullscreen mode Exit fullscreen mode

Here's our nonce and its stored value in the session.

nonce

You can see that we have each token separated by a colon in the generated nonce, and we have it stored in the session by hashing it with** md5()**.

Now we need to code a public method that will verify a nonce and return a boolean (true or false), depending on whether or not the nonce is valid.

Before we code this method, you need to understand that:

  • Our nonce is stored in $_SESSION
  • Our tokens are separated by a colon ($salt : $form_id : $time : $hash)
  • We will use the same secret used to generate the Nonce, to verify it.
public function verifyNonce($nonce){
    //our secret
    $secret = NONCE_SECRET;
    //split the nonce using our delimeter : and check if the count equals 4
    $split = explode(':', $nonce);
    if(count($split) !== 4){
        return false;
    }

    //reassign variables
    $salt = $split[0];
    $form_id = $split[1];
    $time = intval($split[2]);
    $oldHash = $split[3];
    //check if the time has expired
    if(time() > $time){
        return false;
    }

    /* Nonce is proving to be valid, continue ... */

    //check if nonce is present in the session
    if(isset($_SESSION['nonce'][$form_id])){
        //check if hashed value matches
        if($_SESSION['nonce'][$form_id] !== md5($nonce)){
            return false;
        }
    }else{
         return false;
    }

    //check if the nonce is valid by rehashing and matching it with the $oldHash
    $toHash = $secret.$salt.$time;
    $reHashed = hash('sha256', $toHash);
    //match with the token
    if($reHashed !== $oldHash){
        return false;
    }
    /* Wonderful, Nonce has proven to be valid*/
    return true;
}
Enter fullscreen mode Exit fullscreen mode

So we have a few conditions in the method above that checks the nonce. This method will only return true when the checks are successful and false when one or more checks fail.

Here are the checks:

  • If the tokens are not complete, the nonce is invalid
  • If Nonce is not stored in the session, the Nonce is invalid
  • If Nonce is stored but the value does not match, the Nonce is invalid
  • If the time has elapsed, the nonce is invalid
  • If there's an alteration to the hash, the nonce is invalid

Let us create a nonce and verify it to confirm if our class works and we can replace the public function generateSalt() with a private function so that it can only be accessed within our class.

Here's the full code


session_start();

define('NONCE_SECRET', 'CEIUHET745T$^&%&%^gFGBF$^');

class Nonce {
    //generate salt
    private function generateSalt($length = 10){
        //set up random characters
        $chars='1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM';
        //get the length of the random characters
        $char_len = strlen($chars)-1;
        //store output
        $output = '';
        //iterate over $chars
        while (strlen($output) < $length) {
            /* get random characters and append to output till the length of the output 
             is greater than the length provided */
            $output .= $chars[ rand(0, $char_len) ];
        }
        //return the result
        return $output;
    }
    //store Nonce
    private function storeNonce($form_id, $nonce){
        //Argument must be a string
        if (is_string($form_id) == false) {
            throw new InvalidArgumentException("A valid Form ID is required");
        }
        //group Generated Nonces and store with md5 Hash
        $_SESSION['nonce'][$form_id] = md5($nonce);
        return true;
    }
    //hash tokens and return nonce
    public function generateNonce($length = 10, $form_id, $expiry_time){
        //our secret
        $secret = NONCE_SECRET;

        //secret must be valid. You can add your regExp here
        if (is_string($secret) == false || strlen($secret) < 10) {
            throw new InvalidArgumentException("A valid Nonce Secret is required");
        }
        //generate our salt
        $salt = self::generateSalt($length);
        //convert the time to seconds
        $time = time() + (60 * intval($expiry_time));
        //concatenate tokens to hash
        $toHash = $secret.$salt.$time;
        //send this to the user with the hashed tokens
        $nonce = $salt .':'.$form_id.':'.$time.':'.hash('sha256', $toHash);
        //store Nonce
        self::storeNonce($form_id, $nonce);
        //return nonce
        return $nonce;
    }
   public function verifyNonce($nonce){
        //our secret
        $secret = NONCE_SECRET;
        //split the nonce using our delimeter : and check if the count equals 4
        $split = explode(':', $nonce);
        if(count($split) !== 4){
            return false;
        }

        //reassign variables
        $salt = $split[0];
        $form_id = $split[1];
        $time = intval($split[2]);
        $oldHash = $split[3];
        //check if the time has expired
        if(time() > $time){
            return false;
        }
        /* Nonce is proving to be valid, continue ... */

        //check if nonce is present in the session
        if(isset($_SESSION['nonce'][$form_id])){
            //check if hashed value matches
            if($_SESSION['nonce'][$form_id] !== md5($nonce)){
                return false;
            }
        }else{
             return false;
        }

        //check if the nonce is valid by rehashing and matching it with the $oldHash
        $toHash = $secret.$salt.$time;
        $reHashed = hash('sha256', $toHash);
        //match with the token
        if($reHashed !== $oldHash){
            return false;
        }
        /* Wonderful, Nonce has proven to be valid*/
        return true;
    }
}
Enter fullscreen mode Exit fullscreen mode

So I generated a nonce and passed it as a string to the method and it worked perfectly!

To verify your nonces, note that If true is returned, it means that the nonce is still valid, but If false is returned, it means that the nonce is invalid.

So our function works perfectly! In order to use this nonce, you need to place the class into a file and include that file into your page using the require() keyword.

Then create a new instance of the class, and generate a nonce

//include nonce 
require_once('nonce.php');
//create new instance of the class
$nonce = new Nonce();
//generate nonce
$myToken = $nonce->generateNonce(25, 'form_login', 10);
//verify nonce
$result = $nonce->verifyNonce($myToken)
//display result
var_dump($result);
Enter fullscreen mode Exit fullscreen mode

So there you have it

nonce


AMAZING ISN'T IT?

spongeBob

Make it suit your project by using a very strong secret.

You have reached the end of my article.

EXTRA

I recently launched a JavaScript package that validates your HTML forms using validation rules, regular expressions, and form input attributes.

I will appreciate it if you spared a few seconds to check it out.

octaValidate - A client-side form validation library | Product Hunt

This Tiny Script helps to validate your HTML forms using validation rules, sophisticated regular expressions and form input attributes.

favicon producthunt.com

Thank You

Discussion (0)