Search code examples
phpauthenticationtokenlibsodium

sodium_crypto_auth_verify vs hash_equals for Token Authentication


I am building my own small framework for my final year project at university and I am very confused about best practices here. I have read quiet a lot of articles and have a general idea but some clarification would be better.

I really like the new sodium extension in PHP But I am a bit confused.

I am creating a split token authentication thing both for long term persistent cookies and password resets.

I am using Libsodium as much as possible as it seems very secure, all be it new and not very well documented.

I am creating a split token like selector:validator. What I want to do is basically use the selector to query the DB,

Then I want to either compare the two hashes VS Hash the plain text version from the cookie and then compare (But the latter means keeping a key somewhere which creates an issue)

I have heard across many articles especially with Paragone suggest that it is preferable to store the hashed version of a token in the Database and the plain version in the cookie or token. Is there any real benefit of this?

I have created a simple Token class:

class SplitToken extends Token
{

protected $selector;

protected $validator;

function __construct($selector=14, $validator=18)
{
    $this->selector = bin2hex(random_bytes($selector));
    $this->validator = bin2hex(random_bytes($validator));
    $this->key = random_bytes(SODIUM_CRYPTO_AUTH_KEYBYTES);
    $this->tokenHash = sodium_crypto_auth($this->validator, $this->key);
}

public function Set()
{
    $this->token = $this->selector.':'.$this->validator;
    return $this;
}

public function Get()
{
    return $this->token;
}

$sptoken = new SplitToken();
$token = $sptoken->set()->Get();
$dt = new DateTime('+ 2 months');
$expiry = $dt->getTimestamp();
//Gets bin2hex version of validator side of token for DB
$validatorHash = $token->GetValidatorHashHex();
$key = $token->GetKey();

//Store token In DB:
$query = "UPDATE Users SET Selector, Validator, Expiry 
WHERE Selector = :Selector and    Validator = :Validator and Expiry = :Expiry;
$stmt = $pdo->prepare($query);
$stmt->execute(
['Selector' => $token->GetSelector(), 
'Validator' => $validatorHash, 
'Expiry' => $expiry]);

Now that the temp token is in the DB. Now it is time to set the cookie (The same can apply to PW Reset with a different expiry of course. Here I am getting confused and I have two options:

Option 1:

//Store the hexed version of selector:validator in the cookie (but not hashed)
setcookie('auth_token', $token, $expiry, '/', 'CONST_DOMAIN', true, true);

//Where do I store the key?
//So far I am using JSON fuNCTION which gets key from the folder where it is stored:
$storedKey = Key::GetFromVault('auth_token');

if(isset($_COOKIE['auth_token')){
 $cookie = explode(':', $_COOKIE['auth_token'];

//Gets the User from the DB Where Selector = Selector
 $user = DB::SelectUser($user);
//If User exists
 if($user){
 //*** Checks the Hash separate from the query to avoid timing attack ***
 var_dump(sodium_crypto_auth_verify($user->Validator, $cookie[1], $storedKey);
 }

}

Option 2: Seems a bit simpler and cleaner because I am simply comparing two hashes and there is no need to worry about the key later but it means that I have to store the hashed version of the validator in the cookie:

$hashedToken = $token->GetSelector.':'.$token->GetValidatorHashHex();
setcookie('auth_token', $hashedToken, $expiry, '/', 'CONST_DOMAIN', true, true);

//Now On Request:
if(isset($_COOKIE['auth_token')){
 $cookie = explode(':', $_COOKIE['auth_token'];
 $user = DB::SelectUser($user);
 if($user){
 //Checks the Hash separate from the query to avoid timing attack
 var_dump(hash_equals($user->Validator, $cookie[1]);
 }

}

I know this probably sounds silly and it does not make much difference, But In option 1 I am comparing a plain text token to a hashed version and comparing it with the sodium function And with Option 2 I am comparing two hashes

If I compare the same two hashes with the sodium function it returns false and if I compare the plain text to the hashed with hash_equals even though the token before the hash is the same it returns false.

So basically:

1) Does this make much of a difference? 2) I would like to find a neat solution for Key storage and then store plain in the cookie but not sure how

Any advice would be greatly appreciated. Happy to clarify my question Thanks


Solution

  • It doesn't make any difference.

    What sodium_crypto_auth_verify() does is compute the hash, and compare it with the provided one.

    As long as the comparison is in constant time, there is no difference between this and doing it yourself.