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: /');
exit;
}
//functions to create and to validate a token
function createCSRFtoken($sessionSecret, $expiration, $info='') {
$salt = bin2hex(random_bytes(32));
$key = bin2hex(hash_hkdf('sha3-512', $sessionSecret, 0, $info."\0".$expiration, $salt));
$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, $info."\0".$expiration, $salt));
if (hash_equals($key, $reHashKey) === false) { return false; }
if ($expiration < time()) { return false; }
return true;
}