【问题标题】:equivalent salt and hash in golanggolang中的等效盐和哈希
【发布时间】:2014-05-27 04:40:44
【问题描述】:

这是一个在 python 中对给定密码进行加盐和散列的示例。

import scrypt
import os

# Length of salt
PW_SALT_BYTES = 32
# Length of scrypt hash of passwords
PW_HASH_BYTES = 64
# test password
password = "hello"

salt = os.urandom(PW_SALT_BYTES).encode('hex')
# hash(password, salt, N=1 << 14, r=8, p=1, buflen=64)
hashed_password = scrypt.hash(str(password), salt.decode('hex'), buflen=PW_HASH_BYTES).encode('hex')
print(hashed_password)

这会给我们一个散列和加盐的字符串作为回报:-

4d1da45b401961fccb10e094ecd70ec79510f05483ca293d300bbd0024e35866ca39fe09fbc15f83a359431021a1ed9644f7d2b871b357e37a186300877edb18

我将如何在 golang 中实现这一点?

【问题讨论】:

    标签: go


    【解决方案1】:

    Go 在标准库中没有 scrypt,但在 go.crypto 存储库中有一个“官方”实现。

    import (
        "crypto/rand"
        "fmt"
        "io"
        "log"
    
        "code.google.com/p/go.crypto/scrypt"
    )
    
    const (
        PW_SALT_BYTES = 32
        PW_HASH_BYTES = 64
    
        password = "hello"
    )
    
    func main() {
        salt := make([]byte, PW_SALT_BYTES)
        _, err := io.ReadFull(rand.Reader, salt)
        if err != nil {
            log.Fatal(err)
        }
    
        hash, err := scrypt.Key([]byte(password), salt, 1<<14, 8, 1, PW_HASH_BYTES)
        if err != nil {
            log.Fatal(err)
        }
    
        fmt.Printf("%x\n", hash)
    }
    

    【讨论】:

    • 这看起来像我正在尝试的,但我是否误解了一些东西,因为结果输出hash(来自golang)与python脚本中打印的hashed_password不匹配?我在想它们应该产生相同的输出,因为两个测试密码都是“hello”。
    • @CalvinCheng:在比较 Python 和 Go 版本时,您使用的是相同的盐吗?如果不是,那么输出不同是一件好事。
    • 是的。我也在尝试使用固定盐,但它们不匹配。对于上面的例子,你是对的,因为盐是随机生成的,所以它们不应该匹配。
    • 好的,我想通了。这是因为在我的固定盐尝试中,我忘记了固定盐上的.encode('hex')。所以问题解决了,感谢您在上面的解决方案。这正是我想要的。
    【解决方案2】:

    看起来现在 Go 在官方库中有 scrypt。它的subrepositoryx/crypto 在许多其他加密函数中都有一个scrypt

    这是一个如何使用它的示例:

    package main
    
    import (
        "golang.org/x/crypto/scrypt"
        "fmt"
    )
    
    func main(){
        salt := []byte("asdfasdf")
        dk, err := scrypt.Key([]byte("some password"), salt, 16384, 8, 1, 32)
    
        fmt.Println(dk)
        fmt.Println(err)
    }
    

    【讨论】:

      【解决方案3】:

      而不是使用scrypt,在Golang 中使用随机盐安全地散列密码的一个很棒的库是golang.org/x/crypto/bcrypt,如以下答案中所述:

      Bcrypt password hashing in Golang (compatible with Node.js)?

      使用bcrypt 代替scrypt 的几个好处:

      1. 盐是在对密码进行哈希处理时自动(随机)生成的,因此您不必担心盐的生成。
      2. 在数据库中存储散列密码时,您不必再担心存储每个密码散列的 salt。
      3. 简化了哈希和检查密码的语法。
      4. bcrypt 产生的哈希包括 bcrypt 版本、成本、盐和密码,而不仅仅是密码。

      这是从上述答案中提取的使用 bcrypt 的示例:

      package main
      
      import (
          "golang.org/x/crypto/bcrypt"
          "fmt"
      )
      
      func main() {
          password := []byte("MyDarkSecret")
      
          // Hashing the password with the default cost of 10
          hashedPassword, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
          if err != nil {
              panic(err)
          }
          fmt.Println(string(hashedPassword))
      
          // Comparing the password with the hash
          err = bcrypt.CompareHashAndPassword(hashedPassword, password)
          fmt.Println(err) // nil means it is a match
      }
      

      【讨论】:

        【解决方案4】:

        这是我基于RFC 2898 / PKCS #5 v2.0 编写的完整的散列实用程序函数。

        Hash 可用于对密码进行哈希处理,例如Hash("hello")

        虽然Verify 可用于针对散列的原始密码,但基本上它的作用是散列原始字符串并将其与实际散列进行比较。

        package common
        
        import (
            "crypto/rand"
            "crypto/sha1"
            "encoding/base64"
            "errors"
            "fmt"
            "golang.org/x/crypto/pbkdf2"
            "io"
            "strconv"
            "strings"
        )
        
        const (
            SALT_BYTE_SIZE    = 24
            HASH_BYTE_SIZE    = 24
            PBKDF2_ITERATIONS = 1000
        )
        
        func Hash(password string) (string, error) {
            salt := make([]byte, SALT_BYTE_SIZE)
            if _, err := io.ReadFull(rand.Reader, salt); err != nil {
                fmt.Print("Err generating random salt")
                return "", errors.New("Err generating random salt")
            }
        
            //todo: enhance: randomize itrs as well
            hbts := pbkdf2.Key([]byte(password), salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE, sha1.New)
            //hbtstr := fmt.Sprintf("%x", hbts)
        
            return fmt.Sprintf("%v:%v:%v",
                PBKDF2_ITERATIONS,
                base64.StdEncoding.EncodeToString(salt),
                base64.StdEncoding.EncodeToString(hbts)), nil
        }
        
        func Verify(raw, hash string) (bool, error) {
            hparts := strings.Split(hash, ":")
        
            itr, err := strconv.Atoi(hparts[0])
            if err != nil {
                fmt.Printf("wrong hash %v", hash)
                return false, errors.New("wrong hash, iteration is invalid")
            }
            salt, err := base64.StdEncoding.DecodeString(hparts[1])
            if err != nil {
                fmt.Print("wrong hash, salt error:", err)
                return false, errors.New("wrong hash, salt error:" + err.Error())
            }
        
            hsh, err := base64.StdEncoding.DecodeString(hparts[2])
            if err != nil {
                fmt.Print("wrong hash, hash error:", err)
                return false, errors.New("wrong hash, hash error:" + err.Error())
            }
        
            rhash := pbkdf2.Key([]byte(raw), salt, itr, len(hsh), sha1.New)
            return equal(rhash, hsh), nil
        }
        
        //bytes comparisons
        func equal(h1, h2 []byte) bool {
            diff := uint32(len(h1)) ^ uint32(len(h2))
            for i := 0; i < len(h1) && i < len(h2); i++ {
                diff |= uint32(h1[i] ^ h2[i])
            }
        
            return diff == 0
        }
        

        这里有一个单元测试可以帮助你弄清楚如何调用这些函数

        package common
        
        import (
        
            "github.com/stretchr/testify/assert"
            "testing"
        )
        
        func TestHash(t *testing.T) {
            hash, err := Hash("hello")
            assert.Nil(t, err)
            assert.NotEmpty(t, hash)
        
        }
        
        func TestVerify(t *testing.T) {
            hash, err := Hash("hello")
            assert.Nil(t, err)
            assert.NotEmpty(t, hash)
        
            ok, err := Verify("hello", hash)
            assert.Nil(t, err)
            assert.True(t, ok)
        }
        

        【讨论】:

          猜你喜欢
          • 2011-05-27
          • 1970-01-01
          • 2011-01-09
          • 2014-06-09
          • 2012-08-21
          • 2011-02-21
          相关资源
          最近更新 更多