[Go] Try PBKDF2 implementation
Tags: go
Published: 2022-09-30 15:27:33

## Intro

This time, I will try implementing a function for PBKDF2.
Last time when I tried ASP.NET Core Identity, I used a PasswordHasher class. 
And it used PBKDF2 to save password.

* [【.NET 5】【ASP.NET Core Identity】SignIn with custom user](https://dev.to/masanori_msl/net-5-asp-net-core-identity-signin-with-custom-user-56fe)

I will use the SHA-512 function and the HMAC-SHA512 function what I wrote last time.

* [[Go] Try SHA-512 implementation](https://dev.to/masanori_msl/go-try-sha-512-implementation-33gd)
* [[Go] Try HMAC-SHA512 implementation](https://dev.to/masanori_msl/go-try-hmac-sha512-implementation-2pij)

##　Using Go package
I can generate PBKDF2 values by "golang.org/x/crypto/pbkdf2".
It needs a salt value.

The PasswordHasher class uses "RandomNumberGenerator" to generate it. 
It generates random numbers to a specified length.

* [RandomNumberGenerator Class(System.Security.Cryptography) - Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.randomnumbergenerator?view=net-6.0)

```go
package main

import (
	"crypto/rand"
	"crypto/sha512"
	"fmt"
	"log"
	"math"
	"math/big"

	"golang.org/x/crypto/pbkdf2"
)

func main() {
	inputData := []byte("hello")
	salt, err := generateRandomSalt(128 / 8)
	if err != nil {
		log.Panicln(err.Error())
	}
	result = ""
	keyPkg := generatePDKDF2Package(inputData, salt, 100_000, 256/8)
	for _, k := range keyPkg {
		result += fmt.Sprintf("%02X", k)
	}
	log.Println(result)
...
}
// Generate a salt value
func generateRandomSalt(length int) ([]byte, error) {
	results := make([]byte, length)
	for i := 0; i < length; i++ {
		salt, err := rand.Int(rand.Reader, big.NewInt(255))
		if err != nil {
			return nil, err
		}
		results[i] = byte(salt.Int64())
	}
	return results, nil
}
...
func generatePDKDF2Package(password []byte, salt []byte, iterateCount int, keyLength int) []byte {
	return pbkdf2.Key(password, salt, iterateCount, keyLength, sha512.New)
}
```
#### Results
```
58A07933EA993981DBF1856FCCB708B522B0B0E6C8F192C3093307337E9CC747
```

## Writing own function
```go
...
func main() {
	inputData := []byte("hello")
	salt, err := generateRandomSalt(128 / 8)
	if err != nil {
		log.Panicln(err.Error())
	}
...
	result = ""
	keyPkg := generatePDKDF2Package(inputData, salt, 100_000, 256/8)
	for _, k := range keyPkg {
		result += fmt.Sprintf("%02X", k)
	}
	log.Println(result)

}
...
func generatePDKDF2(password []byte, salt []byte, iterateCount int, keyLength int) []byte {
	hashLength := sha512.Size
	// 1. dkLen > (2^32 - 1)
	if keyLength > ((int(math.Exp2(32)) - 1) * hashLength) {
		log.Println("derived key too long")
		return nil
	}
	// 2. Block size
	// l = CEIL (dkLen / hLen)
	blockCount := int(math.Ceil(float64(keyLength) / float64(hashLength)))
	// r = dkLen - (l - 1) * hLen
	//	r := keyLength - (blockCount-1)*hashLength

	dk := make([]byte, hashLength*blockCount)
	s := make([]byte, len(salt)+4)
	copy(s, salt)

	// 3. T_1 = F (P, S, c, 1) , 〜 T_l = F (P, S, c, l) ,
	for blockIndex := 1; blockIndex <= blockCount; blockIndex++ {
		// S || INT (i)
		s[len(s)-4] = byte(blockIndex >> 24)
		s[len(s)-3] = byte(blockIndex >> 16)
		s[len(s)-2] = byte(blockIndex >> 8)
		s[len(s)-1] = byte(blockIndex)
		// U_1 = PRF (P, S || INT (i))
		u := HashHMACSHA512(password, s)
		// 4. DK = T_1 || T_2 ||  ...  || T_l<0..r-1>
		if blockIndex > 1 {
			dk = append(dk, u[:]...)
		} else {
			dk = u[:]
		}
		// F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
		for ci := 1; ci < iterateCount; ci++ {
			u = HashHMACSHA512(password, u)
			for ui := range u {
				dk[ui+(hashLength*(blockIndex-1))] ^= u[ui]
			}
		}
	}
	return dk[:keyLength]
}
...
```

#### Results
```
58A07933EA993981DBF1856FCCB708B522B0B0E6C8F192C3093307337E9CC747
```

## Adding the salt value and the iteration count to generate the hash value
The PasswordHasher class generate hash values to save user passwords.
It must generate the same hash value each time from the same password for authentication.

Thus, it needs adding the salt value and the iteration count into the hashed password.

According to the "PasswordHasher.cs", I should add them in front of the hashed password.

* [PasswordHasher.cs - dotnet / aspnetcore - GitHub](https://github.com/dotnet/AspNetCore/blob/main/src/Identity/Extensions.Core/src/PasswordHasher.cs)

#### main.go
```go
package main

import (
	"crypto/rand"
	"crypto/sha512"
	"encoding/base64"
	"fmt"
	"log"
	"math"
	"math/big"

	"golang.org/x/crypto/pbkdf2"
)
func main() {
...
	keySelf := generatePDKDF2(inputData, salt, 100_000, 256/8)
...
	// Add the salt value and the iteration count
	outputBytes := make([]byte, len(keySelf)+len(salt)+13)
	outputBytes[0] = 0x01
	// In ASP.NET Core Identity, the value of "KeyDerivationPrf.HMACSHA512" is "2"
	writeNetworkByteOrder(outputBytes, 1, 2)
	writeNetworkByteOrder(outputBytes, 5, 100_000)
	writeNetworkByteOrder(outputBytes, 9, uint(len(salt)))
	blockCopy(salt, outputBytes, 13, len(salt))
	blockCopy(keySelf, outputBytes, 13+len(salt), len(keySelf))

	// Base64 encoding
	output := base64.StdEncoding.EncodeToString(outputBytes)
	log.Printf("Result : %s", output)
}
...
func writeNetworkByteOrder(buffer []byte, offset int, value uint) {
	buffer[offset+0] = byte(value >> 24)
	buffer[offset+1] = byte(value >> 16)
	buffer[offset+2] = byte(value >> 8)
	buffer[offset+3] = byte(value >> 0)
}
func blockCopy(src []byte, dst []byte, offset int, copyLength int) {
	index := offset
	for i := 0; i < copyLength; i++ {
		dst[index] = src[i]
		index += 1
	}
}
```

#### Result
```
AQAAAAIAAYagAAAAEEZHtMLiF5yoYHif+AJgoy5QqO2CTfdOzoF7jkOM+omSShyzooRnwl1soh0xzLpbxg==
```

## Resources
* [PKCS #5: Password-Based Cryptography Specification Version 2.1](https://www.rfc-editor.org/rfc/rfc8018)
* [SP 800-132 Recommendation for Password-Based Key Derivation: Part 1: Storage Applications](https://csrc.nist.gov/publications/detail/sp/800-132/final)
* [pbkdf2 package - golang.org/x/crypto - Go Packages](https://pkg.go.dev/golang.org/x/crypto/pbkdf2)
* [PasswordHasher.cs - dotnet / aspnetcore - GitHub](https://github.com/dotnet/AspNetCore/blob/main/src/Identity/Extensions.Core/src/PasswordHasher.cs)
* [ManagedPbkdf2Provider.cs -  dotnet / aspnetcore - GitHub](https://github.com/dotnet/aspnetcore/blob/main/src/DataProtection/Cryptography.KeyDerivation/src/PBKDF2/ManagedPbkdf2Provider.cs)
* [RandomNumberGenerator Class(System.Security.Cryptography) - Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.randomnumbergenerator?view=net-6.0)