如何在Golang中模拟pbkdf2-scala的密码哈希?

huangapple go评论77阅读模式
英文:

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

问题

我们的应用程序使用SecureHash对象库来创建单向密码。现在我的问题是,我的Go代码在密码检查时返回-1。

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

以上是您提供的代码。

英文:

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

答案1

得分: 1

验证失败的原因是:

  • 要验证的哈希值 tokenS[4] 的长度为32字节,而计算得到的哈希值 key 的长度为64字节,
  • 在计算哈希值之前,盐值没有进行Base64解码,
  • 在比较时,要验证的哈希值是Base64编码的,而计算得到的哈希值是原始值。

可能的修复方法如下:

iteration := 25000

// 哈希用户输入
password := []byte("123")
salt, _ := base64.RawStdEncoding.DecodeString("yCyQMMMBt1TuPa1F9FeKfT0yrNIF8tLB") // 修复1:Base64解码盐值(Base64:去除填充并将+替换为.)
key := pbkdf2.Key(password, []byte(salt), iteration, sha256.Size, sha512.New)     // 修复2:将输出大小设置为32字节

// 从数据库中获取密码进行比较
// 在Scala中的123哈希值
tokenS := strings.Split("$pbkdf2-sha512$25000$yCyQMMMBt1TuPa1F9FeKfT0yrNIF8tLB$TtQt5BZLs4qlA0YAkcGukZwu7pkxOLcxwuoQB3qNtxM", "$")
passwordHashInDatabase, _ := base64.RawStdEncoding.DecodeString(tokenS[4]) // 修复3:Base64解码要验证的哈希值(Base64:去除填充并将+替换为.)
out := bytes.Compare(key, passwordHashInDatabase) // 最好使用恒定时间比较
fmt.Println("out: ", out) // 0

尽管这段代码适用于特定的令牌,但通常会应用修改过的Base64,使用.代替+(因此,如果存在.,在Base64解码之前必须将其替换为+),并且不使用填充(这是在上述代码片段中使用base64.RawStdEncoding的原因)。

请注意,Go语言中有一个passlib实现(passlib),但它似乎基本上使用默认值(例如,对于pbkdf2-sha512,输出大小为64字节),因此不能直接应用于此处。
然而,这个实现可以作为你自己实现的蓝图(例如,关于Base64编码,参见Base64Decode()恒定时间比较,参见SecureCompare(),预防侧信道攻击等)。

英文:

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

答案2

得分: 1

你有几个问题:

  • 在将盐传递给pbkdf2.Key()之前,你没有对盐进行base64解码,也没有对从数据库获取的密钥进行base64解码,然后将其与pbkdf2.Key()的结果进行比较。此外,请注意,Scala实现在base64解码/编码之前/之后进行了一些字符替换。在Go实现中也需要复制这一点。

  • Scala实现中的createHash()方法有一个dkLength参数,默认为32。在Go实现中,你提供的是sha512.Size的结果,该结果为64,与之不匹配。我知道默认值被使用,因为数据库中的值长度为32字节。

这是一个匆忙修复的实现:

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

输出:

4ed42de4164bb38aa503460091c1ae919c2eee993138b731c2ea10077a8db713
4ed42de4164bb38aa503460091c1ae919c2eee993138b731c2ea10077a8db713
32
32
out:  0

Go Playground

英文:

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

huangapple
  • 本文由 发表于 2023年1月29日 15:21:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/75273219.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定