Symmetric Key Encryption With PBKDF2 (and Node.js)

A recent paper from researchers at the University of Bonn found that many developers struggled with the concept of properly protecting a…

Photo by olieman.eth on Unsplash

Symmetric Key Encryption With PBKDF2 (and Node.js)

A recent paper from researchers at the University of Bonn found that many developers struggled with the concept of properly protecting a password [here]:

The research involved asking 260 Java developers from Freelance.com to write code which will safely store passwords. In the end, 43 of them took up the work. The given task was to setup a user registration system for a fake social media site. Of the resultant split, 39 were male, and one female, and the others did not define their gender. 37 had university degrees and had an average experience of 6.4 years of Java coding:

In order to assess whether being paid more for the task resulted in improved security, around half of the group were paid €100 and the rest received €200. Then they split each of these groups into whether they were prompted to use a given methods or not:

  • Use defined password storage method (P100) and paid €100.
  • Use defined password storage method (P200) and paid €200.
  • Not defined the password storage method (N200) and paid €200.
  • Not defined the password storage method (P200) and paid €200.

Generally it took developers around three days to submit their work, and 18 of them had to resubmit their code as the had used plaintext passwords. The reason that 15 of them gave was that there was no requirement given in the specification for secure password storage, and the other three just resubmitted with the same plaintext password.

In the end the results were poor from a security point-of-view, with many developers using encoding formats (Base64), weak hashes (MD5 and SHA-1) and weak encryption of passwords (AES and 3DES):

The worrying thing is that nearly one-in-five selected a method which is not even a hashing method: Base64. The security in Base64 is zero, as it is an encoding method. There was often confusion between the hashing of a password and in encoding them in a scrambled form. Of those who used Base64, the comments included:

encrypted it so the clear password is not visible

and:

It is very tough to decrypt

Overall the researchers found that the developers often didn’t know the difference between hashing and encryption. When it came to salting the password, only 15 out of 43 selected salting, and that 17 out of the 43 had just copied the code from other sites. The researchers, too, found that paying the developers more didn’t lead to better code and that they often had to be told which methods were actually to be implemented. The study also highlighted that the knowledge of encryption and hashing were generally poor, with many unable to differentiate the two of them.

So let’s protect our password

One way we can protect our password is to encrypt it, and use a slow hashing method, such as PBKDF2. You will find the PBKDF2 is the method used to protect your password on your wi-fi system. So let’s use Node.js to encrypt using a range of encryption algorithms, and then generate an encryption key based on a password, a salt value, and a given hashing method:

To generate the key in this case, we use a call back function, and where the PBKDF2 function is called, and then when complete it calls back the code defined between the brackets:

crypto.pbkdf2(password, salt, 100, keysize, hash, (err, key) => {
...
}

In this case, we pass a password, a salt value, and a hashing method. Some typical hashing methods we can use include:

And so for a password of “qwerty”, a hashing method of “SHA-1”, and a salt value of “salt123”, we derive the following 16 byte key:

Password:  qwerty
Hashing: sha1
Salt: salt123
Derived Key:  8b8c9613f705303540f4f52cd4393b0f

The size of the key will obviously depend on the encryption method we want to use, so a 128-bit key requires 16 bytes, and a 256-bit key requires 32 bytes. This value of the keysize is passed into the function:

crypto.pbkdf2(password, salt, 100, keysize, hash, (err, key) => {
...
}

And now we need to pick our encryption method. In most cases we will use either AES or ChaCha20, but there are many to choose from:

For this, in Node.js, we have a tag for each method, such as “aes-256-cbc” for 256-bit AES with CBC mode. So, let’s give it a try:

https://asecuritysite.com/node/node_encrypt2

For 128-bit AES-CTR:

Message:  Test
Encryption Algorithm:  aes-128-ctr
IV: d8e6e5de3d11ab945595c2297a406ee4
Password:  qwerty
Hashing: sha1
Salt: salt123
Derived Key:  8b8c9613f705303540f4f52cd4393b0f
Encrypted:  74247bc0
Encrypted: dCR7wA==
Decrypted:  Test

And for 256-bit AES OFB:

Message:  Test
Encryption Algorithm:  aria-256-ofb
IV: 4f7f8ccd91279504377745fa10cbdfc9
Password:  qwerty
Hashing: sha1
Salt: salt123
Derived Key:  8b8c9613f705303540f4f52cd4393b0f9298aee6eadc3c41960b0301b019aca3
Encrypted:  a4be0b31
Encrypted: pL4LMQ==
Decrypted:  Test

For the encryption and decryption part, we basically encryption a string (message) and then decrypt the ciphertext with:

const cipher = crypto.createCipheriv(algorithm,  Buffer.from(key),iv);

let encrypted = cipher.update(message);

encrypted = Buffer.concat([encrypted, cipher.final()]);

const decipher = crypto.createDecipheriv(algorithm, Buffer.from(key),iv);

Notice that the encryption key is twice the size for the 256-bit version as apposed to the 128-bit key size.