简体   繁体   中英

How to simulate password hash of pbkdf2-scala in Golang pbkdf2

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)
}

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 (eg 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 (eg regarding Base64 encoding, s. Base64Decode() , constant-time comparison , s. SecureCompare() , preventive against side channel attacks etc.).

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM