------------------------------------------------------------------------------- Password Hashing This is a guide in hashing a password for storing it in a unix password file. That is not to say the password couldn't be stored in some other manner. It is closely related to Key Derivation (KDF, see "key_derivation.txt" ) which deals with hashing a password to for a cryptographic key. And typically the same process is used. All user passwords should saved in some way that makes the password irrecoverable from the data that is stored... That is it is one way hashed is used. The original password should NOT be recoverable from the hash. If the password is reversible (encrypted) then you are only obfuscating the password. See "passwd_obfuscation.txt" Note that the password store can be encrypted, but the passwords in that store should still not be recoverable. To validate a users password, the input password MUST be hashed in the same way as the stored password, and then the two hashes compared to see if what the user presented matches the one stored, so as to authenticate the user. UNIX Password (and by extension Web Server "htpasswd" files) files have always used this method, and have slowly been updated to include newer more secure hashing algorithms. Each hashed password should also use a different randomly selected 'salt'. This prevents the use of prepared 'rainbow tables' to compare against multiple (thousands) passwords, simultaneously. A second common (global) 'salt' that comes from a separate source (perhaps from off-site server, and saved only in secure memory (such as a kernel keyring), would enhance security even more. This is not done under UNIX for passwords but should be used for web sites. The hashing itself can also benefit from a randomised looped iteration, such as used for PBKDF v2 password derivation, can these days can be part of the password hashing method used. The number of 'rounds' of hashing must then also be stored. ------------------------------------------------------------------------------- Methods... NOTE: The MD5 password ($1$), is not the same as a normal md5sum output. The MD5 hashing is also no longer thought as a secure hashing function, as MD5 was designed to be quick, as a checksum method (MAC) and not as a key derivation function (KDF). Use 'crypt()' -- man crypt All UNIX password generation is based around the crypt() function, which while simple in the manpages is rather complex to use. See using 'perl crypt() below. The UNIX crypt() function (see "man 3 crypt") will encrypt the un-hashed password to be hashed given as the first argument. The second argument provides the method, salt and other arguments the hashing method needs to perform the hash. It does this in the form of the string that it generates as a part of the hashed password. See "man 5 crypt" for details of the string format used for the hashed password and thus the string used as the second argument. For example sha256crypt (method 5) will need a string of 1 to 16 characters for the random salt. '$5$salt.to.use$...' Everything followng the '$' after the salt is ignored, so you can use the hashed password to encrypt the given password, allwoing you to the compare the resulting encrypted-hash with the provided encrypted-hash (password verification). In C crypt_gensalt() can be used to generate the salt. mkpasswd mkpasswd -m sha256crypt Other options -S|--salt salt string (8 to 16 chars), generated if not given -R|--rounds number of rounds -s|--stdin read password from stdin -P fd read password from open file descriptor Or use multiple rounds mkpasswd -m sha256crypt -S salt.to.use -R 12345 -s << $5$rounds=12345$salt.to.use$4Sby9KSSFB0WHrybzSUlpAU/tF0g3tZgjb5EeMS1A73 The problem is you MUST seperate the components to test using "mkpasswd"! As such it is better to test using crypt() passwd=hello encrypt=$(mkpasswd -m sha256crypt -R $RANDOM -s <<<"$passwd" ) echo "$encrypt" test=$( perl -e "print crypt( '$passwd', '$encrypt' )" ) [[ "$encrypt" = "$test" ]] && echo match || echo "no match" #> match openssl openssl passwd # Des encryption (add -salt for compare) openssl passwd -1 # MD5 hashing function (random salt) openssl passwd -5 # SHA-256 password hashing openssl passwd -6 # SHA-512 password hashing And for testing... -salt salt-to-use No provision for multiple hashing 'rounds' have been provided. Generate... openssl passwd -5 hello Or openssl passwd -1 -in - <<<'hello' output example (it varies) #> $1$87FCAFqV$6Sl/54qUg67ZNJHcxd8Gw0 Test (does password hashes match)... openssl passwd -1 -salt 87FCAFqV hello or openssl passwd -1 -salt 87FCAFqV -in - <<< hello Output #> $1$87FCAFqV$6Sl/54qUg67ZNJHcxd8Gw0 Both "mkpasswd" and "openssl" are wrappers around 'crypt()'. They also strip the newline on the input password before passing to crypt(). As crypt() ignores anything from '$' after the salt given to it, you can pass the string starting at the index where the salt starts, without removing the encrypted part (as it is ignored). However you will need to separate and parse the method, and any arguments (like 'rounds='). They were not really designed for testing password, only generating the encrypted password. Perl crypt() Perl makes the system crypt() available on the CLI. The resulting string can then be compared to the stored password (that was used to hash the original password) to verify it. Or it can be saved away as the hashed copy of the users password. For example... perl -e 'print crypt( "hello", "\$1\$salted\$" ), "\n"' #> $1$salted$7s.eNdQf6g1hsogCF8e6c/ Or using 'sha256crypt' perl -e 'print crypt( "hello", "\$5\$salted\$" ), "\n"' #> $5$salted$Ot4d1J/cBDwPlpJEJZ4qw7fARxq2NECZ./37RfzVx.6 Note how the first part of the second argument is also part of the result! This is important as it lets you repeat the hashing with a user supplied password so you can test if the resulting hashes match. The '$' in the resulting string separates that various arguments of the encryption. You don't even need to seperate things.... Generation is not simple... passwd=hello method=5 salt=$(openssl rand -base64 18 | cut -c1-8) # 8 char salt encrypt=$( perl -e "print crypt( '$passwd', '\$${method}\$${salt}' )" ) echo "$encrypt" #> $5$/ZLXWrfP$nOpz7QI1.tZIgOgrQ51Cpw/vCifVQIUP0LES4cohOq4 Testing a passwd is simple... test=$( perl -e "print crypt( '$passwd', '$encrypt' )" ) [[ "$encrypt" = "$test" ]] && echo match || echo "no match" #> match As you can see the two passwords match! See using crypt() to test a password generated by "mypasswd" below. Some formats allow you to iterate the hash, multiple 'rounds', to slow down the hashing process. but I have not been able to get perl crypt() to do this! The whole string is then prefixed with 'loginname:', and any extra fields needed is added after a second ':', such as user info, date, and expiry times. The line is generally saved to the appropriate file like that. For example... user:$1$salted$7s.eNdQf6g1hsogCF8e6c/:100:100 User John:/home/user:/bin/sh As you can guess the salt, and generated hash, do not use '$' or ':' characters, so the extra fields can be used. Old Grub can also generate md5 passwords (grub v1) /sbin/grub --batch --device-map=/dev/null md5crypt heslo quit NOTE: There is a timing attack that looks at how long a password takes to be tested, allowing you to infer how long the target password is. Package 'subtle' makes crypt testing constant to prevent this. ------------------------------------------------------------------------------- OpenLDAP MD5 Password Hash.... That is OpenLDAP password is simply a direct md5 password saved using base64 without any salt! I hope they improve this situation. From "Exporting MD5 from LDAP to Shadow" http://lists.fedoraproject.org/pipermail/389-users/2009-January/008805.html A password in OpenLDAP slappasswd -h {MD5} -s "heslo" {MD5}lV2wuB7xmJtKTf6ugGGppg== Ignoring the padding, the password is a MD5 hash, that has been converted from hexadecimal to base64... direct convertion using md5 in PHP we get... echo '' | php lV2wuB7xmJtKTf6ugGGppg== The OpenLDAP passwd converted from base64 to hexadecimal... echo '' | php 955db0b81ef1989b4a4dfeae8061a9a6 which is the same as the md5 hash as hexadecimal... echo -n "heslo" | md5sum 955db0b81ef1989b4a4dfeae8061a9a6 echo -n "heslo"|openssl dgst -md5 -hex 955db0b81ef1989b4a4dfeae8061a9a6 php -r 'echo md5("heslo");' 955db0b81ef1989b4a4dfeae8061a9a6 ------------------------------------------------------------------------------- Argon2 Hashing Not in crypt() -- YET! There are 3 variants, argon2i, argon2d, argon2id Then they have arguments for parallelism (p), memory (m), iterations (t) version (which you should not need to worry about). Hashing a password (see shell below for more) echo -n "wand~odd~half~creek" | argon2 ThisIsASalt -t 10 -id -e # # => $argon2id$v=19$m=4096,t=10, # p=1$VGhpc0lzQVNhbHQ$GUiNMkT86QIO+DuyuRP49vd8yFenbpaDl1fRg08yeoo # above line is broken at comma for readability # the command argon2-cli can generate a salt if missing The above command always generates the same arguments and resulting hash Note the salt is base64 encoded between the two '$' after the "p=1" salt_base64=$( echo '...above string...' | grep -oP 'p=.\$\K[^$]+' ) echo "$salt_base64" # => VGhpc0lzQVNhbHQ salt=$( echo "$salt_base64" | b64decode ) echo "$salt" # => ThisIsASalt As usual password verification is by rehashing the password with the same arguments. There is a warning that you should use a verifier that always takes the same amount of time, and not return quickly on first miss-match. Optimal Parameters... Install argon python library dnf install python-argon2-cffi or pip install argon2-cffi and run... python3 -m argon2 which will give you some good parameters to use for your machine. Or open /usr/share/doc/python-argon2-cffi-doc/html/parameters.html =======8<--------CUT HERE---------- from argon2 import PasswordHasher from argon2.exceptions import VerifyMismatchError ph = PasswordHasher() password = 'Hello' passwordHash = '$argon2id$v=19$m=65536,t=4,p=2$Sk05cXZxbUhJQ1E5em02dQ$K8U9+ltV14jERcG5v8fMpcRFg75dz49AipKwEQseuoc' try: isValid = ph.verify(passwordHash, password) except VerifyMismatchError: isValid = False print(isValid) =======8<--------CUT HERE---------- # => True Shell "argon2" command Install dnf install argon2 echo 'passwd" | argon2 TheSalt Without parameters, this works out some parameters, the resulting binary hash in Hexidecimal, password file encoding of the hash. Note "argon2" command can't decode the encoded hash itself! To do that you will need to first split encoding on '$' to get.. (undef, variant, version, options, salt, hash) = split(/\$/,encoded); then further separate variant, option values, and b64decode the salt. Arrrggghhh.... NOTES: * Salt must be given BEFORE other options! * v= is not the same as command line -v version. * Use -k for the m= value found the hash. The -m" is a power-of-2 value. EG: -m 12 -> 2^12 -> m=4096 in the encoded hash Basically command line verification is a total pain! PHP =======8<--------CUT HERE----------