A Guide to Nonce

Nonce, (number used once), is a simple way of adding extra security to a web application, particularly preventing replay attacks, semantic URL attacks, and also verify the source of the request. It’s an excellent solution, found in many popular projects, but remarkably undocumented on the web. This article aims to shed some light onto the subject, including a sample implementation, hopefully making finding information on the subject easier.

At the end of the article you will find complete implementations for download in both PHP and Go.

What is a nonce?

Despite the abbreviation’s meaning, a nonce is any value that can be uniquely identified and tracked as having been used. We also want to verify that they came from us in the first place, that it was generated relatively recently, and that it was intended for the task the user is using it for.

The basic lifetime of a nonce is this:

  1. A user requests a page that can trigger actions, eg: create or deleting content.
  2. The requested page generates a nonce value and makes it part of the action, eg: a GET or POST parameter.
  3. The user requests the action, the handler uses the nonce to validate the request.
  4. On a successful nonce validation, the action is executed and the nonce is flagged as having been used.

In the rest of the article we will walk through these stages in the form of example code. We’ll be using PHP, but this is a concept that can be applied to any language. I choose PHP because its widely used. The situation is a simple delete action, a user has requested a page that allows them to delete items from a list.

Creating a nonce

Some descriptions of nonces state that their values are random, I’ll disagree with that here and say far from being random, they should be re-creatable, but appear to be random to the user. If we can recreate the nonce we can then determine that we created it, and that is an extra layer of security. At a minimum, a nonce should be made up of the following values:

  • the action name
  • a value unique to the item the action applies to (this may not be possible in creation actions)
  • a value unique to the current user
  • the current timestamp
  • a salt that is unique to your application

These values allow you to create (and recreate) a nonce value unique to the user and task in a way that can not be faked from an external source. To create the nonce, simply combine the values and apply an irriversable hash to them.

$action = "items/delete";
$itemid = $curItem->id;
$userid = $curUser->id;
$timestamp = time();1
$salt = "a-fistful-of-dollars";

$nonce = md5($action . $itemid . $userid . $timestamp . $salt);

echo "<a href="/items/delete?item=7243&nonce=$nonce&timestamp=$timestamp">delete item</a>;

Note that we are also adding the timestamp in the URL as well, we do this for two key reasons, first its the one value we can’t possibly know when the action is submitted (and we attempt to recreate it), and secondly it’ll help us in making sure the request has not expired yet.

If you’re not dealing with logged in users, a session_id is a great replacement for the userid. The salt can be anything, as long as it is unique to your application and not predictable.

1PHP’s time function returns the current time represented as the number of seconds since the Unix Epoch

Handling the request

If the user submits the request from the previous step (by clicking on the anchor tag), the application can attempt to recreate the nonce, if the recreated nonce is not the same as the one that was submitted as part of the request URL, we can assume the request is not legitimate.

Further, we will want to check the timestamp of the request, and make sure it has not expired. The expiration date is arbitrary, and will be different for each application, or even each action. Its important not to make is too short so that it becomes a barrier for ligament users/actions. Our example defaults to a 12 hour window.

$action = "items/delete";
$itemid = $_GET['item'];
$userid = $curUser->id;
$timestamp = $_GET['timestamp'];
$salt = "a-fistful-of-dollars";

$recreatedNonce = md5($action . $entryid . $userid . $timestamp . $salt);

if($recreatedNonce != $_GET['nonce']) {
    //something is wrong
}

//Check if its expired
$expiryTime = 60 * 60 * 12; //$expiryTime being a number of seconds
$expires = $timestamp + $expiryTime;
$now = time();

if($expires - $now < 0) {
    //expired
}

If we get to this point we know the user has submitted a valid nonce, and that it has not expired. But we don’t know if the nonce has been used already. Remember, a nonce is a value that can only be used once. Seeing a nonce a second time could indicate a replay attack, or simply that a legitamate user managed to click the link twice before the page redirected. Either way, we don’t want to handle the request more than once.

A good solution to tracking used nonces is in a database. A simple lookup will tell us if it has been used already.

$sql = "SELECT (COUNT(nonce) > 0) AS is_used FROM used_nonces WHERE nonce = $recreatedNonce & timestamp = $timestamp";
$result = $dbConn->query($sql);

if($result->fetch()->is_used) {
    //the nonce has already been used
}

At this point we can execute the requested action confident that the user is legitimate, and that we have given them permission to execute this action, (by giving them the nonce in the first place.)

Flagging the nonce as used

Flagging a nonce as having been used means storing it somewhere, like a database. The goal is to ensure that no nonce is ever used more than once. This can be accomplished with a simple database insert like so:

$sql = "INSERT INTO used_nonces VALUES($recreatedNonce, $timestamp)";
$dbConn->query($sql);

How long to we need to track this value? For-ev-er. Obviously that’s a big commitment, on an active site it could add up to a lot of space in the database. But we can mitigate the need to actually keep them forever.

Our nonce is really made up of two values, the nonce hash and a corresponding timestamp. Because our nonce values expire, we can safely remove any value in our database who’s tiemstamp is less than then the current date minus the expiry time. In simple terms, if its so old that its expired already anyways, there is no need to keep a record of it. So we can just periodically clean up the database of old values.

Conclusion

While this simple mechanism adds an extra layer of security, it is no replacement for proper user validation and managing user access to restricted areas through permissions. The combination of these with a nonce implementation together greatly increases application security, both from malicious attacks and accidental human error.

Grab a complete example implementation here (PHP) or here (Go) Feel free to use it in any way, no restrictions.

References

A few of the references I found on the subject. If you find a good one please post in the comments!

Wikipedia: Cryptographic nonce Sam Ruby

Tagged:

web-developer, PHP, code, programming, application design, nonce, web-security, open source, How to, Cryptographic, Go (golang)

Comments

Comment functionality has been disabled. Contact me on Twitter.

colevscode said:

Thanks for this writeup! What’s the advantage of creating a reproducible nonce rather than storing a random number key mapped to a tuple of values, such as random_key->(action, item-id, user-id, timestamp …). Then when the request comes in, verify that the key was previously generated and is valid, and then check it off as used. Other than saving database lookups, does the nonce provide additional security?

kc said:

Why bother keeping the nonce in the database after it has been used? Just delete it immediately. A nonce that is not in the database is just as invalid as a stored nonce marked as expired.

If you are keeping expired nonces because you are worried about possible hash collisions, then you would also need to check that your generated nonce/timestamp is not already in the database before sending it to the client. But realistically, the chance of a hash collision AND a timestamp being equal is close to zero.

Tyler Egeto said:

@colevscode You’ve touched on the reason, its reproducible simply so I don’t have to store it when generating/verifying.

Tyler Egeto said:

@kc We store it to prevent replay attacks, making sure a nonce can’t be used multiple times. I’m not storing it on generation, only on use. This is because we typically generate significantly more nonce values then are used, a single page can easily generate 50 unique nonces on every load. Its more efficient to store on use rather than generation.

Ingmar Boddington said:

Thanks for the guide / your archive for PHP example code seems to be broken, can you not put this on github? :P

Recent & Popular Articles

Browse All >