Relentless Coding

A Developer’s Blog

Upgrade Password Hashing Method for Existing Users

When you’ve been using the same Linux system for a while, you might want to upgrade the password hashing methods used. This article explains how to discover the methods supported by your system, set the preferred method and then upgrade the password hashes to make use of the new hashing method.

You can see the supported hashing methods by looking at man 5 crypt. (Note that man section 5 indicates “File formats and conversions, e.g. /etc/passwd”.) Currently, on my system, yescrypt is favored and recommended for new hashes.

To check your current hashing method on somebody’s account:

# getent shadow john
john:$6$JYJjfxe2YGsIGlvz$7bDibTmj/Iq.EelFEBJd24Ip66fVeBLGK/CMUyOl/9YpgIxtDfq2cLzzAc5b7sXhW3nCc2f4lKzMejFrBr3UF/:18133::::::

(You can read about the format of shadow by looking at man 5 shadow.) Again, looking at man 5 crypt, we see that the $6$ in the second section after the first colon : indicates that this password was hashed with sha512crypt which, according to the same documentation is acceptable for new hashes. But we know we can do better by using a “memory-hard” hash such as yescrypt.

passwd(1) changes user passwords. It will use the hashing method specified by the pam_unix.so module in the PAM configuration (/etc/pam.d/passwd) or else look at the ENCRYPT_METHOD variable in /etc/login.defs:

$ grep ^password /etc/pam.d/passwd
password	include		system-auth
$ grep ^password /etc/pam.d/system-auth
password   required pam_unix.so try_first_pass nullok shadow
password   optional pam_permit.so

(Information about pam_unix.so arguments can be found in man 8 pam_unix.)

Here, we see that nothing explicit is said about which hashing method should be used for passwords. So we turn to /etc/login.defs:

$ grep ^ENCRYPT_METHOD /etc/login.defs
ENCRYPT_METHOD YESCRYPT

Now, when we upgrade our password with passwd, it will be protected by the yescrypt ($y$) hashing method:

# getent shadow john
john:$y$j9T$8Z3MNrSUYkSPZWcm83Dh$p.Qev5ogCpuF.KfGBjw1QfBBiV7m05PPWxIGuNtbJc9:19793:0:99999:7:::

You can create a yescrypt password hash yourself by running:

$ export PASS=pass SALT='$y$j9T$8Z3MNrSUYkSPZWcm83Dh$'
$ perl -le 'print crypt($ENV{PASS}, $ENV{SALT})'
$y$j9T$8Z3MNrSUYkSPZWcm83Dh$p.Qev5ogCpuF.KfGBjw1QfBBiV7m05PPWxIGuNtbJc9

You can read more about the format of the input to crypt(3) in man 5 crypt under “FORMAT OF HASHED PASSPHRASES”. Suffice to say here that it consists of a prefix, options, a salt and a hash, separated by $. The prefix field indicates which has is to be used and changes what the other fields mean.

In this current example, I do not know the exact requirements for the salt, for instance. I experimented and found that openssl rand -base64 15 produces a value that crypt(3) grokked.

(Incidentally, password verification is made easy, because you can just compare the result of crypt(3) with the password hash as stored.)

The yescrypt hashing method takes a cost factor. This can be specified in /etc/login.defs under the YESCRYPT_COST_FACTOR key. Unlike the KDF rounds used with the sha256crypt and sha512crypt methods, which increases complexity linearly, increasing the cost factor of yescrypt will lead to an exponential increase in memory usage and slow the password comparison down significantly. You might want to experiment on your system and see what the highest setting is you still find bearable. Although you might not want to wait 5 seconds each time you log in or want to use sudo, keep in mind that it will take somebody that cracks these the same amount of resources for each guess.

See yescrypt parameters (especially the N parameter which is influenced by the YESCRYPT_COST_FACTOR key) and a description of them on the Unix & Linux Stack Exchange.