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 frompbkdf2.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 adkLength
parameter which defaults to32
. In the Go implementation, you're instead providing the result ofsha512.Size
, which is64
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
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.).