Paragon Initiative Enterprises Blog

The latest information from the team that develops cryptographically secure PHP software.

How to Safely Store Your Users' Passwords in 2016

If you are unfamiliar with cryptography concepts or the vocabulary it uses, or especially you are looking for guidance on "password encryption", please read this page first.

We've previously said that even security advice should carry an expiration date. So unlike most of our past blog posts, this page should be considered a living document: As requirements change and new attacks are discovered, we will update it accordingly. A changelog is at the end of the document.

Semantic point: Don't store the password, store a hash of the password. (Obligatory.)

Modern, Secure, Salted Password Hashing Made Simple

The Problem: You want people to be able to create a unique user account, with a password, which they will use to access your application. How can you safely implement this feature?

Easiest Solution: Use libsodium, which provides a secure password hashing API in most languages. As of version 1.0.9, libsodium delivers Argon2, the most recent, carefully-selected algorithm from the Password Hashing Competition. Libsodium offers bindings for most programming languages.

Note: There was a published attack on an earlier version of Argon2i, the recommended variant of Argon2 for general purpose password hashing. The practical implications weren't severe; if you're using Argon2i version 1.3 or higher (with at least 3 passes) you're safe. Libsodium does this.

If you, for whatever reason, cannot reconcile your requirements with installing libsodium, you have other options. In preparing this blog post, our security team has investigated several password hashing libraries in multiple programming languages. What follows is our current recommendations for secure password storage with example code.

Acceptable Password Hashing Algorithms

Although there is disagreement about how to rank them, cryptography experts agree that these algorithms are the only ones you should be using to store passwords in 2016:

Secure Password Storage in PHP

If you want to upgrade to Argon2i before PHP's password hashing API catches up, the only way to do so is through libsodium. Refer to the relevant section of the documentation for instructions on installing libsodium for PHP.

// Password hashing:
$hash_str = \Sodium\crypto_pwhash_str(
    $password,
    \Sodium\CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
    \Sodium\CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);
// Password verification:
if (\Sodium\crypto_pwhash_str_verify($hash_str, $password)) {
    // recommended: wipe the plaintext password from memory
    \Sodium\memzero($password);
    
    // Password was valid.
} else {
    // recommended: wipe the plaintext password from memory
    \Sodium\memzero($password);
    
    // Password was invalid.
}

If you're using Halite (our developer-friendly FOSS PHP libsodium wrapper), the Password class uses Argon2i since version 2.0.0.

<?php
use ParagonIE\Halite\{
    KeyFactory,
    Password
};

// Load the encryption key:
$key = KeyFactory::loadEncryptionKey('/path/to/encryption_key');

// Calculate the encrypted password hash:
$hash = Password::hash($password, $key);

// Verify the given password against the encrypted password hash:
if (Password::verify($password, $hash, $key)) {
    // Login successful
}

Alternative: Bcrypt Password Hashing in PHP

First, make sure you're using a supported version of PHP. If you are, then the PHP password API will be available for use. If you aren't, consider upgrading. If you can't, check out password_compat.

$hash = password_hash($userPassword, PASSWORD_DEFAULT);

password_hash() takes care of salting the hash, transparently. You can, however, specify your own cost. The absolute minimum value you should consider using is 10. 12 is good, provided your hardware supports it. The default cost parameter is 10.

$hash = password_hash($userPassword, PASSWORD_DEFAULT, ['cost' => 12]);

Verifying a password against a stored hash is incredibly simple:

if (password_verify($userPassword, $hash)) {
    // Login successful.
    if (password_needs_rehash($hash, PASSWORD_DEFAULT, ['cost' => 12])) {
        // Recalculate a new password_hash() and overwrite the one we stored previously
    }
}

As of PHP 7, PASSWORD_DEFAULT still uses bcrypt. In a future version, it may migrate to Argon2.

Alternative: Scrypt Password Hashing in PHP

If you aren't using libsodium (which we strongly recommend that you do use!), you can still get scrypt hashes in PHP through Dominic Black's Scrypt PHP Extension from PECL.

# If you don't have PECL installed, get that first.
pecl install scrypt
echo "extension=scrypt.so" > /etc/php5/mods-available/scrypt.ini
php5enmod scrypt

Next, grab a copy of the bundled PHP wrapper and include it in your project.

# Hashing
$hash = \Password::hash($userProvidedPassword);
# Validation
if (\Password::check($userProvidedPassword, $hash)) {
    // Logged in successfully.
}

Secure Password Storage in Java

There is a Java implementation of Argon2 outside of libsodium, but it requires you to specify the parameters rather than providing sane defaults.

// Create instance
Argon2 argon2 = Argon2Factory.create();

// Read password from user
char[] passwd = readPasswordFromUser();

