Hash Authentication
After coming across this article on Reddit today, and chatting with a good friend and colleague about the benefits and issues of using the authentication method outlined by the author, we both came to the conclusion that we were not fans of the way the author handled the hash table comparisons for users, so I decided to work on it a little bit at home and try to make it better.
Before I go into details, here are the database tables I created to get this authentication to work:
mysql> DESCRIBE hashes; +_______+_____________+______+_____+_________+_______+ | FIELD | TYPE | NULL | KEY | DEFAULT | Extra | +_______+_____________+______+_____+_________+_______+ | hash | VARCHAR(64) | NO | PRI | NULL | | | salt | VARCHAR(64) | NO | | NULL | | +_______+_____________+______+_____+_________+_______+ 2 ROWS IN SET (0.01 sec) mysql> DESCRIBE users; +__________+_____________+______+_____+_________+________________+ | FIELD | TYPE | NULL | KEY | DEFAULT | Extra | +__________+_____________+______+_____+_________+________________+ | uid | INT(11) | NO | PRI | NULL | AUTO_INCREMENT | | username | VARCHAR(30) | NO | | NULL | | | hash | VARCHAR(64) | NO | | NULL | | | salt | VARCHAR(64) | NO | | NULL | | +__________+_____________+______+_____+_________+________________+ 4 ROWS IN SET (0.00 sec) |
I set the hash field in the hashes table to an indexed column to allow quick(er) searching through the table, trying to alleviate the issues we might run into if this table becomes too large. Without the user’s password, there is no connection between these two tables, as it is required to generate the data within the hash column in the hashes table. Creating a user would happen like so:
// note, this uses my database class, which can be found at http://code.imyourdeveloper.com/database.txt function create($applicationSalt, $user) { //generate salts $userSalt = hash("sha256", $applicationSalt.rand().time()); $hashesSalt = hash("sha256", $applicationSalt.rand().time().$applicationSalt); //generate hashes $userHash = hash("sha256", $applicationSalt.$user->password.$hashSalt); $hashesHash = hash("sha256", $applicationSalt.$user->password.$userSalt); //create queries $user = 'insert into users (uid, username, hash, salt) values (null, "'.$user->username.'", "'.$userHash.'", "'.$userSalt.'")'; $hash = 'insert into hashes (hash, salt) values ("'.$hashesHash.'", "'.$hashesSalt.'")'; //run queries $db->query($user); $db->query($hash); } |
This was a little bit of a pain, and took some staring at my monitor to wrap my head around, trying to remember what went where, but I eventually got it working. The application salt is your secret salt set in the application that you will use to salt data going to the database, and user passes through any data you want to write about the user to the database. A combination of the user salt, the application hash, and the user’s password is used to generate the user hash and the authentication lookup hash in the hashes table. When the user types in a password, it runs through the algorithm creating a hash, looks up that hash in the hashes table, recalculates another hash using a randomly generated salt in the hashes table, the user’s password, and the application salt, then compares with the hash in the Users table. If this matches, then the user is who he says he is, and is logged in. This happens like so:
function lookup($applicationSalt, $user) { GLOBAL $db; $userLookup = $db->getObj('select * from Users where username = "'.$user->username.'"'); $check = $applicationHash.$user->password.$userLookup->salt; $hash = hash("sha256", $check); $hashSalt = $db->getObj("select * from Hashes where hash = '$hash'")->salt; if ($userLookup->hash == hash("sha256", $applicationSalt.$user->password.$hashSalt)) { return true; // logged in } else { return false; // login failed } } |
This may not be a perfect implementation of this code, but I wasn’t a fan of the way it was being handled in the other article; and this was a neat little problem to work on, so expect updates in the future.. The mathematical probability of you being able to brute force the user’s password was astronomically low before, but it was nothing more than a one-way relation. This way, you not only need to brute force the users password to go one way (towards the hashes table), but the password you get is likely not going to work, as another hash is checked going in reverse, ensuring the only string that is accepted is the user’s.
If you have any better ideas of how to implement this, please feel free to post it below. Click here for a full version of the code above.