Stop Storing Passwords Already!

16 commentsWritten on January 23rd, 2012 by
Categories: security

This is largely common sense already, but I still frequently run into people who don't know how dangerous this is or how to properly store user credentials. The many Anonymous hacks in the past year that resulted in the leaking of users' passwords also show that many sites still store passwords in either clear-text or encrypted form. It's actually quite simple to store credentials safely, so here's a quick recap and example.

The biggest issue with storing passwords is that you have to assume that it's always possible that someone can get access to your database. Yes, even if it's not directly exposed to the outside world, which it never should be. Whatever security measures you've put in place to protect your database, it's a good idea to assume that sooner or later, someone will be able to punch a hole through your security measures and be able to read the data. So obviously, you really don't want to store clear-text passwords. You also don't want to store encrypted passwords because encrypted data can always be decrypted. And if people get access to those encrypted passwords even if they weren't supposed to, it'd be wise to assume that they also know how to decrypt them, or that it won't take them long to figure it out.

A much better approach is to store a hashed representation of the password instead, using a strong one-way cryptographic algorithm and a unique salt value per password. If the cryptographic algorithm is one-way, it means you can't apply another algorithm to get the original source value again. The only way to compare passwords is to apply the cryptographic algorithm on a given password using the originally used salt value, and then compare the resulting hash with the one you've stored. If they are identical, the given password is the same as the one that was used originally. If they differ, the password is invalid.

Attackers can still employ rainbow tables to try to find password values that generate the same hashes as the ones in your database. Luckily, generating rainbow tables takes time and plenty of space as well so it makes it much harder for attackers to find the passwords. This is why it's so important to use a unique salt value per password. It effectively means that a rainbow table would have to be generated for every single salt value that you've used, making it practically infeasible to find the original password values.

Let's demonstrate this with a simple example. The example is from a Node.js application, but this technique can be applied with whatever technology stack you're using.

This is my User model:

var mongoose = require('mongoose'),
    crypto = require('crypto'),
    uuid = require('node-uuid'),
    Schema = mongoose.Schema,
    ObjectId = Schema.ObjectId;

var userSchema = new Schema({
    name: { type: String, required: true, unique: true },
    email: { type: String, required: true },
    salt: { type: String, required: true, default: uuid.v1 },
    passwdHash: { type: String, required: true }
});

var hash = function(passwd, salt) {
    return crypto.createHmac('sha256', salt).update(passwd).digest('hex');
};

userSchema.methods.setPassword = function(passwordString) {
    this.passwdHash = hash(passwordString, this.salt);
};

userSchema.methods.isValidPassword = function(passwordString) {
    return this.passwdHash === hash(passwordString, this.salt);
};

mongoose.model('User', userSchema);
module.exports = mongoose.model('User');

Notice that the salt property of my User type has its default value set to 'uuid.v1'. In this case, uuid.v1 is a function which will be invoked by Mongoose whenever a new User instance is created. Every User instance will thus have a UUID value stored in its salt property. You can also see that I'm not storing the given passwordString in the setPassword function, but that I calculate the hash value based on the passwordString and the UUID salt value.

Suppose I create a user with the following code:

var user = new User({
    name: 'test_user',
    email: 'blah'
});
user.setPassword('test');

user.save(function(err, result) {
    if (err) throw err;
}); 

Its database representation will look like this:

{ 
    "passwdHash" : "b604367796274cf64177eec345532fc6ca66c6f0501906f82bb03f7916265e9d", 
    "name" : "test_user", 
    "email" : "blah", 
    "_id" : ObjectId("4f1dbb2cfa6157b118000001"), 
    "salt" : "304a33f0-45fc-11e1-80d2-43c594a44fa0" 
}

If an attacker would get access to this, he'd have to generate a rainbow table using the salt value, which takes time, and even then he has no guarantee that the rainbow table will actually contain the correct password. Again, this is why it's so important to use a unique salt for every password. Also, you can use whatever value you want as the salt value so if you can determine it based on some other fields or by using a specific formula you don't need to store the actual salt value. It's recommended to use a long salt value though. Theoretically speaking, it's safer if the salt value isn't stored so clearly as I'm doing here, but even with the salt value clearly visible to a possible attacker, it would still be practically infeasible for him to generate all those rainbow tables.

And of course, my actual authentication function is still very simple as well:

var authenticate = function(username, password, callback) {
    User.findOne({ name: username }, function(err, user) {
        if (err) return callback(new Error('User not found'));
        if (user.isValidPassword(password)) return callback(null, user);
        return callback(new Error('Invalid password'));
    });
};

So as you can see, there's nothing hard or complicated about storing credentials in a secure manner. It's quite easy to do so and there are no downsides to doing this.

  • http://twitter.com/dagda1 dagda1

    I favour bcrypt over any of the SHA algorithms.  Bcrypt has less chance of  being hacked.

  • Hwiechers

    It looks like you’re suggesting a plain old unique salt and hash.

    If so, this is bad advice. Really bad advice.
    These days, plain old hashes can be calculated so quickly that any password you store
    can be brute forced without too much trouble.

    You need to use a slow hash function like bcrypt, scrypt or pbkdf2 with suitable parameters.

    Here is some more info:
    http://blog.zorinaq.com/?e=8

    • http://davybrion.com Davy Brion

      bcrypt, scrypt or pbkdf2 are better than SHA-2, but the tool mentioned in the post you link to only generates passwords for SHA1, MD5 & MD4 hashes. My example uses sha256 which is a SHA-2 hashing algorithm, and should still provide enough safety.

      • Hwiechers

        With regards to SHA-256 – http://www.google.co.za/search?q=gpgpu+sha256

        But that is missing the point.

        The principal is that you shouldn’t use a general purpose hash for storing passwords.
        You need one specifically designed to be slow.

        Look at this:
        http://security.stackexchange.com/q/211

        If you’re still not convinced after this, let’s just agree to disagree.

        • http://davybrion.com Davy Brion

          i never said i disagreed :)

          i didn’t know that SHA-2 was also so susceptible to brute-force cracking through… i’m a bit surprised by it considering that it’s still often recommended.

          • http://davybrion.com Davy Brion

            Just talked to one of the security guys we have here about this approach and he says that with SHA256, it would still take *a lot* of effort to brute force your way to the actual password, especially if you’re disallowing short passwords. 

            He’s gonna ask around with some other security guys whether they’ve ever heard of it being as trivial as your links claim it to be. I’ll let you know when I get more answers :)

          • http://davybrion.com Davy Brion

            http://security.stackexchange.com/questions/4687/are-salted-sha-256-512-hashes-still-safe-if-the-hashes-and-their-salts-are-expos
            “With a $150 graphics card, you can perform 680 million SHA-1 hash computations per second. If you use only one round of hashing, all 6-character passwords can be tested in a little over 15 minutes (that’s assuming all 94 printable ASCII characters are used). Each additional character multiplies the time by 94, so 7 characters requires one day, 8 characters requires 103 days on this setup. Remember, this scenario is a 14-year-old using his GPU, not an organized criminal with real money.
            Now consider the effect of performing multiple iterations. If 1,000 iterations of hashing are performed, the 6-character password space takes almost 12 days instead of 15 minutes. A 7-character space takes 3 years. If 20,000 iterations are used, those numbers go up to 8 months and 60 years, respectively. At this point, even short passwords cannot be exhaustively searched; the attacker has to fall back to a dictionary of “most likely” passwords.”

  • Jason

    Davy, any similar suggestions for someone using as ASP.NET membership provider?

  • Filip Duyck

    “Again, this is why it’s so important to use a unique hash for every password.”
    I think you meant to say unique salt here :-)

    • http://davybrion.com Davy Brion

      oops, fixed :)

  • Anonymous

    Nitpick, but I guess an attacker would not generate a rainbow table unless he’s attacking the same salt more than once, which is unlikely. Computationally generating a rainbow table >= attacking the hash straight up. 

  • http://jcallico.myopenid.com/ Javier Callico

    In the past instead of using a dedicated salt property I’ve used something unique to the user like the email for example. Do you see any disadvantage with this approach? 

  • http://jcallico.myopenid.com/ Javier Callico

    Never mind, the last paragraph answers my question.

  • http://jfromaniello.blogspot.com José Romaniello

    hi Davy, i know other have said that bcrypt is better. Here is a module that i am using in node.js for my experiments:
    https://github.com/ncb000gt/node.bcrypt.js/ 

    • http://davybrion.com Davy Brion

      ah nice, thx :)