Csrf token


Hello, i often notice that members are asking for help with commerce apps in the beginners category. I find it strange to be creating shopping carts but offering no protection for the customers. I want to post a csrf token snippet so that members have something to strengthen form security. Keep in mind that this code is based upon my original token with changes suggested by Yasuo Ohgaki. My original token stored the hash in the session, whereas Yasuo stores the secret, then rehashes the key for matching. Yasuo uses hash_hkdf, whereas i was using hash_hmac. I’ve added strspn and join methods used by Yasuo. Otherwise, my token was nearly exact. I thank Yasuo for making me aware of rehashing the secret key and for showing me better validation methods.

My method creates a timestamp expiration outside of the function because i wish to show this time limit to users at my login screen (similar to certain banks.) I couldn’t think of an easier method of showing this info while passing it to the function, so i use three variables instead. If you have no need to display a time limit to a user, then you can just pass the expiration to the function in seconds (e.g., 1800 is 30 minutes, then set $expiration += time() in the createCSRFtoken function.), Meantime, i use a stronger hash (sha3-512, but this can be lessened if you wish to do so but nothing less than sha-256 is secure enough for a token.)

//page where token is set
$TokenExpiration = 180; $TokenExpiration += time(); $TokenBirth = $TokenExpiration - 180;
$_SESSION['CSRFtokenSecret'] = random_bytes(32);
$CSRFtoken = createCSRFtoken($_SESSION['CSRFtokenSecret'], $TokenExpiration);

//processing page after form submission
$validToken = validateCSRFtoken($_SESSION['CSRFtokenSecret'], $_POST['tokenStamp']);
unset($_SESSION['CSRFtokenSecret']); unset($_POST['tokenStamp']);
if ($validToken === true) {
    //process form
} else {
    header('Location: /');

//functions to create and to validate a token
function createCSRFtoken($sessionSecret, $expiration) {
    $salt = bin2hex(random_bytes(32));
    $key = bin2hex(hash_hkdf('sha3-512', $sessionSecret, 0, $expiration));
    $token = join('-', [$salt, $key, $expiration]);
  return $token;
function validateCSRFtoken($sessionSecret, $postToken) {
    if (!is_string($sessionSecret) { return false; }
    if (strlen($sessionSecret) != 32) { return false; }
    if (!is_string($postToken)) { return false; }
    if  (strlen($postToken) != 204) { return false; }
    if ($postToken === '') { return false; }
    $getToken = explode('-', $postToken);
    if (count($getToken) !== 3) { return false; }
    list($salt, $key, $expiration) = $getToken;
    if (empty($salt) || empty($key) || empty($expiration)) { return false; }
    if (strlen($expiration) != strspn($expiration, '1234567890')) { return false; }
    $reHashKey = bin2hex(hash_hkdf('sha3-512', $sessionSecret, 0, $expiration));
    if (hash_equals($key, $reHashKey) === false) { return false; }
    if ($expiration < time()) { return false; }
  return true;
1 Like

update: i noticed an error in my code. i mistakenly tested the string length as !strlen() instead of strlen() !=

my apologies. i’ve updated this code.


I usually use declare (strict_types) = 1); with my php pages. i haven’t used it in my functions yet and i really haven’t examined the token method of Yasuo and other security gurus. Today, i added strict types to my functions and this token created a fatal error. I examined why and discovered that parameter 4 is supposed to be a string. time() is not a string, so the timestamp shouldn’t be used in this manner. I now think that my original token is better because it follows the rules of php hashing. I am frustrated with this error. I am updating this post because if you implement this token, then you cannot specify strict types. Furthermore, the original token of Yasuo uses assert command and the manual tells us not to do this on a live server. So i am going back to my method of csrf tokens using hash_hmac. Atleast my tokens will not throw a fatal error.