A Little Bit Of Nonce Sense

I was recently reminded, while improving my WordPress plugins, of the importance of protecting forms and URLs that “perform work” from CSRF attacks. The core WordPress nonce implementation works very well in the WordPress realm (and if you want a true, single-use nonce for WordPress, check out Cal Evans’ solution), but, for my generic PHP needs, I wanted a simple class that was a bit more flexible than the others I’d found while scouring the web.

I won’t go into a detailed discussion of nonces here (that’s been done by Wikipedia and plenty of other resources around the internet). Instead, I’ll dig in to why I wrote my ONonce class the way I did.

To begin with, I defined constants with an eye toward making it a matter of simply changing one line of code if you want things configured differently:

	// Hash algorithm to use
	const HASH_ALGORITHM = 'sha512';
	
	// Length of nonce
	const NONCE_LENGTH = 12;
	
	// Position within hash to start nonce generation
	const NONCE_OFFSET = -15;	// 15 chars from end of string
	
	// Name of form input to store nonce
	const NONCE_FORM_INPUT_NAME = '_ononce';
	
	// Name of query parameter to pass nonce
	const NONCE_QUERY_PARAM_NAME = 'ononce';
	
	// Secret: Make this a looong (64? character) string of randomness
	const SECRET = 'R*Q3Q*t|?,;c;RR2g?<:PIB0J+T/7kDkSS-VQeH^N3-q:XbdCgS>G+!*l<}|)S.h';

This starts with defining the hash algorithm used and calling it via PHP’s hash() function, so that, as better algorithms are released down the road, you can change a single statement in ONonce to update ALL nonces throughout your application.

Different hash algorithms generate different length outputs so I defined both the length of the substring of the hash output to use as the nonce and the point in the hash output from which to start the substring.

Developers all have their preferences, when it comes to naming conventions, so the names of both the nonce form hidden input and the nonce query parameter are defined here as well. Additionally, I wanted to add some flexibility to accommodate applications (including those I build with Kohana) that don’t use query string parameters (you might know them as “fields” instead)1.

The secret MUST be updated (don’t use the sample string included with the code! 🙂 ) to a long string of gibberish to ensure that nonces generated are decently random. A good place to get one, if you’re not feeling particularly creative, is from WordPress’ secret key generator.

I also determined that there were a couple of settings that would be particularly helpful to define as defaults but allow the developer to override, as needed, in their method calls:

	// Default current user ID
	const DEFAULT_CURRENT_USER_ID = 123456789;
	
	// Default lifetime for nonces (in seconds)
	const DEFAULT_LIFETIME = 3600;	// 1 hour

When building a new application, I found that I had a compelling need for more than just a single, set nonce lifetime. Further, not all applications require a user be signed in to fill out forms, and, even those that do, implement this in many different ways (not always relying on Session variables, for instance).

My solution for both of these situations was to define defaults (as constants) for each in the class definition and then allow them to be optionally overridden by the developer, if/when needed, for each pair of nonce creation and validation calls. This also sets up nicely for extensibility, since, if you want to incorporate additional overrides, the $overrides array is an existing vehicle, already present in the methods’ signatures.

Speaking of nonce creation and validation calls, here are the methods to the madness:

 	/**
	 * Create nonce as a string of NONCE_LENGTH, valid for DEFAULT_LIFETIME
	 * seconds (unless overridden).
	 *
	 * @since	1.0.0
	 * @param	string		$name			Nonce name
	 * @param	string		$action			Nonce action
	 * @param	string[]	$overrides		Optional. Array containing 'current_user' and/or 'lifetime' override values.
	 * @uses	self::DEFAULT_CURRENT_USER_ID
	 * @uses	self::DEFAULT_LIFETIME
	 * @uses	self::HASH_ALGORITHM
	 * @uses	self::SECRET
	 * @uses	self::NONCE_OFFSET
	 * @uses	self::NONCE_LENGTH
	 * @return	string
	 */
    private static function create($name, $action, array $overrides = NULL)
    {		  
    	
    	if ( ! isset($overrides['current_user']))
    	{
    		
    		// No current user ID was passed - use default
    		$overrides['current_user'] = self::DEFAULT_CURRENT_USER_ID;
    	}
    	if ( ! isset($overrides['lifetime']))
    	{

    		// No lifetime was passed - use default
    		$overrides['lifetime'] = self::DEFAULT_LIFETIME;
    	}

        $deadline = ceil(time() / ($overrides['lifetime'] / 2));

        return substr(hash(self::HASH_ALGORITHM, $deadline.$name.$action.$overrides['current_user'].self::SECRET), self::NONCE_OFFSET, self::NONCE_LENGTH);
                
    }

