public
Created

password-migrate proof-of-concept

  • Download Gist
example.php
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
<?php
 
function runMigration() {
foreach (getUsers() as $user) {
$hash = $user->hash;
list ($oldhash, $oldsalt) = explode(':', $hash, 2);
$newHash = password_migrate_create($oldhash, $oldsalt, PASSWORD_BCRYPT);
$user->hash = $newHash;
saveUser($user);
}
}
 
function login($username, $password) {
$user = fetchUser($username);
$legacyAlgo = function($password, $hash, $oldsalt) {
return md5($password . $oldsalt);
};
$test = password_migrate_verify($password, $user->hash, $legacyAlgo, PASSWORD_BCRYPT);
if ($test) {
if (is_string($test)) {
$user->hash = $test;
saveUser($user);
}
// Login is successful
} else {
// Incorrect password
}
}
 
function register($username, $password) {
$user = createUser($username);
$user->hash = password_hash($password, PASSWORD_BCRYPT;
saveUser($user);
}
pasword_migrate.php
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
<?php
 
/**
* Verify old and new hashes
*
* @param string $password The password to verify
* @param string $hash The hash to verify against
* @param callable $legacyAlgo A callback expecting ($password, $hash) to test old legacy hashes
* @param int $newAlgo The new aglorithm to use with password_hash
* @param array $newOptions The new options to use with password_hash
*
* @return bool|string A boolean result if not valid, true if valid and new algo. Returns an updated hash if needed.
*/
function password_migrate_verify($password, $hash, $legacyAlgo, $newAlgo, array $newOptions = array()) {
$needsRehash = false;
$origPassword = $password;
if (substr($hash, 0, 8) === '$legacy$') {
$hash = substr($hash, 8);
list ($options, $hash) = explode('$', $hash, 2);
$password = $legacyAlgo($password, unserialize(base64_decode($options)));
$needsRehash = true;
}
if (password_verify($password, $hash)) {
if ($needsRehash || password_needs_rehash($hash, $newAlgo, $newOptions)) {
return password_hash($origPassword, $newAlgo, $newOptions);
}
return true;
}
return false;
}
 
/**
* "Wrap" legacy hashes in a new style hash (to protect them further)
*
* @param string $legacyHash The legacy hash to wrap
* @param mixed $legacyOptions Options that need to be preserved (passed to the legacyAlgo callback)
* @param int $newAlgo The new algorithm to use with password_hash
* @param array $newOptions The new options to use with password_hash
*
* @return string A new hashed value to store.
*/
function password_migrate_create($legacyHash, $legacyOptions, $newAlgo, array $newOptions = array()) {
if (substr($legacyHash, 0, 8) === '$legacy$') {
throw new RuntimeException('Attempting to migrate already migrated password!');
}
$newHash = password_hash($legacyHash, $newAlgo, $newOptions);
return '$legacy$' . base64_encode(serialize($legacyOptions)) . '$' . $newHash;
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.