如何在Golang pbkdf 2中模拟pbkdf 2-scala的密码散列

rxztt3cl  于 2023-01-30  发布在  Scala
关注(0)|答案(2)|浏览(207)

我们的应用使用库SecureHash对象来创建单向密码:https://github.com/nremond/pbkdf2-scala/blob/master/src/main/scala/io/github/nremond/SecureHash.scala
现在我的问题是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)
}
ee7vknir

ee7vknir1#

你有几个问题:

  • 在将salt传递给pbkdf2.Key()之前,你并没有对salt进行base64解码;在将从数据库中获取的密钥与pbkdf2.Key()的结果进行比较之前,你也没有对从数据库中获取的密钥进行base64解码;还要注意的是,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

rkttyhzu

rkttyhzu2#

验证失败,原因如下:

  • 待验证的散列X1 M0 N1 X具有32字节的长度,而计算的散列X1 M1 N1 X,具有64字节的长度,
  • 在计算散列之前不对盐进行Base64解码,
  • 当比较时,待验证的散列是Base64编码的,而计算的散列是原始的。

可能的修复方法是:

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

虽然此代码适用于此特定令牌,但通常修改后的Base64应用于.而非+(因此,如果存在.,则必须在Base64解码之前将其替换为+)且不使用填充(后者是在上述代码片段中使用base64.RawStdEncoding的原因)。
注意,Go语言(passlib package)有一个 passlib 实现,但它似乎使用了默认值(例如pbkdf 2-sha 512的输出大小为64字节),因此不能直接在这里应用。
然而,此实现可以用作您自己实现的蓝图(例如,关于Base64编码、s. Base64Decode()、* 恒定时间比较 *、s. SecureCompare()、防止边信道攻击等)。

相关问题