try {
    int N = 65536;
    int r = 2;
    int p = 1;
    // Hash password
    String hashed = argon2.hash(r, N, p, passwd);
    
    // Validating a hash
    if (argon2.verify(hash, passwd)) {
        // Login successful
    }
} finally {
    // Wipe confidential data
    argon2.wipeArray(passwd);
}

Alternative: Bcrypt Password Hashing in Java

jBCrypt provides the bcrypt password hashing algorithm.

String hash = BCrypt.hashpw(userProvidedPassword, BCrypt.gensalt());

Verifying a bcrypt hash in Java:

if (BCrypt.checkpw(userProvidedPassword, hash)) {
    // Login successful.
}

Alternative: Scrypt Password Hashing in Java

There is a Java implementation of scrypt, but it requires you to specify the parameters rather than providing sane defaults.

# Calculating a hash
int N = 16384;
int r = 8;
int p = 1;
String hashed = SCryptUtil.scrypt(passwd, N, r, p);

# Validating a hash
if (SCryptUtil.check(passwd, hashed)) {
    // Login successful
}

Secure Password Storage in C# (.NET)

Argon2 is available for .NET developers through the .NET bindings for libsodium.

// Hashing a password
var hash = PasswordHash.ArgonHashString(yourPasswordString, Strength.Interactive)
if (PasswordHash.ArgonHashStringVerify(hash, yourPasswordString) {
    //correct password
}

Alternative: Bcrypt Secure Password Storage in C# (.NET)

We recommend Chris McKee's Bcrypt.NET over System.Security.Cryptography.Rfc2898DeriveBytes, which is PBKDF2-SHA1. (We're not saying PBKDF2-SHA1 is unsafe, but that bcrypt is preferable to it.)

// Calculating a hash
string hash = BCrypt.HashPassword(usersPassword, BCrypt.GenerateSalt());

// Validating a hash
if (BCrypt.Verify(usersPassword, hash)) {
    // Login successful
}

Alternative: Scrypt Password Hashing in C# (.NET)

There is an Scrypt package in NuGET as well.

// This is necessary:
ScryptEncoder encoder = new ScryptEncoder();
// Calculating a hash
SecureString hashedPassword = encoder.Encode(usersPassword);
// Validating a hash
if (encoder.Compare(usersPassword, hashedPassword)) {
    // Login successful
}

Secure Password Storage in Ruby

There is a Ruby Argon2 gem, which is rather straightforward to use:

hasher = Argon2::Password.new
hashed_password = hasher.create("password")

if Argon2::Password.verify_password("password", hashed_password)
  # Login successful

Alternative: Bcrypt Hashing in Ruby

From the author of "Use bcrypt. Use bcrypt. Use bcrypt..." comes a Ruby gem for bcrypt password hashing.

require "bcrypt"

# Calculating a hash
my_password = BCrypt::Password.create(usersPassword)
# Validating a hash
if my_password == usersPassword
  # Login successful

Beware that, as of this writing, this library is not following cryptography coding best practices. Consider applying this patch until they get around to merging it.

Alternative: Scrypt Hashing in Ruby

There is also a Ruby gem for scrypt password hashing.

require "scrypt"

# Calculating a hash
password = SCrypt::Password.create(usersPassword)
# Validating a hash
if password == usersPassword
  # Login successful

Secure Password Storage in Python

Python developers generally prefer passlib (Bitbucket). Since version 1.7.0, offers Argon2 support.

from passlib.hash import argon2

# Calculating a hash
hash = argon2.using(rounds=4).hash(usersPassword)

# Validating a hash
if argon2.verify(usersPassword, hash):
    # Login successful

Alternative: Bcrypt Hashing in Python

Passlib also offers bcrypt password hashing. Before version 1.7.0, however, it was called bcrypt.encrypt() rather than bcrypt.hash().

from passlib.hash import bcrypt

# Calculating a hash
hash = bcrypt.hash(usersPassword, rounds=12)

# Misnomer, but that's what it was called before v1.7.0:
# hash = bcrypt.encrypt(usersPassword, rounds=12)

# Validating a hash
if bcrypt.verify(usersPassword, hash):
    # Login successful

Alternatively, you can't go wrong with the bcrypt Python package (also on github):

import bcrypt
import hmac

# Calculating a hash
password = b"correct horse battery staple"
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
# Validating a hash (don't use ==)
if (hmac.compare_digest(bcrypt.hashpw(password, hashed), hashed)):
    # Login successful

Alternative: Scrypt Hashing in Python

Passlib, since version 1.7.0, offers Scrypt support.

from passlib.hash import scrypt

# Calculating a hash
hash = scrypt.using(rounds=8).hash(usersPassword)

# Validating a hash
if scrypt.verify(usersPassword, hash):
    # Login successful

Secure Password Storage in Node.js

There are other implementations of Argon2 available in the Node.js ecosystem, but you're better off using node-sodium, which handles salt generation for you.

