#280 Several issues with pacrypt() [remaining: part 1.]

v2.3.5
open
nobody
Core (82)
5
2014-08-21
2012-10-10
Martijn
No

I would like to bring to your attention what I think are several (related) issues with pacrypt(). Simply skip to "Issues" if you're in a hurry ;-)

Situation: currently I'm working on a virtual mail system based on
- Postfix (2.7.x) as SMTP only, no LDA
- Dovecot (1.x) as LDA / POP3 / IMAP
- Postfixadmin for simple mailbox management

The data on this system will be coming from an older platform, which uses crypt-md5 ($1$...) salted hashed passwords. For newly made accounts we intent to use the superiour SHA-512 ($6$...) instead. Together with the $version$-strings in crypt()'s hashes, this provides for an excellent way to gradually upgrade everyone to the stronger hash:
- Accounts with older passwords are still able to log in.
- New accounts or account that have their password changed will be given the stronger SHA-512 hash and profit from better hashing.

Now, let's see about the availability of SHA-512 in the surrounding systems:
- Dovecot 1.x password scheme 'CRYPT' will support all the hashing methods available to crypt() from glibc on the system, according to http://wiki.dovecot.org/Authentication/PasswordSchemes - For a system with glibc v2.7+, this usually means SHA-256 ($5$...) and (our choice) SHA-512 are available.

- For upgraders, Dovecot 2.x will support 'SHA512-CRYPT' - http://wiki2.dovecot.org/Authentication/PasswordSchemes

- For PHP it's pretty much the same thing: http://php.net/manual/en/function.crypt.php - for PHP < 5.3.0 it can use what's available in glibc, and for > 5.3.0 it uses it's own implementation if glibc lacks support.

- Postfix is a bit of a question mark for me. I've seen examples that used MySQL's encrypt() to match the password, which makes for a 30+ year old Standard DES hash... I figured the same could work with a non-crypt non-salted MD5() in MySQL. I've also seen hashmaps built from plaintext. None of those options are very encouraging imho. In our case however, it won't matter since SMTP-AUTH on Postfix can use "smtpd_sasl_type = dovecot". In other words: Postfix will ask Dovecot if the SMTP-AUTH password is correct and not even try to figure it out by itself ;-)

Summing it up, this looks like we're pretty much safe to use SHA-512 hashes on the main software, now what about Postfixadmin? I've set up my 2.3.5 installation and configured my $CONF['crypt'] to be 'system' as that seemed like the closest option to crypt() for me.

Issues:
1. The config mentions that 'system' is 'whatever you have set as your PHP system default'. But PHP doesn't really have a system default for that, or at least not one that can be set by the user. What PHP's crypt() will be using automatically seems to be defined by what's likely to be compatible, instead of what's safe. And before actually generating a hash, I don't believe it's possible to see which of the hashing methods crypt() is going to choose. In my tests on a modern Ubuntu system with PHP 5.3.x, calling crypt() without pre-setup (like '$6$rounds=5000$[...salt...]$) salt provides me with a Standard DES hash, just the least it can do.

To force PHP crypt() to generate a (new) SHA-512 password we must provide it with a pre-setup version string and salt. This has to be configurable in Postfixadmin. The configured hashing method should only be forced on hashes we're newly creating, and not on password checking: crypt() will already take care of that (see my second point below).

This way, 'system' is compatible with checking all versions of crypt() hashes in the database, and chooses the preferred hashing method for new accounts.

2. After putting some SHA-512 hashes into the database, I noticed Postfixadmin has trouble reading the passwords although it is using crypt() to check. I had a look at the loop that's used for 'system' but I can't figure out what it's supposed to do exactly. For what I can tell, it can only do crypt-md5, which would make it equal to the seperately configurable 'crypt-md5' in config.inc.php.

If you want to check a password with PHP crypt(), just feed it the entire password from the database. crypt() will extract the salt automatically and then use it to hash the provided password from the first function argument. This would make all the code needed just for checking a password for 'system' like this:

if (strlen($pw_db) !== 0) {
$password = crypt($pw, $pw_db);
} [...]
return $password;

For generating a new password with a specific hash method, a setting should be used.

Summing up 1 and 2, a start for a backwards compatible patch is included. It could use some more refining. I haven't tested it thoroughly, but it does work well on my data. It currently misses support for extended DES (old) and blowfish (recommended), and also I don't check on the variables indicating whether or not a hashing method is available in PHP since I didn't see an easy way to bring that error to the user. But it's a start.

3. Please do not escape passwords before hashing them ;-) I noticed this was reported and also reported fixed so no need to get into that.

Unrelated:
4. The minimum password length seems to be enforced when changing the password only, not when creating an admin or mailbox. Is this intended behaviour?

Discussion

  • Martijn
    Martijn
    2012-10-10

    functions.inc.php crypt() patch

     
    Attachments
    • summary: Several issues with pacrypt() --> Several issues with pacrypt() [remaining: part 1.]
     
  • [edited 2013-12-28 - changed "2." to "part 2" etc. to avoid automatic numbering starting at 1.]

    part 2 fixed in SVN trunk r1595 today

    parts 3 and 4 are fixed in SVN trunk since quite some time (with the switch to edit.php instead of create-.php and edit-.php)

    Note that I'm not planning to backport those changes to the 2.3 branch.

    This means the only remaining part is 1. - the option to set the preferred method when hashing new passwords with crypt(). I need to have a second look at your patch for that ;-)

     
    Last edit: Christian Boltz 2013-12-28