Thursday, March 3, 2016

LDAP password hashing in PHP, a better way

Reviewing some PHP code related to user password changes, I noticed that the code was using unsalted SHA-1 hashes to store the password in LDAP. This is potentially very bad, if an attacker were able to gain access to the password hashes, because pre-computed rainbow tables for unsalted SHA-1 are pretty common. This has been the basis for a number of high-profile data breaches in recent memory, and I don't want to be on the news.

Looking for a better solution on Google, I quickly discovered the source of the bad code, the OpenLDAP FAQ-o-matic. In this FAQ entry, there are code examples for a variety of languages, many offering higher security. However, the PHP example included only a SHA-1 variant, and was copy-pasted nearly verbatim into my subject code (I wonder how many other web applications have done this very thing).

OpenLDAP has support for a variety of different hashing algorithms, including an optional SHA-2 module, and passthrough to the OpenSSL crypto(3) library. The strongest native cipher supported by OpenLDAP is salted-SHA1 (SSHA), which allows sufficient strength for this application, when used with a decently-large salt. I wrote the following function to generate such hashes from PHP, provided here in hopes that people will quit using unsalted SHA in their web-apps.

 * This is a helper function, returning a Salted SHA-1 hash, suitable for LDAP.
 * OpenLDAP uses a slightly strange scheme for generating these hashes, but it's
 * far better than unsalted SHA. The only limit on salt length appears to be the
 * maximum length of the userPassword attribute in LDAP (128), allowing us to
 * safely use a 64-byte salt, resulting in a 118-byte SSHA. For the curious,
 * this works out to:
 *   6B ({SSHA} tag) + 28B (20B SHA, B64 encoded) + 88B (64B salt, B64 encoded)

function generate_ssha_hash($cleartext)

         * Generate a unique 64-byte salt value for the salt.
         * Use mcrypt_create_iv to generate some random bytes. PHP 7 has a
         * random_bytes() function, and the OpenSSL extension provides
         * openssl_random_pseudo_bytes(), which may be better, but this
         * should work
        $pw_salt = mcrypt_create_iv(64, MCRYPT_DEV_URANDOM);

        // Concatenate and hash password+salt.
        $hashed = sha1($cleartext . $pw_salt, TRUE);

        // Add the tag and encoded hash+salt.
        $hashed = '{SSHA}' . base64_encode($hashed . $pw_salt);

        // Clean up

        return $hashed;

} // generate_ssha_hash

No comments:

Post a Comment