// notice the .api part; that's important
var sodium = require('sodium').api;

var hash = new Buffer(sodium.crypto_pwhash_STRBYTES);
var passwordBuffer = new Buffer("password goes here");

hash = sodium.crypto_pwhash_str(
  passwordBuffer,
  sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, 
  sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE
);

// Validation:
if (sodium.crypto_pwhash_str_verify(
  hash,
  passwordBuffer
)) {
  // You are logged in
}

Alternative: Bcrypt Password Hashing in Node.js

There are two secure implementations of bcrypt in Node.js, although bcrypt (Github) seems to be the preferred one. We're making extensive use of Promises to greatly simplify error handling in the examples below:

var Promise = require("bluebird");
var bcrypt = Promise.promisifyAll(require("bcrypt"));

function addBcryptType(err) {
    // Compensate for `bcrypt` not using identifiable error types
    err.type = "bcryptError";
    throw err;
}

// Calculating a hash:
Promise.try(function() {
    return bcrypt.hashAsync(usersPassword, 10).catch(addBcryptType);
}).then(function(hash) {
    // Store hash in your password DB.
});

// Validating a hash:
// Load hash from your password DB.
Promise.try(function() {
    return bcrypt.compareAsync(usersPassword, hash).catch(addBcryptType);
}).then(function(valid) {
    if (valid) {
        // Login successful
    } else {
        // Login wrong
    }
});

// You would handle errors something like this, but only at the top-most point where it makes sense to do so:
Promise.try(function() {
    // Generate or compare a hash here
}).then(function(result) {
    // ... some other stuff ...
}).catch({type: "bcryptError"}, function(err) {
    // Something went wrong with bcrypt
});

Alternative: Scrypt Password Hashing in Node.js

We recommend scrypt for humans (Github), a developer-friendly node-scrypt wrapper that's easy to use and hard to misuse.

var Promise = require("bluebird");
var scrypt = require("scrypt-for-humans");

// Calculating a hash:
Promise.try(function() {
  return scrypt.hash(usersPassword);
}).then(function(hash) {
  // Store hash for long term use
});

// Validating a hash:
Promise.try(function() {
  return scrypt.verifyHash(usersPassword, hash);
}).then(function() {
  // Login successful
}).catch(scrypt.PasswordError, function(err) {
  // Login failed
});

(Example code made better by Sven Slootweg, the scrypt-for-humans maintainer.)

Frequently Asked Questions

Where's PBKDF2?

Although PBKDF2 is more widely available than bcrypt or scrypt, it doesn't offer the GPU resistance that we need from a password hashing function. If you must use PBKDF2, make sure you use at least 100,000 iterations and a SHA2 family hash function.

To reiterate: PBKDF2 can still be secure. It's the least secure of the acceptable password hashing algorithms on this page, so we aren't going to provide any example code.

Why prioritize bcrypt over scrypt?

On a technical level, they're vastly different, but for practical purposes they're morally equivalent. The real weakness is in not using an acceptable password hashing function at all. If you can use either of these two, use it. They're both fine.

Our choice for bcrypt as the default was simply: In PHP (which represents a little over 80% of the Internet), the easiest choice for developers to implement in their applications is bcrypt (via the password hashing API that shipped with PHP 5.5). Using scrypt requires root access and the ability to install PHP extensions via PECL.

Why choose scrypt over bcrypt:

Why choose bcrypt over scrypt:

  • Scrypt's memory hardness isn't perfect
  • Scrypt requires about 1000 times the memory as bcrypt for the same security against GPU attacks

If you have to choose between the two for password hashing, they're both good options. If you choose bcrypt, however, passing a base64-encoded SHA-384 hash to bcrypt is probably a good move:

  • base64_encode(hash('sha384', $password, true)) is 64 characters, which nearly fills up the 72 character keyspace
  • A base64-encoded hash is guaranteed to not contain NUL bytes
  • Two passwords with the same 72 character prefix but differ after the 73rd character will, with overwhelming likelihood, produce different SHA-384 hashes
  • SHA-384 has about 192 bits of birthday collision resistance
  • SHA-384 is also the largest SHA-2 family hash function that is resistant to length extension attacks

The above construction may invite theoretical concerns about entropy reduction (i.e. 72 characters of raw binary without any NUL bytes comes out to about 573 bits of possible entropy, but a SHA-384 hash outputs are clearly limited to 384 bits).

A birthday SHA-384 collision still requires $2^{192}$ guesses. $2^{192}$ is large enough to fall under boring cryptography, barring any new attacks.

I'm not using bcrypt/scrypt. How should I migrate my legacy hashes?

