DEV Community

Masui Masanori
Masui Masanori

Posted on

[Go] Try PBKDF2 implementation

#go

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)
}
Enter fullscreen mode Exit fullscreen mode

Results

58A07933EA993981DBF1856FCCB708B522B0B0E6C8F192C3093307337E9CC747
Enter fullscreen mode Exit fullscreen mode

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]
}
...
Enter fullscreen mode Exit fullscreen mode

Results

58A07933EA993981DBF1856FCCB708B522B0B0E6C8F192C3093307337E9CC747
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

AQAAAAIAAYagAAAAEEZHtMLiF5yoYHif+AJgoy5QqO2CTfdOzoF7jkOM+omSShyzooRnwl1soh0xzLpbxg==
Enter fullscreen mode Exit fullscreen mode

Resources

Top comments (0)