Password Hashing and Verification with KDF in Go

12 Dec 2019

Great attention must be paid when doing the storage of password authentication information. Any incautious behavior may lead to the leak of password.

KDF (key derivation function) could derive secret keys with different length from the password, so it is quite suitable for password hashing and verification.

There are several KDF algorithms commonly used for password hashing, like bcrypt, scrypt, Argon2, PBKDF2, etc. According to the password hashing competition from 2013 to 2015, Argon2 was selected as the winner. Therefore, Argon2 is currently the most recommended algorithm for password hashing.

Do it in Go

Go provides the cryptography package golang.org/x/crypto which includes some common KDFs:

KDFCrypt package

It is not very convenient to use golang.org/x/crypto directly. Some extra works are needed, like generation of random salt, preserving salt and parameters, etc.

KDFCrypt package is thus created to simplify the process of password hashing and verification.

KDFCrypt provides the common interface for various KDFs to do password hashing and verification. Here is a simple example:

package main

import (
	"fmt"

	"github.com/xianghuzhao/kdfcrypt"
)

func main() {
	encoded, _ := kdfcrypt.Encode("password", &kdfcrypt.Option{
		Algorithm:        "argon2id",
		Param:            "m=65536,t=1,p=4",
		RandomSaltLength: 16,
		HashLength:       32,
	})

	// $argon2id$v=19,m=65536,t=1,p=4$mD+rvcR+6nuAV6MJFOmDjw$IqfwTPk9RMGeOv4pCE1QiURuSoi655GUVjcQAk81eXM
	fmt.Println(encoded)

	match, _ := kdfcrypt.Verify("password", encoded)
	fmt.Println(match) // true
}

KDFCrypt defines two functions Encode and Verify. Encode could generate an encoded string according to the parameters. The encoded string looks like:

$argon2id$v=19,m=4096,t=1,p=1$4ns1ibGJDR6IQufkbT8E/w$WQ2lAwbDhZmZQMCMg74L00OHUFzn/IvbwDaxU6bgIys
$ KDF    $ param             $ salt (base64)        $ hash (base64)

This string contains full information for password verification, which could be safely saved and verified with function Verify.

kdfcrypt.Option

The Option struct is passed as argument for Encode.

  1. Algorithm: Could be one of argon2id, argon2i, scrypt, pbkdf, hkdf.
  2. Param: String for the KDF param. Different items are separated by comma ”,“. The detailed items vary among different KDFs. Check the details in README.
  3. RandomSaltLength: The length for the random salt in byte.
  4. Salt: Salt for the hash. If Salt is not empty, RandomSaltLength will be ignored.
  5. HashLength: The length of the hash result in byte.

Although various algorithms are provided, argon2id is still the most recommended one. The bcrypt is not included, because golang.org/x/crypto/bcrypt does not provide a function for generating keys directly and used as a KDF. I once tried to add bcrypt, but this would make interfaces and data complicated and inconsistent, so I just removed it.

Use key from KDF directly

Sometimes the keys are needed directly, which could also be achieved with kdfcrypt. For details of the functions, please check the GoDoc.

Generation of AES-256 key

AES-256 algorithm needs the key with 32 bytes, and it could be generated like this:

kdf, err := kdfcrypt.CreateKDF("argon2id", "m=4096,t=1,p=1")
salt, err := kdfcrypt.GenerateRandomSalt(16)
aes256Key, err := kdf.Derive("password", salt, 32)