This is the heart of the class. It checks for overrides and uses them, if present, and then creates (and returns) the nonce as a substring of the hash of all of the concatenated constituents.

 	/**
	 * Create a string containing HTML markup, for a hidden form input field,
	 * with a name of NONCE_FORM_INPUT_NAME and a value consisting of the nonce
	 * generated by self::create().
	 *
	 * @since	1.0.0
	 * @param	string		$name			Nonce name
	 * @param	string		$action			Nonce action
	 * @param	string[]	$overrides		Optional. Array containing 'current_user' and/or 'lifetime' override values.
	 * @uses	self::NONCE_FORM_INPUT_NAME
	 * @uses	self::create
	 * @return	string
	 */
    public static function create_form_input($name, $action, array $overrides = NULL)
    {               

        return '<input type="hidden" name="'.self::NONCE_FORM_INPUT_NAME.'" value="'.self::create($name, $action, $overrides).'" />';
 
    }

This creates the hidden form input field, containing the nonce generated by calling the create() method.

 	/**
	 * Create a string containing a URL fragment with a name of 
	 * NONCE_QUERY_PARAM_NAME (and, if NONCE_QUERY_PARAM_NAME is not blank, a
	 * literal "="), and a value consisting of the nonce generated by
	 * self::create().
	 *
	 * @since	1.0.0
	 * @param	string		$name			Nonce name
	 * @param	string		$action			Nonce action
	 * @param	string[]	$overrides		Optional. Array containing 'current_user' and/or 'lifetime' override values.
	 * @uses	self::NONCE_QUERY_PARAM_NAME
	 * @uses	self::create
	 * @return	string
	 */
    public static function create_url_fragment($name, $action, array $overrides = NULL)
    {               
                            
        // NOTE: Some routing schemes may not use query string parameters, instead opting to use only delimited
        //	values
        if (($url_fragment = self::NONCE_QUERY_PARAM_NAME) != '')
        {
        	$url_fragment .= '=';
        }
        $url_fragment .= self::create($name, $action, $overrides);
       
        return $url_fragment;

    }

This creates the URL fragment, consisting of (the optionally-defined nonce query parameter name and ‘=’ and) the nonce generated by calling the create() method.

 	/**
	 * Determine whether a supplied nonce is valid and return success/failure.
	 *
	 * @since	1.0.0
	 * @param	string		$name			Nonce name
	 * @param	string		$action			Nonce action
	 * @param	string		$nonce			Nonce value
	 * @param	string[]	$overrides		Optional. Array containing 'current_user' and/or 'lifetime' override values.
	 * @uses	self::create
	 * @return	boolean
	 */
    public static function is_valid($name, $action, $nonce, array $overrides = NULL)
    {               
                    
        return self::create($name, $action, $overrides) == $nonce;

    }

Finally, this method returns true or false based on a comparison of the passed nonce and the nonce generated by calling the create() method.

That’s all well and good, but how about some examples of how to actually use the class:

  1. Include the class in each file from which you need to call it:

    include 'ONonce.php';
    
  2. Let’s look at how to create and confirm a hidden form input:

    1. Create (with defaults):

      echo ONonce::create_form_input('name_value', 'action_value');
      
    2. Confirm (with defaults):

      $nonce_from_form = sanitize($_POST['_ononce']);  
      if (ONonce::is_valid('name_value', 'action_value', $nonce_from_form))  
      {  
      
          // Do something  
      
      }  
      
    3. Create (with overrides):

      echo ONonce::create_form_input('name_value', 'action_value', array('current_user' => 12, 'lifetime' => 300));
      
    4. Confirm (with overrides):

      $nonce_from_form = sanitize($_POST['_ononce']);
      if (ONonce::is_valid('name_value', 'action_value', $nonce_from_form, array('current_user' => 12, 'lifetime' => 300)))
      {
      
          // Do something
      
      }
      
  3. And how to create and confirm a URL fragment:

    1. Create (with defaults):

      echo 'https://some_url.com?query_param1=foo/&'.ONonce::create_url_fragment('name_value', 'action_value');
      
    2. Confirm (with defaults):

      $nonce_from_url = sanitize($_GET['ononce']);
      if (ONonce::is_valid('name_value', 'action_value', $nonce_from_url))
      { 
      
          // Do something  
      
      }
      
    3. Create (with overrides):

      echo 'https://some_url.com?query_param1=foo/&'.ONonce::create_url_fragment('name_value', 'action_value', array('current_user' => 12, 'lifetime' => 300));
      
    4. Confirm (with overrides):

      $nonce_from_url = sanitize($_GET['ononce']);
      if (ONonce::is_valid('name_value', 'action_value', $nonce_from_url, array('current_user' => 12, 'lifetime' => 300)))
      {
      
          // Do something  
      
      }
      

That about wraps it up for now. If you have a PHP project requiring nonces, and you give ONonce a try, I’d love to hear your feedback. Or if you just want to critique my code, without actually trying it out, I’m fine with that too! 🙂

  1. You’ll see more about this below, in the discussion of the class methods.
00