【问题标题】:Function should return sha256/sha384/sha512 result as byte slice函数应将 sha256/sha384/sha512 结果作为字节片返回
【发布时间】:2018-01-02 20:18:21
【问题描述】:

我正在编写一个函数,它将输入数据作为字符串以及要调用的 SHA 算法的位大小。它应该将生成的哈希作为字节片返回(第一次尝试):

package main

import (
    "crypto/sha256"
    "crypto/sha512"
    "errors"
    "fmt"
)

func main() {
    input := "This is a test."
    sha256, _ := shaSum(input, 256)
    sha384, _ := shaSum(input, 384)
    sha512, _ := shaSum(input, 512)
    fmt.Println(input, sha256, sha384, sha512)
}

func shaSum(data string, size uint) ([]byte, error) {
    input := []byte(data)
    switch size {
    case 256:
        return sha256.Sum256(input), nil
    case 384:
        return sha512.Sum384(input), nil
    case 512:
        return sha512.Sum512(input), nil
    default:
        return nil, errors.New("unsupported sha size")
    }
}

这当然行不通:

$ go run shasum.go
# command-line-arguments
./shasum.go:22:23: cannot use sha256.Sum256(input) (type [32]byte) as type []byte in return argument
./shasum.go:24:23: cannot use sha512.Sum384(input) (type [48]byte) as type []byte in return argument
./shasum.go:26:23: cannot use sha512.Sum512(input) (type [64]byte) as type []byte in return argument

所以我尝试从哈希函数的返回值中获取一个切片,在每次调用后添加[:](第二次尝试):

func shaSum(data string, size uint) ([]byte, error) {
    input := []byte(data)
    switch size {
    case 256:
        return sha256.Sum256(input)[:], nil
    case 384:
        return sha512.Sum384(input)[:], nil
    case 512:
        return sha512.Sum512(input)[:], nil
    default:
        return nil, errors.New("unsupported sha size")
    }
}

这也不起作用:

$ go run shasum.go
# command-line-arguments
./shasum.go:22:30: invalid operation sha256.Sum256(input)[:] (slice of unaddressable value)
./shasum.go:24:30: invalid operation sha512.Sum384(input)[:] (slice of unaddressable value)
./shasum.go:26:30: invalid operation sha512.Sum512(input)[:] (slice of unaddressable value)

所以我尝试获取返回值的地址,使用括号确保先取表达式的地址,然后再切片(第三次尝试):

func shaSum(data string, size uint) ([]byte, error) {
    input := []byte(data)
    switch size {
    case 256:
        return (&(sha256.Sum256(input)))[:], nil
    case 384:
        return (&(sha512.Sum384(input)))[:], nil
    case 512:
        return (&(sha512.Sum512(input)))[:], nil
    default:
        return nil, errors.New("unsupported sha size")
    }
}

产生此错误消息:

$ go run shasum.go
# command-line-arguments
./shasum.go:22:10: cannot take the address of sha256.Sum256(input)
./shasum.go:24:10: cannot take the address of sha512.Sum384(input)
./shasum.go:26:10: cannot take the address of sha512.Sum512(input)

所以我放弃并使用额外的行来解决它(第四次尝试):

func shaSum(data string, size uint) ([]byte, error) {
    input := []byte(data)
    switch size {
    case 256:
        bytes := sha256.Sum256(input)
        return bytes[:], nil
    case 384:
        bytes := sha512.Sum384(input)
        return bytes[:], nil
    case 512:
        bytes := sha512.Sum512(input)
        return bytes[:], nil
    default:
        return nil, errors.New("unsupported sha size")
    }
}

最终编译并运行。现在我想知道:为什么第四次尝试有效而其他尝试(尤其是第三次尝试)无效?一个好的解决方案会是什么样子?有没有办法像第四次尝试那样避免那些额外的行?

编辑:我的问题的根本问题不是如何从字节数组转换为字节切片,而是我对可寻址性概念以及如何表达解决方案缺乏了解我在惯用 Go 中遇到的问题。

【问题讨论】:

  • “好的解决方案”是最终的解决方案。由于错误消息中给出的原因,其他的不能工作 - 切片和字节不可互换,并且您不能像函数返回值那样获取不可寻址值的切片;你必须把它放在一个可寻址的值中,即一个变量。
  • 那么秘诀不是从返回值中获取地址,而是将结果存储在函数上下文中?编辑:这不是关于语法而是关于内存管理?
  • 不完全是。对数组进行切片需要在内部获取对数组的引用(地址)。
  • 是的,您必须先存储该值,然后才能对其进行切片或获取指向它的指针。在您存储之前,没有地址可获取。

标签: arrays go cryptography


【解决方案1】:

语言规范states this about the slice operator

如果切片操作数是一个数组,它必须是可寻址的,并且切片操作的结果是一个与数组元素类型相同的切片。

this about addressability:

操作数必须是可寻址的,即变量、指针间接或切片索引操作;或可寻址结构操作数的字段选择器;或可寻址数组的数组索引操作。

由此可见,第四次尝试是唯一有效的尝试。

这是使用hash.Hash 接口的另一种方法。

var hashFactory = map[int]func() hash.Hash{
    256: sha256.New,
    384: sha512.New384,
    512: sha512.New,
}

func shaSum(data string, size int) ([]byte, error) {
    f := hashFactory[size]
    if f == nil {
        return nil, errors.New("unsupported sha size")
    }
    h := f()
    io.WriteString(h, data)
    return h.Sum(nil), nil
}

【讨论】:

    【解决方案2】:

    重要的是,请注意sha256.Sum256 function(和其他 SHA 函数)的签名返回一个 array,它与 slice 完全不同。这篇文章清楚地解释了区别:Go Slices: usage and internals

    为了说明,请考虑以下几点:

    x := [3]int{1, 2, 3} // x has type [3]int
    y := []int(x) // ERROR: cannot convert x (type [3]int) to type []int
    

    这就是为什么您的每次尝试都不起作用的原因:

    1. shaSum 函数返回一个 ([]byte, error) 元组,但每个 return 语句都试图返回 ([Size]byte, error),因此编译器会抱怨类型不匹配。

    2. 每个sha*.Sum* 函数返回一个([Size]byte, error) 元组;您不能在这样的元组上使用 [:] 运算符,只能在 arrays and slices 上使用。

    3. 与上面的 #2 类似,您只能在 addressable types 上使用 & 运算符,它不包括函数调用(或它们返回的值,直到它们存储在本地)。

    4. 这个工作示例是惯用的 go。语言设计者故意选择优化以使读者更清楚,而不是作者的简写。

    【讨论】:

      猜你喜欢
      • 2012-02-09
      • 2021-05-02
      • 2021-07-22
      • 2015-12-10
      • 2015-05-20
      • 1970-01-01
      • 2015-07-29
      • 2017-03-09
      • 2018-11-13
      相关资源
      最近更新 更多