How To Create A Simple Nonce in PHP

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.
     * 
     */
}

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

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

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

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

image.png

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

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

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

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

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

image.png

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

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

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

So there you have it

image.png

AMAZING ISN'T IT?

spongebob-resized.png

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

You have reached the end of my updated article.

EXTRA

I am currently test-running a SAAS app that I built to help collect and manage customer reviews and ratings for your website or business. Do you mind checking it out? I'm looking for partners to work with, on this big project.

Your feedback will be highly appreciated.

Check out Givemeastar(GMAS)

Thank You