Crypt For Passwords

Let’s talk about password hashing. Whenever I review code, I’m always surpised to see that many developers still hash secrets with SHA-1 or…

Photo by Mauro Sbicego on Unsplash

Crypt For Passwords

Let’s talk about password hashing. Whenever I review code, I’m always surpised to see that many developers still hash secrets with SHA-1 or SHA-256 to generate encryption keys or to store passwords. Overall, these hashing methods are fast, and are thus prone to brute force analysis with GPU cracking.

In OpenSSL, we have support for a number of hashed password options [here]:

In Linux, there is support for a range legacy hashing, such as with SHA-256 crypt, HMAC-SHA1, MD5 crypt, BSDi crypt and DES crypt, but for new passwords, we should use either bcrypt or SHA-512 crypt. So let’s go from worst to best.

DES crypt

DES crypt was the first password hashing method and used a modified version of the DES symmetric key method. Unfortunately, the password was truncated to eight characters, and then further reduced to 7-bits per character. This gives a 56-bit DES key (and which forms the basis of 64-bit DES). This key is then used to encrypt a data block with all zeros. Next, we go though 25 DES encryption rounds with the same key. A 12-bit salt value is also used modify the output of the rounds — this is only two characters of salt (and where the last four bits are ignored).

These days, crypt has been removed in many applications, as it can be easily cracked (as with the DES encryption method — due to its relatively small encryption key). Now let’s try for “Hello” and a salt value of “241fa86763b85341:

openssl passwd -crypt -s 241fa86763b85341 Hello
Word: Hello
Method: -crypt
Salt: 241fa86763b85341
24dw4/Mam3sLc

If we try “111111111”, we get:

openssl passwd -crypt -s 111111111 Hello
Word: Hello
Method: -crypt
Salt: 111111111
11GMhubChLGxM

and “123456”:

openssl passwd -crypt -s 123456 Hello
Word: Hello
Method: -crypt
Salt: 123456
12AuwdSPXuQnc

What we see is that most of the salt is ignored, and where only the first two characters are used for the salt value. This can be tested with:

openssl passwd -crypt -s 12 Hello
Word: Hello
Method: -crypt
Salt: 12
12AuwdSPXuQnc

And which gives the same hashed value. We can thus see why crypt should not be used, and which has been deprecated by OpenSSL 3.0.

BSDi crypt

BSDi is based on des_crypt, but uses a larger salt value and has a variable number of rounds. A sample is:

Password: hello123
BSDI Crypt: _Gl/.00S/WNrZl.GgeM.

MD5 crypt

Once hashing methods were developed, the natural focus became the usage of the MD5 hashing method. For this Poul-Henning Kamp created a method which allowed any length of the password to be converted into a hashed password. In OpenSSL, we use the “-1” option [here]:

openssl passwd -1 -s 1234567890 Hello
Word: Hello
Method: -1
Salt: 1234567890
$1$12345678$pXsdVOIgvj7gmTb3pUCW9/

We can see in this case, that we now have the “$” symbols to break up the hash. The value of “$1$” identifies the MD5 method, “12345678” defines the salt value used, and the rest is the hash (“pXsdVOIgvj7gmTb3pUCW9/”). We can see that using “12345678” produces the same hashed value:

openssl passwd -1 -s 12345678 Hello
Word: Hello
Method: -1
Salt: 12345678
$1$12345678$pXsdVOIgvj7gmTb3pUCW9/

Using “1234567”, we get a different hash function:

openssl passwd -1 -s 1234567 Hello
Word: Hello
Method: -1
Salt: 1234567
$1$1234567$AwLojMJlUGmX9.4tqa1Ws0

MD5 crypt was a replacement for DES crypt, and is an improvement but is seen to be insecure. It uses a salt value with up to eight characters and has a fixed round of 1,000. In order to store the hashed password, we have fields defined between “\$” symbols. A sample hash issample hash is “\$1\$Y6tL9oGA$FoYF9naVmbEd29gU915J00”, and which has the fields of:

  • Type: 1
  • Salt: Y6tL9oGA
  • Hash: FoYF9naVmbEd29gU915J00

We can test with OpenSSL for a password of “hello123”:

