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.
htpasswd
are trivial to reverse. – Conciliar