The most straightforward approach to migrating your legacy hashes from, e.g. MD5, to bcrypt/scrypt is to follow this strategy (which was first introduced to us in a Reddit discussion by NeoThermic):

  1. Add a column to your user accounts table, called legacy_password (or equivalent).
  2. Calculate the bcrypt/scrypt/Argon2 hash of the existing password hashes and store them in the database.
  3. Modify your authentication code to handle the legacy flag.

When a user attempts to login, first check if the legacy_password flag is set. If it is, first pre-hash their password with your old password hashing algorithm, then use this prehashed value in place of their password. Afterwards, recalculate the bcrypt hash and store the new hash in the database, disabling the legacy_password flag in the process. A very loose example in PHP 7+ follows:

/**
 * This is example code. Please feel free to use it for reference but don't just copy/paste it.
 *
 * @param string $username Unsafe user-supplied data: The username
 * @param string $password Unsafe user-supplied data: The password
 * @return int The primary key for that user account
 * @throws InvalidUserCredentialsException
 */
public function authenticate(string $username, string $password): int
{
    // Database lookup
    $stmt = $this->db->prepare(
        "SELECT
             userid, passwordhash, legacy_password
         FROM
             user_accounts
         WHERE
             username = ?"
    );
    $stmt->execute([$username]);
    $stored = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$stored) {
        // No such user, throw an exception
        throw new InvalidUserCredentialsException();
    }
    if ($stored['legacy_password']) {
        // This is the legacy password upgrade code
        if (password_verify(legacy_hashing_algorithm($password), $stored['passwordhash'])) {
            $newHash = password_hash($password, PASSWORD_DEFAULT);
            $stmt = $this->db
                ->prepare(
                   "UPDATE
                        user_accounts
                    SET
                        passwordhash = ?,
                        legacy_password = FALSE
                    WHERE
                        userid = ?"
                )->execute([
                    $newHash,
                    $stored['userid']
                ]);
            // Return the user ID (integer)
            return $stored['userid'];
        }
    } elseif (password_verify($password, $stored['passwordhash'])) {
        // This is the general purpose upgrade code e.g. if a future version of PHP upgrades to Argon2
        if (password_needs_rehash($stored['passwordhash'], PASSWORD_DEFAULT)) {
            $newhash = password_hash($password, PASSWORD_DEFAULT);
            $this->db
                ->prepare("UPDATE user_accounts SET passwordhash = ? WHERE userid = ?")
                ->execute([$newhash, $stored['userid']]);
        }
        // Return the user ID (integer)
        return $stored['userid'];
    }
    // When all else fails, throw an exception
    throw new InvalidUserCredentialsException();
}

Usage:

try {
    $userid = $this->authenticate($username, $password);
    // Update the session state
    // Redirect to the post-authentication landing page
} catch (InvalidUserCredentialsException $e) {
    // Log the failure
    // Redirect to the login form
}

Proactively upgrading legacy hashes is a security win over an opportunistic strategy (rehashing when the user logs in, but leave the insecure hashes in the database for inactive users): With a proactive strategy, if your server gets compromised before everyone logs in again, their passwords are already using an acceptable algorithm (bcrypt, in the example code).

The above example code is also available in Bcrypt-SHA-384 flavor.

What about password managers?

Password managers are a different beast. When we say "don't encrypt passwords", we're talking about server-side password validation. You should definitely use a password manager.

How do I implement secure server-side password storage in (other language)?

If you'd like us to consider another programming language for inclusion on this page, please reach out to our security@ team directly and we'll let popular demand guide our investigations.

I followed the advice on this page, but how can I be sure I did it correctly?

Check the library you're using; it should provide test vectors. If all else fails: We audit code.

Changelog

  • 2017-03-19 - Recommend a better (and maintained) Bcrypt implementation for .NET projects.
  • 2016-12-13 - We now recommend Argon2 as the go-to algorithm of choice, and demoted bcrypt to an alternative.
  • 2016-05-16 - Argon2 v1.3 has matured and landed in libsodium. We've updated our recommendations for each language to explain how to get Argon2i as an alternative. Once it's widely adopted, we will make it the primary recommendation. Specifically covered:

About the Author

P.I.E. Staff

Paragon Initiative Enterprises

Paragon Initiative Enterprises is a Florida-based company that provides software consulting, application development, code auditing, and security engineering services. We specialize in PHP Security and applied cryptography.


Need Technology Consultants?

Will tomorrow bring costly and embarrassing data breaches? Or will it bring growth, success, and peace of mind?

Our team of technology consultants have extensive knowledge and experience with application security and web/application development.

We specialize in cryptography and secure PHP development.

Let's Work Together Towards Success

Our Security Newsletters

Want the latest from Paragon Initiative Enterprises delivered straight to your inbox? We have two newsletters to choose from.

The first mails quarterly and often showcases our behind-the-scenes projects.

The other is unscheduled and gives you a direct feed into the findings of our open source security research initiatives.

Quarterly Newsletter   Security Announcements