% openssl passwd -1 -salt Y6tL9oGA hello123
$1$Y6tL9oGA$FoYF9naVmbEd29gU915J00

But, we shouldn’t use this version, as it is fairly easy to crack with MD5, as it is a fast hashing method. What we need is a slow method of generating the hashed value, and one of the main methods is to use a number of rounds. This creates a sequential operation, and which will disable any kind of parallel processing operation — such as with the use of GPUs.

An enhancement is the APR1 function:

openssl passwd -apr1 -s 12345678 Hello
Word: Hello
Method: -apr1
Salt: 12345678
$apr1$12345678$lKtjSygR2codplxge95Ih/

This gives us 48 bits of salt (as we use a Base-64 format for the salt — and where each Base-64 character represents six bits) and uses 1,000 rounds. We thus take the input and the salt value and hash it with MD5 for 1,000 rounds to get the result. This breaks GPU cracking, as we cannot apply parallel processing methods. The salt value is still fairly small, and the MD5 hashing method is flawed.

SHA-1 crypt

With HMAC we have a keyed hash, and where we can apply an encryption key to the hashed version. In this case, we use the SHA-1 hashing method, and then apply an encryption key based on the input data. For this we can add a variable number of rounds.

For SHA-1 crypt, a sample hash for “Hello123” with a salt of “gY6FhauW” is: “$sha1$20498$gY6FhauW\$uIkSNXp8Xw9hS6f4UJKM1G9LvBxX”, we get:

  • Type: sha1
  • Rounds: 20498
  • Salt: gY6FhauW (8 bytes)
  • Hash: uIkSNXp8Xw9hS6f4UJKM1G9LvBxX

The hash is 160 bits long.

SHA-256 crypt

With SHA-256 crypt, a sample hash is “$5$hqmQOhPafzx4Ld9\NvyxMsyM8HJziXuoaBlpQtz.f2IkfbOYvLBVaYK7SfC”, we get:

  • Type: 5 (SHA-256 crypt)
  • Salt: /hqmQOhPafzx4Ld9 (16 Base-64 characters — 96 bits)
  • Hash: NvyxMsyM8HJziXuoaBlpQtz.f2IkfbOYvLBVaYK7SfC (256 bits)

The hash output is 256 bits long.

bcrypt

Niels Provos and David Mazières created in bcrypt hashing method, and is bawe on the Blowfish cipher. Overall, Blowfish is fast, but is significantly slower when the key is changed. This key changing provides a signficant performance cost, and which is an advantage in password hashing. Again, we have a number of rounds for the hashing. For bcrypt, a sample hash is: “$2b$10$ralFpkUktJLdXmyN.XP0YOFqUw4hw6IeIRTV8f0UVdv.QABffgvGW”, and which has the fields of:

  • Type: 2b (bcrypt)
  • Cost: 10 (2¹⁰= 1,024 rounds)
  • Salt: ralFpkUktJLdXmyN.XP0YO (128-bit value)
  • Hash: FqUw4hw6IeIRTV8f0UVdv.QABffgvGW

Thus rather than rounds, we have a cost value. As we see below, we roughly double the time to create a hash for every increment of the cost. In this case, the time to create a hash for a cost of 12 is 0.266 seconds, and for 13 it is 0.54 seconds. This is because an extra bit will double the amount of rounds — and making it twice as costly to crack a hash with brute force.

Figure [here]

SHA-512 crypt

These days, the most commonly used hashed password is SHA-512 crypt. For this we have a $rounds=X$ field. If this is omitted the number of rounds is 5,000. For “SHA-512 Crypt, a sample hash is: “$6$q5D7B8Z9tRWLWuas$8WNTuTtm5HxfMmLJ0RMiocrLcBcOQgfHsbaRKPA6AswjB1LKGpy3f0KsgZmk1ix62sdEZ5MPG7DfJnPk76gdM”

  • Type: 6 (SHA-512 crypt)
  • Salt: q5D7B8Z9tRWLWuas (8 bytes)
  • Hash: 8WNTuTtm5HxfMmLJ0RMiocrLcBcOQgfHsbaRKPA6AswjB1LKGpy3f0KsgZmk1ix62sdEZ5MPG7DfJnPk76gdM

