Given the focus on security breaches leaking account information the last few years, we have taken a fresh look at how secure our LDAP passwords really are, and if we can let OpenLDAP use a modern hash algorithm.
So what is a modern hash? Articles on the net usually mention SHA-512, PBKDF-2, brypt and scrypt. The latter is considered better as it consumes an arbitrarily large amount of memory and can thus be more resistant to a GPU based attack.
Storing passwords
OpenLDAP can store passwords in clear-text, as encrypted strings, or as
hashes (one-way algorithms). Usually one stores the password in the
userPassword attribute provided by or inherited from the
organization
, organizationalUnit
or person
object class
(RFC4519).
The userPassword attribute is in most installations by default protected with ACLs in the server configuration, by only giving administrators or the owner of the object containing the attribute access to this attribute.
The LDAP server content is in it self stored in a local database backend, usually BDB or HDB.
In a worst case scenario, a malicious user can either somehow bypass the LDAP server access protection and retrieve passwords or hashes via the LDAP protocol, or somehow access the OS, get root privileges and read the LDAP server’s database file from the file system. In these cases, a strong password hash is imperative.
OpenLDAP built-in security
If the password content is prepended by a `{
- MD5
- hashed password using the MD5 hash algorithm
- SMD5
- MD5 with salt
- SHA
- hashed password using the SHA-1 hash algorithm
- SSHA
- SHA-1 with salt
The SSHA is given as the most secure password scheme supported. Unfortunately attacks against SHA-1 were found back in 2005 and the scheme has been officially frowned upon for a long time. The salt does help, but SHA-1 is getting a bit long in the tooth. To put it mildly.
So is that it?
OpenLDAP pass-through authentication
OpenLDAP can also use external processes to verify and hash passwords. These schemes are:
- CRYPT - will use the OS’ crypt library as a password handler
- SASL - will use Cyrus SASL as a password handler
Cyrus SASL was last updated in 2012, but CRYPT is a part of the POSIX API and should be continuously updated. So - can CRYPT give us an up-to-date hash?
Crypt to the rescue
It turns out that Linux based glibc versions of crypt support additional encryption schemes through an additional versioning scheme encoded in the password hash, This scheme is now defined as the PHC String format. The versions currently supported in glibc crypt are:
Scheme ID | Schema |
---|---|
1 | MD5 |
2a | Blowfish / bcrypt |
3 | NTHASH |
5 | SHA-256 |
6 | SHA-512 |
md5 | Solaris MD5 |
sha1 | PBKDF1 with SHA-1 |
The format used for the additional encryption schemes can be expressed according to the PHC string format:
$<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
Here the salt and the hash are Base64 encoded strings.
An interesting detail here is the parameter argument in the PHC formatted string - for
the sha-512 hash a valid argument is rounds=<N>
, with a default
value of 5000. This implements a key stretching scheme.
So with SHA-512, a long salt and a high ‘rounds’ parameter, we should be able to generate a reasonably secure hash. As of writing.
OpenLDAP default password variables
You can instruct OpenLDAP to use a strong encryption scheme by default:
password-hash {CRYPT}
password-crypt-salt-format "$6$%.16s"
Or for a cn=config enabled OpenLDAP server:
olcPasswordHash: {CRYPT}
olcPasswordCryptSaltFormat: $6$%.16s
The latter setting’s value is in sprintf(3) format which should only contain one % conversion. The string character count implies the length of the salt, which for crypt can be up to 16 characters long.
In order to gain a longer key stretch, you can use:
password-crypt-salt-format "$6$rounds=50000$%.16s"
I have unfortunately not had the time to test the key-stretching default value, but will update this post with results later.
mkpasswd and LDIF
mkpasswd
is a handy utility which can create a PHC formatted hash. In
the following example, I read a salt from /dev/random
:
$ mkpasswd --rounds 500000 -m sha-512 --salt `head -c 40 /dev/random | base64 | sed -e 's/+/./g' | cut -b 10-25` 'Try to break this one!'
$6$rounds=500000$KWcfAQjEPsey2aEr$X/E/I9ySD3zO6Z6cfrTo6MrSIMV5oNQuLjxg4kf4nB8f0WZLFuBYlQ86EkHatocuLB0ajd6DsHfmn8Bajoo9u/
Combined with LDIF we get the following hypothetical LDIF string in order to set a secure password for a given account:
dn: uid=lars,ou=Employees,dc=redpill-linpro,dc=com
changetype: modify
replace: userPassword
userPassword: {CRYPT}$6$rounds=500000$KWcfAQjEPsey2aEr$X/E/I9ySD3zO6Z6cfrTo6MrSIMV5oNQuLjxg4kf4nB8f0WZLFuBYlQ86EkHatocuLB0ajd6DsHfmn8Bajoo9u/
Further steps
With the password storage in a better shape, don’t forget to ensure that all communication with the LDAP server are encrypted!