sodium_crypto_auth_verify vs hash_equals for Token Authentication - php

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

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.

Related

Securely store user data in DB with Symfony

I want to store data from users so that they become useless even if the database gets leaked somehow. I also don't want to be able to encrypt the data, so I encrypt all my data via `openssl_encrypt' like this:
$passCode = base64_encode($this->get('session')->get('_pk'));
if (strlen($passCode) <= 16) {
$iv = str_pad($passCode, 16, '0');
} else {
$iv = substr($passCode, 0, 16);
}
$ciphertext = openssl_encrypt('whatevervalue', 'AES-256-CBC', $passCode, 0, $iv);
$test = new Test();
$test->setValue($ciphertext);
...
$em->persist($test);
$em->flush();
...
The $passCode is actually their password, which I put into the session var like this:
SecurityListener.php
<?php
namespace AppBundle\Listener;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
class SecurityListener
{
public function __construct($security, Session $session)
{
$this->security = $security;
$this->session = $session;
}
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$this->session->set('_pk', base64_encode($event->getRequest()->get('_password')));
}
}
2 Problems:
Storing the $passCode (whitout knowing actually that much about sessions) seems to be a security issue possibly?
What happens if a user changes the password. With the current way I'd need to decrypt and re-encrypt all his DB data with the new password, so that does not seem like a proper solution. What if he looses his password?
Maybe it is easier to understand what I want to here:
I want to encrypt all data in my database that the user himself enters there. I want it to be a "feature" that even the admin(s) cannot read the data without the key. I know the latter is not 100% possible (as there will be ways to intercept passwords/keys if entered through a web interface, but at least that invloves some change of code). Is something like that even possible at all? Any open source porjects I can have a look at?
Thank you!
Have some invariants for each user that will be used to generate the private key: user ID, account creation date, some random generated salt...
Write a hashing function that will generate the private key.
Obfuscate the code of the function.
This is not perfect, since people with access to the code and the database will be able to decipher the data, but it should be safe against database leaks and it doesn't have the problems you mention.
The only safer solution I can think of is to have users generate their own private keys and encrypt the data on client side, then send the encrypted data to the server. Otherwise, if the server has the tools necessary to decrypt, there will always be the way for someone with complete access to the system to decrypt the sensitive data.
I'm no expert in this matter and please tell me if I'm wrong.

Laravel - How do you use Hash::needsRehash()?

