【问题标题】:Golang crypto multiple calls have different responsesGolang 加密多次调用有不同的响应
【发布时间】:2013-05-02 15:37:56
【问题描述】:

我为密码验证库编写的一些 Go 代码有问题。总体思路是提供 2 个函数,Check() 和 New(),它们都提供了密码和 256 位 HMAC 密钥。 Check() 函数还提供了 256 位盐和 256 位哈希,并返回一个布尔值。 New() 函数返回一个新的随机盐,以及它对应的哈希值。这两个函数都依赖于一个辅助函数 hash(),它使用 scrypt 来延长密钥,并完成生成输出哈希的真正工作。

这在我最初编写它时是有效的(事实证明,我有由早期丢失的代码修订生成的工作测试数据)。

我现在遇到的问题是,当提供旧版本代码生成的数据时,Check() 函数似乎可以正常工作,但现在代码自己的 New() 生成的任何数据似乎都失败了函数(两者都使用底层的 hash() 函数)。

我知道,我应该从一开始就让 git 版本控制代码!我现在已经吸取了教训。

我已将函数和问题的快速演示分组到一个 .go 文件中,如下所示,并添加了一些输出以进行调试:

package main

import (
    "code.google.com/p/go.crypto/scrypt"
    "crypto/hmac"
    "crypto/rand"
    "crypto/sha256"
    "crypto/subtle"
    "errors"
    "fmt"
    "io"
)

// Constants for scrypt. See code.google.com/p/go.crypto/scrypt
const (
    KEYLENGTH = 32
    N         = 16384
    R         = 8
    P         = 1
)

// hash takes an HMAC key, a password and a salt (as byte slices)
// scrypt transforms the password and salt, and then HMAC transforms the result.
// Returns the resulting 256 bit hash.
func hash(hmk, pw, s []byte) (h []byte, err error) {
    sch, err := scrypt.Key(pw, s, N, R, P, KEYLENGTH)
    if err != nil {
        return nil, err
    }
    hmh := hmac.New(sha256.New, hmk)
    hmh.Write(sch)
    h = hmh.Sum(nil)
    hmh.Reset() // Probably not necessary
    return h, nil
}

// Check takes an HMAC key, a hash to check, a password and a salt (as byte slices)
// Calls hash().
// Compares the resulting 256 bit hash against the check hash and returns a boolean.
func Check(hmk, h, pw, s []byte) (chk bool, err error) {
    // Print the input hash
    fmt.Printf("Hash: %x\nHMAC: %x\nSalt: %x\nPass: %x\n", h, hmk, s, []byte(pw))
    hchk, err := hash(hmk, pw, s)
    if err != nil {
        return false, err
    }
    // Print the hash to compare against
    fmt.Printf("Hchk: %x\n", hchk)
    if subtle.ConstantTimeCompare(h, hchk) != 1 {
        return false, errors.New("Error: Hash verification failed")
    }
    return true, nil
}

// New takes an HMAC key and a password (as byte slices)
// Generates a new salt using "crypto/rand"
// Calls hash().
// Returns the resulting 256 bit hash and salt.
func New(hmk, pw []byte) (h, s []byte, err error) {
    s = make([]byte, KEYLENGTH)
    _, err = io.ReadFull(rand.Reader, s)
    if err != nil {
        return nil, nil, err
    }
    h, err = hash(pw, hmk, s)
    if err != nil {
        return nil, nil, err
    }
    fmt.Printf("Hash: %x\nSalt: %x\nPass: %x\n", h, s, []byte(pw))
    return h, s, nil
}

