sha256加密原理及代码实现
Posted better_hui
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了sha256加密原理及代码实现相关的知识,希望对你有一定的参考价值。
目录
学习区块链,总是无法避开各种加密算法,因为各种加密算法在实现区块链当中的各个环节都有着不可替代的作用。这里介绍一下在比特币挖矿以及merkle树当中被大量使用的鼎鼎大名的SHA256算法。
一、简介
一个n
位的哈希函数就是一个从任意长的消息到n
位哈希值的映射,一个n
位的加密哈希函数就是一个单向的、避免碰撞的n
位哈希函数。这样的函数是目前在数字签名和密码保护当中极为重要的手段。
当前比较流行的哈希函数主要有128位的MD4和MD5和160位的SHA-1,今天介绍的SHA-2族有着更多位的输出哈希值,破解难度更大,能够提高更高的安全性。
SHA-2,名称来自于安全散列算法2(英语:Secure Hash Algorithm 2)的缩写,一种密码散列函数算法标准,由美国国家安全局研发,由美国国家标准与技术研究院(NIST)在2001年发布。属于SHA算法之一,是SHA-1的后继者。其下又可再分为六个不同的算法标准,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。
这些变体除了生成摘要的长度、循环运行的次数等一些细微差异之外,基本结构是一致的。本文主要讲一讲SHA256。
对于任意长度的消息,SHA256都会产生一个256bit长的哈希值,称作消息摘要。
二、SHA256过程详解
对于任意长度的消息,SHA256都会产生一个256位的哈希值,称作消息摘要。这个摘要相当于是个长度为32个字节的数组,通常有一个长度为64的十六进制字符串来表示,其中1个字节=8位,一个十六进制的字符的长度为4位。
例如:学习区块链要记得记笔记
sha256后 :241f18af4356859f5f47b38d3f532b855b30884141c913b140a4f81ae5505dbc
这里有一个在线加密的链接 可以验证 http://tools.bugscaner.com/cryptosha256/
总体上,HSA256与MD4、MD5以及HSA-1等哈希函数的操作流程类似,待哈希的消息在继续哈希计算之前首先要进行以下两个步骤:
- 对消息进行补位处理,是的最终的长度是512位的倍数,然后
- 以512位为单位对消息进行分块为
消息区块将进行逐个处理:从一个固定的初始哈希开始,进行以下序列的计算:
其中C是SHA256的压缩函数,+ 是mod 加法,即将两个数字加在一起,如果对取余, 是消息区块的哈希值.
SHA256的压缩函数主要对512位的消息区块和256位的中间哈希值进行操作,本质上,它是一个通过将消息区块为密钥对中间哈希值进行加密的256位加密算法。 因此,为了描述SHA256算法,有以下两方面的组件需要描述:
- SHA256压缩函数
- SHA256消息处理流程
1、公式
运算符号
符号 | 含义 |
∧ | 按位“与” |
¬ | 按位“补” |
⊕ | 按位“异或” |
循环右移n个bit | |
右移n个bit |
2、常量及初始化
SHA256算法中用到了8个哈希初值以及64个哈希常量
取自自然数中前面8个素数(2,3,5,7,11,13,17,19)的平方根的小数部分, 并且取前面的32位. 下面举个例子:
小数部分约为0.414213562373095048, 而其中
0.414213562373095048≈6∗16−1+a∗16−2+0∗16−3+...
于是,质数2的平方根的小数部分取前32bit就对应出了0x6a09e667
如此类推, 初始哈希值由以下8个32位的哈希初值构成:
SHA256算法当中还使用到64个常数, 取自自然数中前面64个素数的立方根的小数部分的前32位, 如果用16进制表示, 则相应的常数序列如下:
428a2f98 71374491 b5c0fbcf e9b5dba5
3956c25b 59f111f1 923f82a4 ab1c5ed5
d807aa98 12835b01 243185be 550c7dc3
72be5d74 80deb1fe 9bdc06a7 c19bf174
e49b69c1 efbe4786 0fc19dc6 240ca1cc
2de92c6f 4a7484aa 5cb0a9dc 76f988da
983e5152 a831c66d b00327c8 bf597fc7
c6e00bf3 d5a79147 06ca6351 14292967
27b70a85 2e1b2138 4d2c6dfc 53380d13
650a7354 766a0abb 81c2c92e 92722c85
a2bfe8a1 a81a664b c24b8b70 c76c51a3
d192e819 d6990624 f40e3585 106aa070
19a4c116 1e376c08 2748774c 34b0bcb5
391c0cb3 4ed8aa4a 5b9cca4f 682e6ff3
748f82ee 78a5636f 84c87814 8cc70208
90befffa a4506ceb bef9a3f7 c67178f2
和8个哈希初值类似,这些常量是对自然数中前64个质数(2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97…)的立方根的小数部分取前32bit而来。
3、消息预处理
SHA256算法中的预处理就是在想要Hash的消息后面补充需要的信息,使整个消息满足指定的结构。
信息的预处理分为两个步骤:填充比特
和附加长度
填充比特
信息必须进行填充,也就是说,即使长度已经满足对512取模后余数是448,补位也必须要进行,这时要填充512个比特。因此,填充是至少补一位,最多补512位。
举例说明:
a,b,c对应的ASCII码分别是97,98,99
于是原始信息的二进制编码为:01100001 01100010 01100011
补位第一步,首先补一个“1” : 0110000101100010 01100011 1
补位第二步,补423个“0”:01100001 01100010 01100011 10000000 00000000 … 00000000
补位完成后的数据如下(为了简介用16进制表示):
填充对应的代码
padded := append(message, 0x80)
if len(padded) % 64 < 56 {
suffix := make([]byte, 56 - (len(padded) % 64))
padded = append(padded, suffix...)
} else {
suffix := make([]byte, 64 + 56 - (len(padded) % 64))
padded = append(padded, suffix...)
}
解释:按照上文的解释 , 一个分组是512 ,这里为什么 %64呢 ? 这是因为byte类型 是8bit的
附加长度值
将原始数据(第一步填充前的消息)的长度信息补到已经进行了填充操作的消息后面。
SHA256用一个64位的数据来表示原始消息的长度。
回到刚刚的例子,消息“abc”,3个字符,占用24个bit
因此,在进行了补长度的操作以后,整个消息就变成下面这样了(16进制格式)
附加长度值对应的代码
msgLen := len(message) * 8
bs := make([]byte, 8)
binary.BigEndian.PutUint64(bs, uint64(msgLen))
padded = append(padded, bs...)
4、计算消息摘要
消息拆分
假设消息M可以被分解为n个块,于是整个算法需要做的就是完成n次迭代,n次迭代的结果就是最终的哈希值,即256bit的数字摘要。
拆分对应的代码
broken := [][]byte{};
for i := 0; i < len(padded) / 64; i++ {
broken = append(broken, padded[i * 64: i * 64 + 64])
}
构造64个字(word)
对于每一块,将块分解为16个32-bit的big-endian的字,记为w[0], …, w[15]
也就是说,前16个字直接由消息的第i个块分解得到
其余的字由如下迭代公式得到:
对应代码如下
w := []uint32{}
for i := 0; i < 16; i++ {
w = append(w, binary.BigEndian.Uint32(chunk[i * 4:i * 4 + 4]))
}
w = append(w, make([]uint32, 48)...)
//W消息区块处理
for i := 16; i < 64; i++ {
s0 := rightRotate(w[i - 15], 7) ^ rightRotate(w[i - 15], 18) ^ (w[i - 15] >> 3)
s1 := rightRotate(w[i - 2], 17) ^ rightRotate(w[i - 2], 19) ^ (w[i - 2] >> 10)
w[i] = w[i - 16] + s0 + w[i - 7] + s1
}
进行64次循环
进行64次加密循环即可完成一次迭代
每次加密循环可以由下图描述:
对应代码如下
//应用SHA256压缩函数更新a,b,...,h
for i := 0; i < 64; i++ {
S1 := rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)
ch := (e & f) ^ ((^e) & g)
temp1 := h + S1 + ch + k[i] + w[i]
S0 := rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)
maj := (a & b) ^ (a & c) ^ (b & c)
temp2 := S0 + maj
h = g
g = f
f = e
e = d + temp1
d = c
c = b
b = a
a = temp1 + temp2
}
由此变完成了SHA256算法的所有介绍
三、完整的代码实现
package main
import (
"encoding/binary"
"encoding/hex"
"fmt"
)
func main(){
encrypted:=wikiSha256([]byte("学习区块链要记得记笔记"))
fmt.Println(hex.EncodeToString(encrypted))
}
func wikiSha256(message []byte) []byte {
//初始哈希值
h0 := uint32(0x6a09e667)
h1 := uint32(0xbb67ae85)
h2 := uint32(0x3c6ef372)
h3 := uint32(0xa54ff53a)
h4 := uint32(0x510e527f)
h5 := uint32(0x9b05688c)
h6 := uint32(0x1f83d9ab)
h7 := uint32(0x5be0cd19)
//计算过程当中用到的常数
k := [64]uint32{
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}
padded := append(message, 0x80)
if len(padded) % 64 < 56 {
suffix := make([]byte, 56 - (len(padded) % 64))
padded = append(padded, suffix...)
} else {
suffix := make([]byte, 64 + 56 - (len(padded) % 64))
padded = append(padded, suffix...)
}
msgLen := len(message) * 8
bs := make([]byte, 8)
binary.BigEndian.PutUint64(bs, uint64(msgLen))
padded = append(padded, bs...)
broken := [][]byte{};
for i := 0; i < len(padded) / 64; i++ {
broken = append(broken, padded[i * 64: i * 64 + 64])
}
//主循环
for _, chunk := range broken {
w := []uint32{}
for i := 0; i < 16; i++ {
w = append(w, binary.BigEndian.Uint32(chunk[i * 4:i * 4 + 4]))
}
w = append(w, make([]uint32, 48)...)
//W消息区块处理
for i := 16; i < 64; i++ {
s0 := rightRotate(w[i - 15], 7) ^ rightRotate(w[i - 15], 18) ^ (w[i - 15] >> 3)
s1 := rightRotate(w[i - 2], 17) ^ rightRotate(w[i - 2], 19) ^ (w[i - 2] >> 10)
w[i] = w[i - 16] + s0 + w[i - 7] + s1
}
a := h0
b := h1
c := h2
d := h3
e := h4
f := h5
g := h6
h := h7
//应用SHA256压缩函数更新a,b,...,h
for i := 0; i < 64; i++ {
S1 := rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)
ch := (e & f) ^ ((^e) & g)
temp1 := h + S1 + ch + k[i] + w[i]
S0 := rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)
maj := (a & b) ^ (a & c) ^ (b & c)
temp2 := S0 + maj
h = g
g = f
f = e
e = d + temp1
d = c
c = b
b = a
a = temp1 + temp2
}
h0 = h0 + a
h1 = h1 + b
h2 = h2 + c
h3 = h3 + d
h4 = h4 + e
h5 = h5 + f
h6 = h6 + g
h7 = h7 + h
}
hashArray := make([]byte , 0)
hashArray = append(hashArray , iToB(h0)...)
hashArray = append(hashArray , iToB(h1)...)
hashArray = append(hashArray , iToB(h2)...)
hashArray = append(hashArray , iToB(h3)...)
hashArray = append(hashArray , iToB(h4)...)
hashArray = append(hashArray , iToB(h5)...)
hashArray = append(hashArray , iToB(h6)...)
hashArray = append(hashArray , iToB(h7)...)
return hashArray
}
func iToB(i uint32) []byte {
bs := make([]byte, 4)
binary.BigEndian.PutUint32(bs, i)
return bs
}
//循环右移函数
func rightRotate(n uint32, d uint) uint32 {
return (n >> d) | (n << (32 - d))
}
以上是关于sha256加密原理及代码实现的主要内容,如果未能解决你的问题,请参考以下文章