单向散列函数及案例

Posted 小圣.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单向散列函数及案例相关的知识,希望对你有一定的参考价值。

1. 概述

单向散列函数(one-way hash function),又称单向Hash函数、杂凑函数。有一个输入和一个输出,输入的数据称为消息(message),输出的数据称为散列值(hash value)。单向散列函数根据输入消息的内容计算出散列值,而散列值可以被用来检验消息的完整性等。

1.1 单向散列函数的性质

  • 根据任意长度的消息计算出固定长度的散列值
  • 消息不同散列值也不同,又称抗碰撞性
  • 无法或很难通过散列值计算的到消息,又称单向性

1.2 单向散列函数的应用

  • 检测软件是否被篡改:如果软件被篡改后,那么散列值就会发生变化。
  • 消息认证码:消息认证码是将发送者和接收者之间的共享密钥和消息进行混合后计算出的散列值。使用消息认证码可以防止通信过程中的错误、篡改以及伪装。
  • 数字签名:单向散列函数加上非对称加密算法就是数字签名,利用数字签名可以防止对数据的否认。
  • 伪随机数生成器:为了保障随机数的不可预测性,可以利用单向散列函数的单向性。

2. MD4、MD5

2.1 介绍

MD4是由 Ronald Rivest于1990年设计的一种信息摘要算法。它是一种用来测试信息完整性的密码散列函数的实现。其散列值长度为128位
MD5也是由Ronald Rivest设计,于1992年公开,用于取代MD4算法。是被广泛使用的密码散列函数,可以产生出一个128位的散列值(hash value),主要用于确保信息传输完整一致。1996年后该算法被证实存在弱点,可以被加以破解,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。2004年,证实MD5算法无法防止碰撞(collision),因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。

MD4和MD5中的MD就是消息摘要(Message Digest),在Go语言中,默认只提供了MD5的包。

2.2 案例

// 对少量数据进行MD5
func MD5Test1(data string) {
	// 计算散列值
	result := md5.Sum([]byte(data))
	fmt.Printf("少量数据hash后的数据:%x\\n", result)
}

// 对文件进行hash
func MD5Test2(fileName string) {
	// 获取文件句柄
	f, err := os.Open(fileName)
	if err != nil {
		fmt.Println("open file err:", err)
		return
	}
	defer f.Close()

	// 获取hash器
	hasher := md5.New()
	// 把文件内容拷贝到hash器
	_, err = io.Copy(hasher, f)
	if err != nil {
		fmt.Println("copy data err: ", err)
		return
	}

	// 计算散列值
	result := hasher.Sum(nil)

	fmt.Printf("大量数据hash后的数据:%x", result)
}

3. RIPEMD160

3.1 介绍

