Created
June 28, 2011 14:26
-
-
Save pjlsergeant/1051238 to your computer and use it in GitHub Desktop.
Upgrading passwords in place
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// How to upgrade your users passwords in the DB without their intervention | |
// | |
// SHA-1 isn't strong enough to hash passwords with, but lots of people have a | |
// whole bunch of SHA-1'd passwords because they thought it was. You could use | |
// bcrypt or scrypt, but maybe in two years' time someone will tell you that's | |
// also not strong enough, and you'll want to upgrade. | |
// | |
// This sample demonstrates how you can remove weak password hashes from your | |
// user database, without needing the user to enter their password. | |
// | |
// NOTE: the hashing mechanisms I use here are ridiculous, and you should be | |
// fired for laziness if you use my password schema packing format. They are, | |
// however, exceptionally easy to follow. SO: | |
// First, some slightly implausible hashing mechanisms, which have the advantage | |
// of being very human readable... | |
var algorithms = { | |
// This was our initial hashing mechanism. We have hundreds of user | |
// passwords in our DB hashed using this. | |
'capitals': function ( input, salt ) { | |
return input.replace( /[A-Z]/g, salt ); | |
}, | |
// After a month, someone tells us about this one, which we deem much more | |
// secure. | |
'vowels': function ( input, salt ) { | |
return input.replace( /[aeoiu]/ig, salt ); | |
}, | |
// After another month, someone tells us about a new-fangled number hasher! | |
'numbers': function ( input, salt ) { | |
return input.replace( /[0-9]/g, salt ); | |
} | |
}; | |
// We will be looking at migrating users from 'capitals' to 'vowels', and then | |
// from 'vowels' to 'numbers', without their intervention. If they /do/ login, | |
// we'll just do a proper job of updating their password. | |
// Simple crypt | |
function crypt ( algorithm, input ) { | |
var salt = "ABCDEFGHIJ".charAt(Math.floor(Math.random() * 10 )); | |
var hashed = algorithms[algorithm]( input, salt ); | |
return [hashed, salt]; | |
} | |
// We store our passwords in the following format, again, optimized for human | |
// readibility, and making incorrect assumptions about the allowed characters in | |
// a password. We list the most recent hashing method and its salt on one side | |
// of a colon, and the hashed password on the right-hand side. We separate | |
// hashing functions with a semi-colon. The password 'Password1', as found in | |
// our original password file, hashed with 'capitals' with salt 'A': | |
// | |
// capitals;A:Aassword1 | |
// | |
// When we want to upgrade this hash to use 'vowels', with salt 'B', we'll store | |
// it as: | |
// | |
// vowels;B:capitals;A:BBsswBrd1 | |
// | |
// When we choose to upgrade this a few months later to use 'numbers', with salt | |
// 'C', we get: | |
// | |
// numbers;C:vowels;B:capitals;A:BBsswBrdC | |
// | |
// When the user logs in, we get a copy of the plaintext password again, and can | |
// remove a whole bunch of this cruft: | |
// | |
// numbers;D;PasswordD | |
// | |
// Here, for completeness, are password upgrade and validation routines: | |
function validate ( hashedAndSchemed, cleartext ) { | |
var atoms = hashedAndSchemed.split(/\:/).reverse(); | |
var hashed = atoms.shift(); | |
atoms.forEach( | |
function ( step ) { | |
var stepAtoms = step.split(/;/); | |
cleartext = algorithms[stepAtoms[0]]( cleartext, stepAtoms[1] ); | |
} | |
); | |
return cleartext == hashed; | |
} | |
// So these things should all validate just fine | |
[ | |
'capitals;A:Aassword1', // Original | |
'vowels;B:capitals;A:BBsswBrd1', // Upgraded to 'vowels' | |
'numbers;C:vowels;B:capitals;A:BBsswBrdC' // Upgraded to 'numbers' | |
].forEach( function ( hashed ) { | |
if ( validate( hashed, 'Password1' ) ) { | |
print( "Validated: " + hashed ); | |
} else { | |
print( "NO VALIDATE: " + hashed ); | |
} | |
}); | |
function upgrade ( scheme, input ) { | |
// If the password is already hashed | |
if ( input.indexOf(':') > -1 ) { | |
var atoms = input.split(/\:/).reverse(); | |
var hashed = atoms.shift(); | |
// Update the hashed password | |
var crypted = crypt( scheme, hashed ); | |
var output = crypted[0]; | |
// Add the schemes back | |
atoms.push( scheme + ';' + crypted[1] ); | |
atoms.forEach( function( item ) { | |
output = item + ':' + output; | |
}); | |
return output; | |
// Create the password from scratch | |
} else { | |
var crypted = crypt( scheme, input ); | |
return scheme + ';' + crypted[1] + ':' + crypted[0]; | |
} | |
} | |
// Generate the original password | |
var partOne = upgrade('capitals', 'Password1'); | |
print("Generated hash using capitals: " + partOne); | |
if ( validate( partOne, 'Password1' ) ) print ("That validates"); | |
// Upgrade to vowels | |
var partTwo = upgrade('vowels', partOne); | |
print("Upgraded using vowels: " + partTwo); | |
if ( validate( partTwo, 'Password1' ) ) print ("That validates"); | |
// Upgrade to numbers | |
var partThree = upgrade('numbers', partTwo); | |
print("Upgraded using numbers: " + partThree); | |
if ( validate( partThree, 'Password1' ) ) print ("That validates"); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment