July 11, 2013

3 Wrong Ways to Store a Password

And 5 code samples doing it right

Sooner or later, in the field of Web Development, you’re going to need to store someone’s password. This is easy to get wrong, but easy to get right, too. Today, let’s take a tour of the wrong ways, and then find out how to do it the (current) right way.

Plaintext

Alice just learned PHP, and are making an app with user authentication. Her database stores an email and password in a table called users. When a user logs in, she checks for the existance of that username and password. Easy peasy.

What’s wrong with it?

Never store a password in plain text

If an attacker – say, a disgruntled employee at her hosting company – ever got a hold of Alice’s database, all her users’ passwords are immediately compromised. Care to guess how many of those users use that password for their email account, the username for which is stored in the database right there next to it?

Hash(password)

Where Hash is md5, or sha1, or sha256, or something like that. Bob has been developing for a bit, and knows better than to store a plaintext password. Instead, he stores a hash of the password. There! There’s no way to get it back. Then, to log in, Bob hashes the users’ password as it comes in and compares that to the hash in the database.

What’s wrong with it?

Dictionary Attacks

Have you ever heard of a hash map? It uses hashes as keys to store values, and is very efficient at lookups – with no collisions, entries can be looked up in constant time (O(1)).

This is bad news for Bob, because an attacker can just download (or produce) a hash-map that maps hashes to common passwords. Some of these dictionaries are very extensive, and most of Bob’s users are probably using a password that’s in one of them. (In fact, Bob’s users – and yours – probably use passwords like “password”.) Each entry can be cracked almost immediately. To anyone remotely sophisticated, this sort of protection is scarcely better than nothing. Even a user with a long and/or complicated password is susceptable to cracking via a Rainbow Table under this scheme.

Hash(password + salt) or HMAC+Hash(password, salt)

Carla knows about hash maps, but she has a way to thwart that. Her password strings are combined with a secret, randomly-generated salt. The salt is stored along with the password hash and email address. Let’s see that dictionary attack work on this!

What’s wrong with it?

Fast Hashing Algorithms are bad

MD5, SHA1 and so forth are designed to be very fast. This is a plus to an attacker who decides to brute force a given password – md5 and sha1 hashes can be produced at a rate of millions, or even billions per second on a consumer GPU these days.

To put that into perspective, there are about 56 billion passwords containing 6 alphanumeric characters. That’s 56 seconds to brute-force a 6-character password. And it gets worse.

Salting your password may foil dictionary attacks, but an attacker can still use a wordlist to inform their guesses. And a clever attacker, or an attacker with a clever tool, will know to add random numbers or letter to the end of words in the way real users do.

So finally, what is the right way?

Use a Password Hashing Algorithm

There are algorithms designed specifically for this purpose. The two in common use are pbkdf2 and bcrypt. Both of these algorithms use a technique called Key Stretching, which essentially involves repeatedly running a one-way function on the input. They are designed to be expensive to compute, protecting you from brute-force and wordlist attacks. Additionally, both algorithms come with an adjustable work factor, and so can be made harder as computers get faster.

Also, nobody would let me forget about scrypt, so I’m going to mention it now.

Finally, here are some examples you can use to hash your passwords:

Python

Neither PBKDF2 nor bcrypt implementations are included in Python, so you’ll need to use either py-bcrypt or python-pbkdf2.


# With bcrypt
import bcrypt

# Hash a password for the first time, with a randomly-generated salt
hashed = bcrypt.hashpw(password, bcrypt.gensalt())

# gensalt's log_rounds parameter determines the complexity.
# The work factor is 2**log_rounds, and the default is 12
hashed = bcrypt.hashpw(password, bcrypt.gensalt(10))

# With PBKDF2
import pbkdf2
# Generate a 24-byte-long hash (48 hex digits) using 1000 iterations.
# Increase the iteration count for a slower hash calculation.
salted_password = pbkdf2.pbkdf2_hex(password, some_random_salt,
                                    iterations=1000, keylen=24)

Ruby

# Ruby has a PBKDF2-HMAC-SHA1 implemention in its OpenSSL module.
require 'openssl'
salted_password = OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, salt, 1000, 24)
salted_password_hex = salted_password.unpack('H*')[0]

PHP

<?php
// PHP has had a built-in password_hash function since 5.5.0
// It's currently based on BCRYPT, but pass PASSWORD_BCRYPT
// to ensure this doesn't change.

// Generate a password using a random salt
password_hash($password, PASSWORD_BCRYPT);

// Generate a password with a known salt.
password_hash($password, PASSWORD_BCRYPT, array("salt" => $salt));


// Before 5.5:
// 5.3.7 and on: use $2y$ as the salt prefix
// Otherwise, use $2x$
// This will cause crypt to generate a bcrypt hash
$salt = '$2y$10$' . mcrypt_create_iv(22);
$salted_password = crypt($password, $salt)

// Both algorithms generate a 60-character string that looks like:
// $salt . $hashed_password

Java

// Java ships with PBKDF2 support
import java.util.UUID;
import java.security.SecureRandom;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.math.BigInteger;

class Pbkdf2Demo {
  private static final int ITERATIONS = 1000;
  private static final int KEY_LENGTH = 192; // bits


  public static String hashPassword(String password, String salt){
    char[] passwordChars = password.toCharArray();
    byte[] saltBytes = salt.getBytes();

    PBEKeySpec spec = new PBEKeySpec(
        passwordChars,
        saltBytes,
        ITERATIONS,
        KEY_LENGTH
    );
    SecretKeyFactory key = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    byte[] hashedPassword = key.generateSecret(spec).getEncoded();
    return String.format("%x", new BigInteger(hashedPassword));
  }

  public static void main(String[] args) throws Exception{
    System.out.println(hashPassword("password", "salt"));
  }
}

Clojure

; Just use java libs. It's exactly the same!
(ns addressbin.utils
  (:import
    java.security.SecureRandom
    javax.crypto.SecretKeyFactory
    javax.crypto.spec.PBEKeySpec))

(defn pbkdf2
  ; Get a hash for the given string and optional salt
  ([x salt]
   (let [k (PBEKeySpec. (.toCharArray x) (.getBytes salt) 1000 192)
         f (SecretKeyFactory/getInstance "PBKDF2WithHmacSHA1")]
     (format "%x"
             (java.math.BigInteger. (.getEncoded (.generateSecret f k)))))))

There’s also a nice library for scrypt for clojure.

Node.js

Node has a standard pbkdf2 in its crypto library.

var crypto = require('crypto');

var iterations = 1000;
var keylen = 24; // bytes

var callback = function(err, key){
  var hexHash = Buffer(encodedPassword, 'binary').toString('hex');
  // do something with the hashed password
};

crypto.pbkdf2('password', 'salt', iterations, keylen, callback);

Further Reading

Another good article with more detail on the subject

Further Reading

A special message

This is where the affiliate links live, but hear me out! I use these two services every day, and I wouldn't recommend them if I wasn't satisfied.

DigitalOcean - Purveyors of fine (and inexpensive) virtual servers. I use DigitalOcean to host Address Bin and a few others; it's my go-to host. Use this referral link for a $10 credit.

AirBnb - I've been living in AirBnbs for over a year now, and plan to for many more. If you've ever wanted to try them out, you can get a $25 discount from this referral link.