RIPMD(RIPEMD(RACE Integrity Primitives Evaluation Message Digest,RACE 原始完整性校验讯息摘要)。是一种加密哈希函数,由鲁汶大学 Hans Dobbertin,Antoon Bosselaers 和 Bart Prenee组成的COSIC 研究小组发布于1996年。 RIPEMD以MD4为基础原则进行设计。RIPEMD-160是其中的一种,这一系列函数还包括RIPEMD-128、RIPEMD-256、RIPEMD-320。

3.2 案例

// 对少量数据进行hash
func Ripemd160Test1(data string) {
	// 获取hash器
	hahser := ripemd160.New()
	// 计算散列值
	result := hahser.Sum([]byte(data))
	fmt.Printf("少量数据hash后的数据:%x\\n", result)
}

// 对文件进行hash
func Ripemd160Test2(fileName string) {
	// 获取文件句柄
	f, err := os.Open(fileName)
	if err != nil {
		fmt.Println("open file err:", err)
		return
	}
	defer f.Close()

	// 获取hash器
	hasher := ripemd160.New()
	// 把文件内容拷贝到hash器
	_, err = io.Copy(hasher, f)
	if err != nil {
		fmt.Println("copy data err: ", err)
		return
	}

	// 计算散列值
	result := hasher.Sum(nil)

	fmt.Printf("大量数据hash后的数据:%x", result)
}

4. SHA-1、SHA-2

4.1 介绍

SHA-1是由NIST(NationalInstituteOfStandardsandTechnology,美国国家标准技术研究所)设计的一种能够产生160比特的散列值的单向散列函数。1993年被作为美国联邦信息处理标准规格(FIPS PUB 180)发布的是SHA,1995年发布的修订版FIPS PUB 180-1称为SHA-1。但是SHA-1 的强抗碰撞性已于2005年被攻破, 也就是说,现在已经能够产生具备相同散列值的两条不同的消息。
于是就是产生了SHA-2,SHA2的单向散列函数包括SHA-256、SHA-384、SHA-512。目前SHA-2还没有被攻破。

4.2 SHA-256案例

// 对少量数据进行hash
func Sha256Test1(data string) {
	// 计算散列值
	result := sha256.Sum256([]byte(data))
	fmt.Printf("少量数据hash后的数据:%x\\n", result)
}

// 对文件进行hash
func Sha256Test2(fileName string) {
	// 获取文件句柄
	f, err := os.Open(fileName)
	if err != nil {
		fmt.Println("open file err:", err)
		return
	}
	defer f.Close()

	// 获取hash器
	hasher := sha256.New()
	_, err = io.Copy(hasher, f)
	if err != nil {
		fmt.Println("copy data err: ", err)
		return
	}

	// 计算散列值
	result := hasher.Sum(nil)

	fmt.Printf("文件数据hash后的数据:%x", result)
}

5. POW共识算法的实现

5.1 介绍

工作量证明(Proof-of-Work,PoW)是一种对应服务与资源滥用、或是拒绝服务攻击的经济对策。一般要求用户进行一些耗时适当的复杂运算,并且答案能被服务方快速验算,以此耗用的时间、设备与能源做为担保成本,以确保服务与资源是被真正的需求所使用。比特币的共识算法就是工作量证明。

在比特币系统中,完成工作量证明的条件是:不断地对区块头进行哈希,直到计算出一个小于目标值的hash值。在计算期间,会不断的变化区块头中的nonce值。

5.2 代码实现

// 定义区块结构
type Block struct {
	// 版本号
	Version uint64
	// 前区块哈希
	PrevHash []byte
	// Merkel根
	MerkelRoot []byte
	// 时间戳
	TimeStamp uint64
	// 难度值
	Bits uint64
	// 随机数,也就是挖矿要找的数据
	Nonce uint64
}

func Pow(block *Block) *Block {

	// 模拟目标值
	target := "0000100000000000000000000000000000000000000000000000000000000000"

	// 定义bigInt类型
	targetInt := big.Int{}
	// 将难度值转换成bigInt类型,指定为16进制格式
	targetInt.SetString(target, 16)

	// 计算出指定的哈希值
	var nonce uint64
	var hash [32]byte
	for {
		// 拼装区块数据,主要是nonce这个字段
		tempBlock := [][]byte{
			Uint64ToByte(block.Version),
			block.PrevHash,
			block.MerkelRoot,
			Uint64ToByte(block.TimeStamp),
			Uint64ToByte(block.Bits),
			// 这个值不断变换
			Uint64ToByte(nonce),
		}
		// 将二维切片转成一维切片
		blockInfo := bytes.Join(tempBlock, []byte{})

		// 做哈希运算
		hash = sha256.Sum256(blockInfo)

		// 将计算出的hash转成bigInt,好和目标值比较
		blockInt := big.Int{}
		blockInt.SetBytes(hash[:])

		// 与目标值比较,如果小于目标值,就代表找到了
		if blockInt.Cmp(&targetInt) == -1 {
			block.Nonce = nonce
			return block
		} else {
			// 没有找到,nonce值加1
			nonce++
		}

	}
}

运行结果:

文章中的所有代码已上传到github: https://github.com/bigzoro/cryptography/tree/main/hash

以上是关于单向散列函数及案例的主要内容,如果未能解决你的问题,请参考以下文章

单向散列函数及案例

单向散列算法的常见算法

密码技术--单向散列函数即Go语言应用

图解密码技术笔记单向散列函数——获取消息的指纹

图解密码技术笔记单向散列函数——获取消息的指纹

一文搞懂单向散列函数