哈希(Hash)也称为散列,是把任意长度的输入通过哈希算法变换为固定长度的输出,这个输出值也就是散列值。
哈希表是根据键值对(key value)而直接进行访问的数据结构,通过将键值对映射到表中一个位置来访问记录,以加快查询速度。映射函数又称为散列函数,存放记录的数组叫做哈希表。
如果两个输入串的哈希函数的值相同则发生了碰撞(Collision),既然把任意较长字符串转化为固定长度且较短的字符串,因此必有一个输出串对应多个输入串,碰撞是必然存在的。这种碰撞又称为哈希冲突。
散列函数#
哈希算法是一种广义的算法,也可以认为是一种思想,使用哈希算法可提高存储空间的利用率和数据查询效率。
- 哈希函数又称为散列函数,采用散列算法。
- 哈希函数是一种从任何一种数据中创建小的数字“指纹”的方法。
- 哈希函数将数据打乱混合,重新创建一个叫做散列值的“指纹”。
- 哈希函数会将消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。
Go 接口#
Golang的hash包提供多种散列算法,比如crc32/64
, adler32
, fnv
…
1
2
3
4
5
6
7
| type Hash interface{
io.Writer //嵌入io.Writer接口,向执行中的hash加入更多数据。
Sum(b []byte) []byte//将当前hash追加到字节数组b后面,不会改变当前hash状态。
Reset()//重置hash到初始化状态
Size() int//返回hash结果返回的字节数
BlockSize() int//返回hash的集成块大小,为提高效率,Write方法传入的字节数最好是block size的倍数。
}
|
MD5#
MD5消息摘要算法,是一种被广泛使用的密码散列函数,可以产出一个128位(16子节)的散列值。
MD5已被证实无法防止碰撞,已经不算是很安全的算法,因此不适用于安全性认证,比如SSL公开密钥认证或数字签名等用途。
对于需要高度安全性的数据,一般建议改用其他算法,比如SHA256。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
input := "123456"
hash := md5.New() //创建散列值
n, err := hash.Write([]byte(input)) //写入待处理字节
if err != nil {
fmt.Println(err, n)
os.Exit(-1)
}
//bytes := hash.Sum([]byte(""))
bytes := hash.Sum(nil) //获取最终散列值的字符切片
fmt.Printf("%v\n", bytes)
//[225 10 220 57 73 186 89 171 190 86 224 87 242 15 136 62]
fmt.Printf("%x\n", bytes) //以16进制字符串格式化字符切片
//e10adc3949ba59abbe56e057f20f883e
|
MD5和SHA256是非常常用的两种单向散列函数
1
2
3
4
5
6
7
8
9
10
11
12
| import (
"crypto/md5"
"encoding/hex"
"testing"
)
func MD5(input string) string {
c := md5.New()
c.Write([]byte(input))
bytes := c.Sum(nil)
return hex.EncodeToString(bytes)
}
|
SHA-1#
1
2
3
4
5
6
7
| password := "123456"
ins := sha1.New()
ins.Write([]byte(password))
result := ins.Sum([]byte(""))
fmt.Printf("%x\n", result)
//7c4a8d09ca3762af61e59520943dc26494f8941b
|
1
2
3
4
5
6
7
8
9
10
11
12
| import (
"crypto/sha1"
"encoding/hex"
"testing"
)
func SHA1(input string) string {
c := sha1.New()
c.Write([]byte(input))
bytes := c.Sum(nil)
return hex.EncodeToString(bytes)
}
|
CRC32#
- CRC即Cyclic Redundancy Check循环冗余校验码
- CRC是实现32位循环冗余校验或CRC-32校验和
在远距离数据通信中,为确保高效而无差错地传送数据,必须对数据进行校验即差错控制。
CRC(Cyclic Redundancy Check/Code)循环冗余校验是对一个传送数据块进行校验,是一种高效的差错控制方法。
ChecksumIEEE使用IEEE多项式返回数据的CRC-32校验和
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| package test
import (
"hash/crc32"
"testing"
)
func CRC32(input string) uint32 {
bytes := []byte(input)
return crc32.ChecksumIEEE(bytes)
}
func TestHash(t *testing.T) {
input := "123456"
t.Log(CRC32(input)) //158520161
}
|
MurMur#
我们有时候想将一段内容(比如字符串)转换成一个随机整数值,这里我们使用murmur3 hash算法可以达到这个目的
1)hash算法有可能发生碰撞,即不同的输入转换出的hash值是一样的,好的算法当然发生碰撞的概率会很小。
2)murmur3算法是非加密哈希算法
- 加密哈希函数旨在保证安全性,很难找到碰撞。即:给定的散列h很难找到的消息m;很难找到产生相同的哈希值的消息m1和m2。
- 非加密哈希函数只是试图避免非恶意输入的冲突。作为较弱担保的交换,它们通常更快。如果数据量小,或者不太在意哈希碰撞的频率,甚至可以选择生成哈希值小的哈希算法,占用更小的空间。
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| package main
import (
"fmt"
"github.com/spaolacci/murmur3"
)
func main() {
originalStr := "pwww.google.com"
// 注意:生成的hash值有三种取值,uint32,uint64,uint128,分别对应方法Sum32,Sum64,Sum128
// 下面例子以Sum64为例
// 1、使用默认种子,生成哈希值
// 默认种子,其实是seed=0
hValue1 := murmur3.Sum64([]byte(originalStr))
fmt.Printf("hValue1 is %d\n", hValue1)
// hValue1 is 13092418635223121727
// 默认返回值是uint64, 转化为int64
hValue1 := murmur3.Sum64([]byte(originalStr))
fmt.Printf("hValue1 is %d\n", hValue1)
// hValue1 is -5354325438486429889
// 2、使用指定种子,生成哈希值
seed := uint32(0000)
hValue2 := murmur3.Sum64WithSeed([]byte(originalStr), seed)
fmt.Printf("hValue2 is %d\n", hValue2)
// hValue2 is 13092418635223121727
// 3、使用指定种子,生成哈希值,2的另一种写法
h := murmur3.New64WithSeed(seed)
h.Write([]byte(originalStr))
hValue3 := h.Sum64()
fmt.Printf("hValue3 is %d\n", hValue3)
// hValue3 is 13092418635223121727
// 如果使用h继续计算其他值,则需要首先调用Reset,引为write这里是追加写
h.Reset()
h.Write([]byte(originalStr))
hValue4 := h.Sum64()
fmt.Printf("hValue4 is %d\n", hValue4)
// hValue4 is 13092418635223121727
}
|