I'm wondering how to use Hash::needsRehash() as I'm struggling to see using the documentation exactly what it's for.
if (Hash::needsRehash($hashed)) {
$hashed = Hash::make('plain-text');
}
What exactly causes Hash::needsRehash() to return true or false, does it return true if the hashed password is in another hash (such as MD5, SHA1 etc)?
In the case that your database is full of hashes in another algorithm and Hash::needsRehash() returns true, how would you rehash the users password so that it's they're up to date? You can't rely on the "login" password because it needs to be compared first to validate, right?
I guess maybe I'm overthinking things but I'm confused right now. Luckily my users passwords are using password_hash() anyway so shouldn't be a problem.
Hash::needsReHash() just calls php's built-in password_needs_rehash function. A helpful comment in the docs is:
// Check if a newer hashing algorithm is available
// or the cost has changed
if (password_needs_rehash($hash, PASSWORD_DEFAULT, $options)) {
So Hash::needsReHash() will return false if and only if hashing algorithm has changed (since you're not passing any options such as cost).
As for how and when to use this, you can only rehash a user's password when you have it -- e.g. when they're logging in. So during the login process, you check if their stored password's algorithm differs from your current algorithm, and if so, you replace their stored password hash with a new one.
This seems to be how to do it in Laravel 5.6
Put this in your LoginController:
protected function authenticated(Request $request, $user) {
if (Hash::needsRehash($user->password)) {
$user->password = Hash::make($request->password);
$user->save();
}
}
https://laravel.com/docs/5.6/hashing#basic-usage
The method returns true when PHP is updated and a new/better default algorithm was added or any other parameters changed. This lets you automatically take advantage of it without updating your code.
This method is used when a user is logging in as that is the only time you have access to the plain-text password. After confirming it is correct according to the old hash, you take the plain text password, rehash it, and put it back into the database for future use.
For a hypothetical example, lets say that right now the algorithm is md5() 10k times. In PHP7, it was updated to sha512() 15k times. If the hash is in the $count|$algo|$hash format, the method can tell when a hash is outdated. Since the old algorithm was not removed, you can still validate the password with old parameters before rehashing.
Note: obviously using md5()/sha512() is a bad idea. I'm just using them as examples.

How can I test if I have all the right credentials with php-opencloud sdk?

Here is my code for now:
$cloud = new Rackspace('https://identity.api.rackspacecloud.com/v2.0/', $php_cloudconfig['credentials']);
$array_creds = getCredentials();
$cloud->ImportCredentials($array_creds);
$array_creds = $cloud->ExportCredentials();
setCredentials($array_creds['authorization_token'], $array_creds['expiration'], $array_creds['tenant_id'], $array_creds['service_catalog']);
function getCredentials() {
$sql_get_credential = "SELECT * FROM cloud_apiconnection";
$q = $conn->prepare($sql_get_credential);
return $q->execute();
}
function setCredentials($authorization_token, $expiration, $tenant_id, $service_catalog) {
$sql_insert = "INSERT INTO cloud_apiconnection (authorization_token, expiration, tenant_id, service_catalog) VALUES (:authorization_token, :expiration, :tenant_id, :service_catalog)";
$q = $conn->prepare($sql_insert);
$q->execute(array(':authorization_token' => $authorization_token, ':expiration' => $expiration, ':tenant_id' => $tenant_id, ':service_catalog' => $service_catalog));
}
Is there a way to detect if the credentials were updated in: $cloud->ImportCredentials($array_creds); ?
I am wandering because I don't want to write to the DB if I don't need to.
Also is this the best strategy for managing my connection to RackSpace API?
It seems like a good strategy for maintaining a persistent session, because you're re-using an existing token ID. The only other suggestion is to cache your credentials in a local file, rather than making an MySQL transaction. You don't really need to store your tenant ID and service catalog because these are easily retrievable through the software layer.
To check the validity an existing token, I'd do this:
$connection = new Rackspace(...);
// Import existing credentials
$credentials = getCredentials();
$connection->importCredentials($credentials);
// Authenticate against the API to make sure your token is still valid
$connection->authenticate();
// Compare result
$newCredentials = $connection->exportCredentials();
if ($newCredentials['authorization_token'] != $credentials['authorization_token']) {
// You know it's been updated, so save new ones
setCredentials();
}
All the authenticate() method does is execute a request against the Rackspace API; and based on the results, it's effectively letting you know whether your existing stuff is still valid. When other methods call authenticate(), they usually do another check beforehand: they check the expiry value (i.e. not in the past). You could implement the same thing they do (to find out whether credentials need to be refreshed and saved):
if (time() > ($credentials['expiration'] - RAXSDK_FUDGE)) {
// They're old, you need to call authenticate()
} else {
// They seem to be still valid. Sweet!
}
By the way, we've recently changed this functionality so that exportCredentials() makes a call to authenticate() - so this would mean you wouldn't need to call it yourself. But for your current version it's worth leaving it in.
Does that answer everything?

How can I load a function just once and store the return in a variable?

I'm currently going over my user registration code. The part I'm focusing on right now is the password hashing part.
What I do is get a static_salt from a config file, and use mt_rand() to generate a dynamic_salt. What I want to do is have this dynamic_salt stored in my database.
But if I pass the dynamic_salt() method to the create method in order to send it to the salt column of a table in my database it will just run the method again and create a different result from the one produced in my hashed() method.
What would be the best way to achieve what I'm trying to achieve, could you show me an example if possible?
public function create() {
$dbcolumn->password = $this->hashed();
$dbcolumn->salt = $this->dynamic_salt;
$this->db->insert('users', $dbcolumn);
}
public function dynamic_salt() {
$get_dynamic_salt = mt_rand();
return $get_dynamic_salt;
}
public function hashed() { //hashing method, that also makes
// sha1 and salt password
$static_salt = $this->config->item('encryption_key'); //grab static salt from config file
$dynamic_salt = $this->dynamic_salt();
$password = $this->encrypt->sha1($this->input->post('password')); //encrypt user password
$hashed = sha1($dynamic_salt . $password . $static_salt);
return $hashed;
}
I recommend that you don't use a dynamic salt, as it will reduce your applications flexibility and is likely not to work in it's current form.
The purpose of a salt is to prevent against dictionary attacks that someone could do if they obtained your user database. In that regard, a static salt is definitely a good idea to implement in your application.
Adding a dynamic salt to each user would mean that you will have to hit a datastore to retrieve the dynamic salt and the hashed version of the user's password, then you will have to perform the CPU intensive hash function (in your code, twice -- you are hashing a hash which is less secure and more likely to have collisions).
Having a simple known, static salt, and a hashed password will allow you to use key/value storage systems like memcache should you application grow. Store the userid as the key and the users hashed password as the value and you will have a lightening fast authentication system.
Try this:
public function dynamic_salt() {
if(!isset($this->dyn_salt))
$this->dyn_salt = mt_rand();
return $this->dyn_salt;
}
If the dyn_salt instance variable doesn't already exist, it will assign it the result of mt_rand(), else it will just return the previous value.

Zend_Auth setCredentialTreatment

I'm using Zend_Auth with setCredentialTreatment to set the hash method and salt. I see all examples doing something like this, where the salt seems to be inserted as a text.
->setCredentialTreatment('SHA1(CONCAT(?,salt))'
but my salt is stored in the database. I could retrieve it first then use it in setCredentialTreatment but is there a way I could define it directly as a field name, so setCredentialTreatment would know to get it from that field? sort of like the way we define the field name for the username or password
->setCredentialColumn('password')
A side issue I'm having is that I'd like to use SHA512 not SHA1. Is this possible or is it not available? All the examples I see using SHA1.
I should say I'm fairly new to zend and am porting an existing application, so please go easy on me with the answers.
The example you've given does use the salt as stored in the database. It will work as long as the salt is stored in each row in a field called 'salt'. If the salt was not in the DB and in a PHP variable instead, the code would be something more like:
->setCredentialTreatment("SHA1(CONCAT(?, '$salt'))")
As for using SHA512, this might be a little trickier. Assuming you're using MySQL, SHA1() in this case is a MySQL function, and MySQL does not have a function for SHA512 as far as I can tell, and neither does PHP (edit: I was wrong about the latter, see comments). So you'll have to implement your own PHP SHA512 function, load the salt for the user out of the DB first, hash the result and not do anything to the variable in setCredentialTreatment.
As the other answer suggested you might want to write your own Zend_Auth_Adapter for this. An auth adapter is a class that handles authentication, presumably at the moment you're using Zend_Auth_Adapter_DbTable. You can find some more info about auth adapters in the manual: http://framework.zend.com/manual/en/zend.auth.introduction.html
Here's an example:
class My_Auth_Adapter extends Zend_Auth_Adapter_DbTable
{
public function authenticate()
{
// load salt for the given identity
$salt = $this->_zendDb->fetchOne("SELECT salt FROM {$this->_tableName} WHERE {$this->_identityColumn} = ?", $this->_identity);
if (!$salt) {
// return 'identity not found' error
return new Zend_Auth_Result(Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND, $this->_identity);
}
// create the hash using the password and salt
$hash = ''; // SET THE PASSWORD HASH HERE USING $this->_credential and $salt
// replace credential with new hash
$this->_credential = $hash;
// Zend_Auth_Adapter_DbTable can do the rest now
return parent::authenticate();
}
}
You can write your own Zend_Auth_Adapter.

Categories