英文:
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
英文:
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论