The hash value is 512 bits long.

On Linux, each of the users are then stored in the passwd file in the etc folder, so for a user “csn01” we have:

:csn01:$6$Uk8SVGLsBuSmD75R$Lhp5yjwRUAM.LbH5IIthZ1u0bAUdJwBvvccBshAvpFPiRn62EYeiKOaP8xh97aV4UaNfVykRZhUy/3ZOZd1oc.:::user::::18161

So what is the method used for hashing the password? Well, we split the hashed password into three main groups (separated by the “$” symbol):

6
Uk8SVGLsBuSmD75R
Lhp5yjwRUAM.LbH5IIthZ1u0bAUdJwBvvccBshAvpFPiRn62EYeiKOaP8xh97aV4UaNfVykRZhUy/3ZOZd1oc.

and where “6” is the hashing method (SHA-512), “Uk8SVGLsBuSmD75R” is the salt value, and “Lhp5yjwRUA..d1oc.” is the hashed version of the password using 5,000 rounds. When the user logs into Splunk, their password will be added to the salt value, and the same hashed version should be created. Well, the “$6” part identifies that it is SHA-512, but when I try to hash with SHA-512, it gives the wrong hashed value. The answer lies in slowing the hashing process down by performing a number of rounds. For this 5,000 rounds works to give the right result, and so here is the Python code [here]:

import passlib.hash;
import sys;
password="qwerty123"
salt="Uk8SVGLsBuSmD75R"
# csn01:$6$Uk8SVGLsBuSmD75R$ Lhp5yjwRUAM.LbH5IIth
user="fred"
h=passlib.hash.sha512_crypt.hash(password, salt=salt,rounds=5000)
# Fred:$6$Uk8SVGLsBuSmD75R$Lhp5yjwRUAM.LbH5IIthZ1u0bAUdJwBvvccBshAvpFPiRn62EYeiKOaP8xh97aV4UaNfVykRZhUy/3ZOZd1oc.
hash=user+":"+h
print (hash)

A sample run:

===Splunk hashed password===
User: Fred
Password: qwerty123
Salt: Uk8SVGLsBuSmD75R
===Hashed password
Fred:$6$Uk8SVGLsBuSmD75R$Lhp5yjwRUAM.LbH5IIthZ1u0bAUdJwBvvccBshAvpFPiRn62EYeiKOaP8xh97aV4UaNfVykRZhUy/3ZOZd1oc.

and this matches the Splunk entry. With hashcat, the default is also 5,000 rounds. If we try, with the program on this page we also get the same result:

openssl passwd -6 -s Uk8SVGLsBuSmD75R qwerty123
Word: qwerty123
Method: -6
Salt: Uk8SVGLsBuSmD75R
$6$Uk8SVGLsBuSmD75R$Lhp5yjwRUAM.LbH5IIthZ1u0bAUdJwBvvccBshAvpFPiRn62EYeiKOaP8xh97aV4UaNfVykRZhUy/3ZOZd1oc.

It should be see that the salt value is defined in a Base 64 format. If we now use Hashcat on the hashed version, we should be able to discover the original password:

root@kali:~# hashcat -m 1800 1.txt -a 0 /usr/share/wordlists/rockyou.txt
Initializing hashcat v0.49 with 1 threads and 32mb segment-size...
Added hashes from file 1.txt: 1 (1 salts)
Activating quick-digest mode for single-hash with salt
NOTE: press enter for status-screen
$6$Uk8SVGLsBuSmD75R$Lhp5yjwRUAM.LbH5IIthZ1u0bAUdJwBvvccBshAvpFPiRn62EYeiKOaP8xh97aV4UaNfVykRZhUy/3ZOZd1oc.:qwerty123
All hashes have been recovered
Input.Mode: Dict (/usr/share/wordlists/rockyou.txt)
Index.....: 1/5 (segment), 3627172 (words), 33550339 (bytes)
Recovered.: 1/1 hashes, 1/1 salts
Speed/sec.: - plains, 461 words
Progress..: 2172/3627172 (0.06%)
Running...: 00:00:00:05
Estimated.: 00:02:11:03

In this case, we use the rockyou.txt list of common passwords, and where it only takes five seconds to find the password. The advantage of using SHA-512 with a number of rounds is highlighted when we run a benchmark:

