Home > Blockchain >  How to simulate password hash of pbkdf2-scala in Golang pbkdf2
How to simulate password hash of pbkdf2-scala in Golang pbkdf2

Time:01-29

Our app uses the library, SecureHash object, in order to create one-way password: https://github.com/nremond/pbkdf2-scala/blob/master/src/main/scala/io/github/nremond/SecureHash.scala

Now my problem is that my code in Go returns -1 for password check.

package main

import (
    "bytes"
    "crypto/rand"
    "crypto/sha512"
    "fmt"
    "golang.org/x/crypto/pbkdf2"
    "math/big"
    "strings"
)

func main() {
    iteration := 25000

    // Hash User input
    password := []byte("123")
    salt := "yCyQMMMBt1TuPa1F9FeKfT0yrNIF8tLB"
    key := pbkdf2.Key(password, []byte(salt), iteration, sha512.Size, sha512.New)

    // COMPARE PASSWORD fetched from DB
        // 123 hash in scala
    tokenS := strings.Split("$pbkdf2-sha512$25000$yCyQMMMBt1TuPa1F9FeKfT0yrNIF8tLB$TtQt5BZLs4qlA0YAkcGukZwu7pkxOLcxwuoQB3qNtxM", "$")
    passwordHashInDatabase := tokenS[4]
    out := bytes.Compare(key, []byte(passwordHashInDatabase))
    fmt.Println("out: ", out)
}

CodePudding user response:

You have a couple problems:

  • You're not base64 decoding the salt before passing it to pbkdf2.Key() and you're not base64 decoding the key fetched from the database before comparing it to the result from pbkdf2.Key(). Also note that the Scala implementation does some character replacement before/after base64 decoding/encoding. This too needs to be replicated in the Go implementation.

  • The createHash() method in the Scala implementation has a dkLength parameter which defaults to 32. In the Go implementation, you're instead providing the result of sha512.Size, which is 64 and does not match. I know that the default value was used because the value from the database is 32 bytes long.

Here's a hastily fixed implemnentation:

package main

import (
    "bytes"
    "crypto/sha512"
    "encoding/base64"
    "fmt"
    "log"
    "strings"

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

func b64Decode(s string) ([]byte, error) {
    s = strings.ReplaceAll(s, ".", " ")
    return base64.RawStdEncoding.DecodeString(s)
}

func main() {
    iteration := 25000

    // Hash User input
    password := []byte("123")
    salt, err := b64Decode("yCyQMMMBt1TuPa1F9FeKfT0yrNIF8tLB")
    if err != nil {
        log.Fatal("Failed to base64 decode the salt: %s", err)
    }
    key := pbkdf2.Key(password, salt, iteration, 32, sha512.New)

    // COMPARE PASSWORD fetched from DB
    // 123 hash in scala
    tokens := strings.Split("$pbkdf2-sha512$25000$yCyQMMMBt1TuPa1F9FeKfT0yrNIF8tLB$TtQt5BZLs4qlA0YAkcGukZwu7pkxOLcxwuoQB3qNtxM", "$")
    passwordHashInDatabase, err := b64Decode(tokens[4])
    if err != nil {
        log.Fatal("Failed to base64 decode password hash from the database: %s", err)
    }
    fmt.Printf("%x\n%x\n", key, passwordHashInDatabase)
    fmt.Printf("%d\n%d\n", len(key), len(passwordHashInDatabase))
    out := bytes.Compare(key, passwordHashInDatabase)
    fmt.Println("out: ", out)
}

Output:

4ed42de4164bb38aa503460091c1ae919c2eee993138b731c2ea10077a8db713
4ed42de4164bb38aa503460091c1ae919c2eee993138b731c2ea10077a8db713
32
32
out:  0

Go Playground

CodePudding user response:

Verification fails because:

  • the hash to be verified, tokenS[4], has a length of 32 bytes, while the calculated hash, key, has a length of 64 bytes,
  • the salt is not Base64 decoded before the hash is computed,
  • when comparing, the hash to be verified is Base64 encoded while the calculated hash is raw.

A possible fix is:

iteration := 25000

// Hash User input
password := []byte("123")
salt, _ := base64.RawStdEncoding.DecodeString("yCyQMMMBt1TuPa1F9FeKfT0yrNIF8tLB") // Fix 1: Base64 decode salt (Base64: without padding and with . instead of  )
key := pbkdf2.Key(password, []byte(salt), iteration, sha256.Size, sha512.New)     // Fix 2: Apply an output size of 32 bytes

// COMPARE PASSWORD fetched from DB
// 123 hash in scala
tokenS := strings.Split("$pbkdf2-sha512$25000$yCyQMMMBt1TuPa1F9FeKfT0yrNIF8tLB$TtQt5BZLs4qlA0YAkcGukZwu7pkxOLcxwuoQB3qNtxM", "$")
passwordHashInDatabase, _ := base64.RawStdEncoding.DecodeString(tokenS[4]) // Fix 3: Base64 decode the hash to be verified (Base64: without padding and with . instead of  )
out := bytes.Compare(key, passwordHashInDatabase) // better apply an constant-time comparison
fmt.Println("out: ", out) // 0

Although this code works for this particular token, in general a modified Base64 is applied with . instead of (thus, if . are present, they must be replaced by before Base64 decoding) and without padding (the latter is the reason for using base64.RawStdEncoding in above code snippet).

Note that there is a passlib implementation for Go (passlib package), but it seems to use essentially default values (e.g. an output size of 64 bytes for pbkdf2-sha512) and so cannot be applied directly here.
Nevertheless, this implementation can be used as a blueprint for your own implementation (e.g. regarding Base64 encoding, s. Base64Decode(), constant-time comparison, s. SecureCompare(), preventive against side channel attacks etc.).

  • Related