func main() {

    // Known values that work
    pass := "pleaseletmein"

    hash := []byte{
        0x6f, 0x38, 0x7b, 0x9c, 0xe3, 0x9d, 0x9, 0xff,
        0x6b, 0x1c, 0xc, 0xb5, 0x1, 0x67, 0x1d, 0x11,
        0x8f, 0x72, 0x78, 0x85, 0xca, 0x6, 0x50, 0xd0,
        0xe6, 0x8b, 0x12, 0x9c, 0x9d, 0xf4, 0xcb, 0x29,
    }

    salt := []byte{
        0x77, 0xd6, 0x57, 0x62, 0x38, 0x65, 0x7b, 0x20,
        0x3b, 0x19, 0xca, 0x42, 0xc1, 0x8a, 0x4, 0x97,
        0x48, 0x44, 0xe3, 0x7, 0x4a, 0xe8, 0xdf, 0xdf,
        0xfa, 0x3f, 0xed, 0xe2, 0x14, 0x42, 0xfc, 0xd0,
    }

    hmac := []byte{
        0x70, 0x23, 0xbd, 0xcb, 0x3a, 0xfd, 0x73, 0x48,
        0x46, 0x1c, 0x6, 0xcd, 0x81, 0xfd, 0x38, 0xeb,
        0xfd, 0xa8, 0xfb, 0xba, 0x90, 0x4f, 0x8e, 0x3e,
        0xa9, 0xb5, 0x43, 0xf6, 0x54, 0x5d, 0xa1, 0xf2,
    }

    // Check the known values. This Works.
    fmt.Println("Checking known values...")
    chk, err := Check(hmac, hash, []byte(pass), salt)
    if err != nil {
        fmt.Printf("%s\n", err)
    }
    fmt.Printf("%t\n", chk)

    fmt.Println()

    // Create new hash and salt from the known HMAC and Salt
    fmt.Println("Creating new hash and salt values...")
    h, s, err := New(hmac, []byte(pass))
    if err != nil {
        fmt.Printf("%s\n", err)
    }

    // Check the new values. This Fails!
    fmt.Println("Checking new hash and salt values...")
    chk, err = Check(hmac, h, []byte(pass), s)
    if err != nil {
        fmt.Printf("%s\n", err)
    }
    fmt.Printf("%t\n", chk)
}

我在 Linux 64bit 和 Windows8 64bit 上都试过了,但都失败了。

任何帮助将不胜感激!正如我所说,我确实在某些时候有这个工作,但我似乎在某个地方打破了它。我通常只是在编写单元测试时才发现它不起作用......我想这就是他们的目的!

谢谢,

迈克。

【问题讨论】:

  • 当 scrypt 已经使用了 HMAC 和 SHA-256 时,为什么还要使用这两者?
  • 我直接从hmh := hmac.New(sha256.New, hmk) 行中的这两个包中调用函数,所以我需要导入它们。
  • 你为什么要 HMAC'ing scrypt 结果?
  • 请查看crackstation.net/hashing-security.htm#properhashing,这是上面代码所代表的库的灵感来源。基本上,那里使用的 HMAC 密钥应该来自其他地方(例如yubico.com/YubiHSM)。这意味着,如果用户、盐和哈希的整个数据库受到损害,那么如果没有单独的 HMAC,就无法轻易恢复。
  • 盐不需要保密。除非您在客户端执行 scrypt,否则获得 scrypt 的结果是没有意义的。 scrypt 哈希是不可逆的,并且需要大量的计算能力来暴力破解它。在服务器上执行后再次对其进行散列,将属于同一篇文章的“双散列和古怪散列函数”部分。保护加密密钥时需要 HSM。散列是单向的。

标签: cryptography go hmac sha256 scrypt


【解决方案1】:

您似乎在您的一个函数中将参数反转为hash()。在Check(),你有:

hchk, err := hash(hmk, pw, s)

New() 你有:

h, err = hash(pw, hmk, s)

这些显然不会产生导致验证失败的相同结果。

对于三个具有相同类型的类似参数,这样的错误并不会太令人惊讶。也许值得看看您是否可以重组事物以让类型系统捕获此类错误?

【讨论】:

  • 谢谢你,这太明显了,太尴尬了!我已标记为正确答案,但我没有足够的代表来支持你。对不起,如果可以的话,我会的!我已经看了好几个小时的代码,并且一直在看明显的东西!
猜你喜欢
  • 1970-01-01
  • 2019-08-24
  • 2018-03-03
  • 2017-08-25
  • 1970-01-01
  • 2022-01-13
  • 2015-04-17
  • 2019-09-22
相关资源
最近更新 更多