简介
Go语言的hash库提供了一系列用于数据哈希的功能。哈希函数是将输入数据映射为固定大小的哈希值的算法。哈希函数可以用于各种用途,包括数据完整性验证、密码学安全、数据索引和散列表等。
Go语言的hash库提供了多个哈希函数的实现,包括常见的哈希算法如MD5、SHA-1、SHA-256等,以及一些非加密哈希函数如Adler-32、CRC-32等。这些哈希函数可以通过调用相应的方法来计算给定数据的哈希值。
golang的hash库,大致可以完成以下任务
- 数据完整性验证:通过计算数据的哈希值,可以在数据传输或存储过程中验证数据的完整性。接收方可以重新计算哈希值并与发送方提供的哈希值进行比较,以确定数据是否被篡改。
- 密码学安全:哈希函数在密码学中起到重要作用。比如,存储密码时,通常不会将明文密码存储在数据库中,而是将密码的哈希值存储起来。当用户登录时,输入的密码经过哈希计算后与存储的哈希值进行比较,以验证密码的正确性。
- 数据索引:哈希函数可以用于数据索引和散列表的实现。通过将数据的哈希值作为索引,可以快速查找、插入和删除数据。
- 散列算法:哈希函数还可以用于散列算法,将数据映射到散列桶中。这在分布式系统中常用于数据分片、负载均衡等场景。
顶层接口
type Hash interface {
// Write (via the embedded io.Writer interface) adds more data to the running hash.
// It never returns an error.
io.Writer
// Sum appends the current hash to b and returns the resulting slice.
// It does not change the underlying hash state.
Sum(b []byte) []byte
// Reset resets the Hash to its initial state.
Reset()
// Size returns the number of bytes Sum will return.
Size() int
// BlockSize returns the hash's underlying block size.
// The Write method must be able to accept any amount
// of data, but it may operate more efficiently if all writes
// are a multiple of the block size.
BlockSize() int
}
// Hash32 is the common interface implemented by all 32-bit hash functions.
type Hash32 interface {
Hash
Sum32() uint32
}
// Hash64 is the common interface implemented by all 64-bit hash functions.
type Hash64 interface {
Hash
Sum64() uint64
}
Hash是所有Hash算法的顶层接口。
io.Writer的Write(p []byte) (n int, err error)方法用于将外部数据p通过指定的哈希算法计算后覆盖当前的哈希值。Sum(b []byte) []byte方法会将哈希值添加到b的末尾,添加的字节个数就是Size()返回的大小,一般用于进行校验和的操作验证数据的完整性。Resize()用于重置hash值到初始值Size()返回Sum方法添加到b数组末尾的字节大小,一般都是固定的,比如Hash32的Size就是4,Hash64的Size就是8.一般也就是哈希值所占的字节数。BlockSize() int哈希算法底层每次操作的块大小,你在一些哈希算法里面可能会看到很多的位操作,如果这些位操作是对int8操作,那么BlockSize()的返回值就是1,如果每次位操作都是对一个int64,那么BlockSIze()的返回值就是8.
Hash32类型的hash值是一个uint32类型,同理,Hash64类型的hash值是一个uint64类型。
fnv
FNV算法主要分为FNV-1 hash和FNV-1a hash两者区别不大,唯一的区别就是算法中执行乘法和异或操作的顺序相反。
fnv不是加密hash.
我们先来看看golang的fnv包中的这两种算法的实现,我们以sum32和sum32a为例:
func (s *sum32) Write(data []byte) (int, error) {
hash := *s
for _, c := range data {
hash *= prime32
hash ^= sum32(c)
}
*s = hash
return len(data), nil
}
func (s *sum32a) Write(data []byte) (int, error) {
hash := *s
for _, c := range data {
hash ^= sum32a(c)
hash *= prime32
}
*s = hash
return len(data), nil
}
我们可以清晰地看到,fnv算法非常简单,基本上只有两步操作:异或和乘法
具体来说fnv-1的主要原理就是:
- fnv算法的hash初始值是一个fnv偏移基值,该初始值在通过New32和New32a的时候赋予
- 遍历data字节数组
- 每次遍历时都用当前hash值去乘上一个
fnv素数得到新值,并且将其与当前遍历的字节值进行异或操作。
并且由于它是异或一个字节,因此除了低8位以外的都是0,0异或其他数都会得到原数,所以每次异或操作只会影响低8位。
至于fnv-1a算法,就是将异或操作和乘上一个fnv素数操作的顺序反过来。
在这个过程中你可能发现了,在这个算法中有两个比较重要的参数,分别是fnv偏移基值和fnv素数。以下是其参数表: