Skip to content

Instantly share code, notes, and snippets.

@pjlsergeant
Created June 28, 2011 14:26
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save pjlsergeant/1051238 to your computer and use it in GitHub Desktop.
Save pjlsergeant/1051238 to your computer and use it in GitHub Desktop.
Upgrading passwords in place
// 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