Password problems with crypt() and htpasswd files

In working with Solar today, I discovered an issue related to the crypt() function and password files generated by Apache htpasswd. Technically, it’s not a security issue with either of those fine programs, because they do work as documented and intended. However, due to my own ignorance of the limitations of crypt(), I created a security issue of my own; perhaps this post will help others avoid it.

The Solar_User_Auth class is very much like the PEAR Auth class, in that it lets you pick different container or storage types for your username/password authentication. You can use a database table, LDAP, POP or IMAP email account, a .ini file, and more. Similar to LiveUser, Solar_User_Auth also comes with a “Multi” container that lets you specify multiple authentication sources, so you can fall back from one to another automatically.

My problem today was with the “Htpasswd” container for Solar_User_Auth. Apache comes with a utility called “htpasswd” which lets you create a file of usernames and encrypted passwords. The file format is pretty straightforward; each line consists of “username:cryptedpass”. To create a htpasswd file and insert the first username/password combination, you would issue “htpasswd -c /where/you/want/ -a someuser”; it will create the file and prompt you for the password for “someuser”, encrypting the password with the system crypt() function.

Now here’s the thing about crypt() … effectively, it only looks at the first 8 characters of the password to generate the encrypted hash. (Yes, there are ways to make crypt() use a longer salt, but that’s not pertinent to this particular discussion, as we are only concerned with the way Apache htpasswd uses the crypt() function.)

Thus, if you have a password *longer* than 8 characters, as long as the first 8 characters match, crypt() will call it a valid match. For example, if your password is “password” and is stored as a crypted hash in a htpasswd file, checking against “passwordX” will be exactly the same as checking against “password”, “password123”, and so on; that is, it will be returned as a valid check because the first 8 characters match properly. Similarly, if your password is “longpassword” and you check against only “longpass”, that will be returned as valid, too.

Obviously this is a problem. The solution, at least for Solar_User_Auth_Htpasswd, is that from now on, password checks longer than 8 characters will be rejected automatically as invalid. This sidesteps the problem entirely, even though it does limit users who want to use the Htpasswd driver to passwords of 8 characters or less. The next release of Solar will have this patch in place, and it has already been committed to the Subversion repository for Solar.

Have I missed anything important here? Has anyone else out there run into anything like this? If so, what was your solution?

Update: (2005-04-14) Although I’m retaining the 8-character limit under default DES encryption, I’ve added SHA1 and APR1-MD5 support, which should help a great deal.

Are you stuck with a legacy PHP application? You should buy my book because it gives you a step-by-step guide to improving your codebase, all while keeping it running the whole time.

6 thoughts on “Password problems with crypt() and htpasswd files

  1. Clarification: are you saying that when crypted with the same salt “password” and “password2” produce the same string (or at least strings with matching first eight characters)?

    Or did I misunderstand?

  2. Hi, Oscar — You understood; that does appear to be the case. Apparently it’s a “known limitation” of crypt(). However, I’m doing a little more research and there may be another workaround (e.g., having htpasswd use SHA encryption, but that’s not the default).

  3. You can use crypt(), MD5 or SHA for password. All within the same password file too. Why not just force users who have older crypt password to change their password and then save it using MD5.

  4. This is from the GNU crypt man pages.

    The glibc2 version of this function has the following additional fea-
    tures. If salt is a character string starting with the three charac-
    ters “$1$” followed by at most eight characters, and optionally termi-
    nated by “$”, then instead of using the DES machine, the glibc crypt
    function uses an MD5-based algorithm, and outputs up to 34 bytes,
    namely “$1$$”, where “” stands for the up to 8 charac-
    ters following “$1$” in the salt, followed by 22 bytes chosen from the
    set [a-zA-Z0-9./]. The entire key is significant here (instead of only
    the first 8 bytes).

    Programs using this function must be linked with -lcrypt

  5. I found that out too, with the latest stable Debian release. I wonder how this kind of problem is still here, after 6 years from the original post of this thread.

Leave a Reply

Your email address will not be published. Required fields are marked *