bcrypt support for passwords in /etc/shadow

Updated -

Abstract

  • On the internet, many a cry hath gone forth:

    • Please could bcrypt support be added to glibc so that we have a modern, safe way of storing passwords?
    • Why doesn't Red Hat support using bcrypt for passwords in /etc/shadow?
    • According to codahale.com/how-to-safely-store-a-password, using SHA-hashed passwords is dangerous and easy to crack. Why doesn't Red Hat care about security?

Background

  • What is bcrypt?

  • Who uses bcrypt?

    • Of note, the security-focused OpenBSD operating system (along with other BSDs) has used bcrypt as its password-based key derivation function for many years
    • No version of Red Hat Enterprise Linux (indeed few versions of Linux at all) supports use of bcrypt in /etc/shadow
  • On learning this, many folks ask why Linux doesn't support bcrypt

    • The argument for using bcrypt usually centers around how bcrypt must be better than SHA-based schemes because of how quickly modern computers (not to mention specialized hardware) can compute SHA hashes

If not bcypt, then what?

  • In 2007, the GNU C Library project added custom SHA-2 password-hashing algorithms to the crypt() implementation in glibc v2.7

  • The new algorithms utilized SHA-256 and SHA-512 in an iterative process with a configurable number of rounds, i.e., here's an excerpt direct from the code (sha512-crypt.c) where the min, max, and default number of rounds are specified:

    /* Default number of rounds if not explicitly specified.  */
    #define ROUNDS_DEFAULT 5000
    /* Minimum number of rounds.  */
    #define ROUNDS_MIN 1000
    /* Maximum number of rounds.  */
    #define ROUNDS_MAX 999999999
    

    Note that glibc dictates a max of 999,999,999; however, in current versions of RHEL, the pam_unix.so module limits the max to 9,999,999, i.e., here's an excerpt from pam_unix/support.c:

    /* Enforce sane "rounds" values */
    if (on(UNIX_ALGO_ROUNDS, ctrl)) {
            if (on(UNIX_BLOWFISH_PASS, ctrl)) {
                    if (*rounds < 4 || *rounds > 31)
                            *rounds = 5;
            } else if (on(UNIX_SHA256_PASS, ctrl) || on(UNIX_SHA512_PASS, ctrl)) {
                    if ((*rounds < 1000) || (*rounds == INT_MAX))
                            /* don't care about bogus values */
                            unset(UNIX_ALGO_ROUNDS, ctrl);
                    if (*rounds >= 10000000)
                            *rounds = 9999999;
            }
    }
    
  • The implementation is not the same as OpenBSD's bcrypt scheme, but the result is similar: it is time-consuming for an attacker to attempt the creation of hash-collisions because they have to do [at least] thousands of rounds of hashing for each try

Well 2007 was a long time ago ...

  • Note that like bcrypt, this scheme includes a method to raise iterations (and thus computational complexity); from the above-linked specification document:
    (ed.note: emphasis added)

    If the SALT strings starts with

      rounds=<N>$

    where N is an unsigned decimal number the numeric value of N is used to modify the algorithm used. As will be explained later, the SHA-based algorithm contains a loop which can be run an arbitrary number of times. The more rounds are performed the higher the CPU requirements are. This is a safety mechanism which might help countering brute-force attacks in the face of increasing computing power.

  • However, despite surviving years of peer review (and still being in use today in every modern Linux distribution that uses glibc), the default number of rounds has not been raised from 5000

  • That said, the default number of rounds is easily configurable
    RHEL 5.2 added settings to the /etc/login.defs and /etc/libuser.conf files, along with the most important: a rounds= option in pam_unix.so

  • There is also some upstream discussion about tweaking the default behavior, along with another request to implement bcrypt or scrypt

    In addition to that, 2015 saw the completion of the Password Hashing Competition and the announcement of winner Argon2; the following quote from Tomas Mraz (a senior security-focused Software Engineer at Red Hat) gives an idea of what this should mean:

    As the current SHA-2 based password hash is fairly sufficient and comparable with bcrypt in regards to security for implementing any additional password hash we should wait for the Password Hashing Competition to select the best choice. The support should be of course added to glibc and not to individual consumers of the crypt() function.

    As of July 2018, glibc does not yet support Argon2

OK OK but why not bcrypt?

A timeline...

  • A glibc bug was filed in January 2006: Bug 2100 - blowfish crypt support

  • The following excerpt from the original September 2007 announcement by Ulrich Drepper (glibc maintainer) makes it clear that the glibc folks had no animus towards bcrypt
    (Keep in mind that this was written in the context of developing a replacement for MD5-based password-hashing; ed. note: emphasis added)

    There have been solutions to the problem for some time. Specifically the Blowfish encryption-based solution is implemented on a variety of systems, including some Linux distributions. The design of this method is sound: an encryption algorithm which has survived peer review for some time and which is not fast is used together with the obligatory salt. The result is a safe password encoding.
    ...
    I can already hear everybody complaining that I suffer from the NIH syndrome but this is not the reason. The same people who object to MD5 make their decisions on what to use also based on NIST guidelines. And Blowfish is not on the lists of the NIST. Therefore bcrypt() does not solve the problem.

    What is on the list is AES and the various SHA hash functions. Both are viable options. The AES variant can be based upon bcrypt(), the SHA variant could be based on the MD5 variant currently implemented.

    Since I had to solve the problem and I consider both solutions equally secure I went with the one which involves less code. The solution we use is based on SHA. More precisely, on SHA-256 and SHA-512.

  • An excerpt from glibc's original 2007 specification document illuminates the decision further
    (ed. note: emphasis added)

    Requests for a better solution to the problem have been heard for some time. Security departments in companies are trying to phase out all uses of MD5. They demand a method which is officially sanctioned. For US-based users this means tested by the NIST.

    This rules out the use of another already implemented method with limited spread: the use of the Blowfish encryption method. The choice comes down to tested encryption (3DES, AES) or hash sums (the SHA family).

    Encryption-based solution do not seem to provide better security (no proof to the contrary) and the higher CPU requirements can be compensated for by adding more complexity to a hash sum-based solution. This is why the decision has been made by a group of Unix and Linux vendors to persue this route.

    The SHA hash sum functions are well tested. By choosing the SHA-256 and SHA-512 algorithms the produced output is 32 or 64 bytes respectively in size. This fulfills the requirement for a large output set which makes rainbow tables less useful to impossible, at least for the next years.

  • Around the same time, glibc Bug 2100 (blowfish crypt support) was closed

  • Years later, the infamous codahale article (How To Safely Store A Password) appeared, leading to lots of misunderstanding

  • Soon after that in October 2011, someone opened an RFE for Fedora 16 to implement bcrypt:

    They were understandably directed to file an RFE with upstream glibc, which they did (still October 2011)

    A whole 4 years after Ulrich Drepper and team implemented the SHA-based password-hashing solution in glibc's crypt() (which addresses the concerns of the above-mentioned article), Drepper commented:

    I added a long time back the new hash sum based password mechanisms. They are at least as good. I'm not going to add anything more.

    Soon after, the new 13286 glibc RFE was closed as "RESOLVED WONTFIX"

  • In April 2014, a new glibc RFE was created -- Bug 16814 - RFE: Reconsider adding bcrypt (or scrypt) support

  • Somewhere around June 2017, the 13286 glibc RFE had its status changed from "RESOLVED WONTFIX" to "RESOLVED DUPLICATE of bug 2100" and bug 2100 was re-opened with a comment suggesting that bcrypt might be added someday

Comments