How To Create A Simple Nonce in PHP
Simon Ugorji
Posted on June 2, 2022
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.
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.
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
AMAZING ISN'T IT?
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.
Thank You
Posted on June 2, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 28, 2024
November 28, 2024