root@kali:~# hashcat -b -m 1800
Initializing hashcat v0.49 with 1 threads and 32mb segment-size…
Device………..: Intel(R) Core(TM) i7–8850H CPU 2.60GHz
Instruction set..: x86_64
Number of threads: 1
Hash type: sha512crypt, SHA512(Unix)
Speed/sec: 454 words
root@kali:~# hashcat -b -m 0
Initializing hashcat v0.49 with 1 threads and 32mb segment-size…
Device: Intel(R) Core(TM) i7–8850H CPU 2.60GHz
Instruction set..: x86_64
Number of threads: 1
Hash type: MD5
Speed/sec: 17.33M words

In this, hashcat can process 454 words per second with SHA512crypt and over 17 million per second with MD5.

Rust code

An outline of the Rust code is [here]:

extern crate pwhash;
use pwhash::{bcrypt,md5_crypt,sha1_crypt,sha256_crypt,sha512_crypt,bsdi_crypt,unix_crypt};
use std::env;
fn main() {

let mut password="password";
let args: Vec<String> = env::args().collect();

if args.len() >1 { password = args[1].as_str();}
println!("Password:\t{:}",password);
let mut h_new = bcrypt::hash(password).unwrap();
println!("\nBcrypt:\t\t{:}",h_new);
h_new = bsdi_crypt::hash(password).unwrap();
println!("BSDI Crypt:\t{:}",h_new);
h_new = md5_crypt::hash(password).unwrap();
println!("MD5 Crypt:\t{:}",h_new);
h_new = sha1_crypt::hash(password).unwrap();
println!("SHA1 Crypt:\t{:}",h_new);
h_new = sha256_crypt::hash(password).unwrap();
println!("SHA-256 Crypt:\t{:}",h_new);

h_new = unix_crypt::hash(password).unwrap();
println!("Unix crypt:\t{:}",h_new);
// let rtn=bcrypt::verify(password, h);
// println!("{:?}",rtn);
}

Finally we simply build with:

cargo build

A sample run is [here]:

Password:	hello123
Bcrypt: $2b$10$1gZgrJFeD8CclpR3UJ1AsuStQcvNCW7l6qX2cK4h26WB0znfA44fe
BSDI Crypt: _Gl/.00S/WNrZl.GgeM.
MD5 Crypt: $1$Y6tL9oGA$FoYF9naVmbEd29gU915J00
SHA1 Crypt: $sha1$20498$gY6FhauW$uIkSNXp8Xw9hS6f4UJKM1G9LvBxX
SHA-256 Crypt: $5$/hqmQOhPafzx4Ld9$NvyxMsyM8HJziXuoaBlpQtz.f2IkfbOYvLBVaYK7SfC
SHA-512 Crypt: $6$q5D7B8Z9tRWLWuas$8WNTuTtm5HxfMmLJ0RMiocrLcBcOQgfHsbaRKPA6AswjB1LKGpy3f0KsgZmk1ix62sdEZ5MPG7DfJnPk76gdM/
Unix crypt: mUsMoOrweXet6

Other contenders

The two main contenders to bcrypt and SHA-512 are Argon 2 and PBKDF2. Argon 2 was designed Alex Biryukov, Daniel Dinu, and Dmitry Khovratovich, and is a key derivation function (KDF), where were we can create hashed values of passwords, or create encryption keys based on a password. It was a winner of the Password Hashing Competition in July 2015, and is robust against GPU and side channel attacks:

https://asecuritysite.com/rust/rust_argon

PBKDF2 (Password-Based Key Derivation Function 2) is defined in RFC 2898 and generates a salted hash. Often this is used to create an encryption key from a defined password, and where it is not possible to reverse the password from the hashed value. It is used in TrueCrypt to generate the key required to read the header information of the encrypted drive, and which stores the encryption keys:

https://asecuritysite.com/rust/rust_pbkdf2

Conclusions

And, so, Linux loves SHA-512, but Windows and WPA2 uses PBKDF2. I see many new applications using Argon 2, and which properly protects against GPU cracking. Overall, they provide excellent protection on passwords. But, perhaps, some day, we will not need passwords anymore.