Adding a salt to .htpasswd?

Ben picture Ben · Nov 14, 2010 · Viewed 11.8k times · Source

Is it possible to add a salt to passwords in .hpasswd files? I assume not since the server would need the salt for each user in order to verify the password and I can't think of how it would get them, but otherwise if the list was to be obtained it would be rather vulnerable. Is there a solution?

Many thanks for your help, Ben

Answer

SimonJ picture SimonJ · Nov 14, 2010

By default htpasswd uses the standard crypt function and thus passwords are already salted - note in this example that both users have the same password yet the hashes are different:

simon@diablo:~$ htpasswd -b -c htpasswd simon abcd
Adding password for user simon
simon@diablo:~$ htpasswd -b htpasswd simon2 abcd
Adding password for user simon2
simon@diablo:~$ cat htpasswd 
simon:NWvm/LCCxQ64E
simon2:2I.LBzsRqULN6

(note: the -b flag is normally discouraged because other users can see your command line arguments and hence the password)

The first two characters of the hash are the salt; passwords are verified by calling crypt() again. Entering the wrong password produces a string that's unequal to the hashed password:

>>> from crypt import crypt
>>> crypt("wrongpass", "NWvm/LCCxQ64E")
'NWbxQgX1unvso'

whereas the correct password produces the expected hash:

>>> crypt("abcd", "NWvm/LCCxQ64E")
'NWvm/LCCxQ64E'

htpasswd -m uses a different algorithm that's MD5-based and uses a longer salt:

simon@diablo:~$ htpasswd -m -b -c htpasswd simon abcd
Adding password for user simon
simon@diablo:~$ cat htpasswd
simon:$apr1$mfvnBVmG$iIHIHOaH9vcImG5G.8eVa/

Here, the salt is the 8 characters between the second and third $.

htpasswd -s stores a SHA-1 digest with no salt; this appears to be for compatibility with Netscape/LDIF:

simon@diablo:~$ htpasswd -s -b -c htpasswd simon abcd
Adding password for user simon
simon@diablo:~$ htpasswd -s -b htpasswd simon2 abcd
Adding password for user simon2
simon@diablo:~$ cat htpasswd 
simon:{SHA}gf6L/odXbD7LIkJvjleEc4KRes8=
simon2:{SHA}gf6L/odXbD7LIkJvjleEc4KRes8=

These can easily be reversed - convert into a hex digest:

>>> "".join("%02x" % ord(c)
...      for c in "gf6L/odXbD7LIkJvjleEc4KRes8=".decode("base64"))
'81fe8bfe87576c3ecb22426f8e57847382917acf'

then use an online hash database.