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/htpasswd.data -a someuser"; it will create the htpasswd.data 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.