[Go] Try PBKDF2 implementation
Masui Masanori
Posted on September 30, 2022
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.
I will use the SHA-512 function and the HMAC-SHA512 function what I wrote last time.
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.
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
...
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.
main.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
- SP 800-132 Recommendation for Password-Based Key Derivation: Part 1: Storage Applications
- pbkdf2 package - golang.org/x/crypto - Go Packages
- PasswordHasher.cs - dotnet / aspnetcore - GitHub
- ManagedPbkdf2Provider.cs - dotnet / aspnetcore - GitHub
- RandomNumberGenerator Class(System.Security.Cryptography) - Microsoft Learn
Posted on September 30, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.