SHA 算法的原理及实现
章节目录
- 简介
- 算法描述
2.1 数据准备
2.1.1 数据填充
2.1.2 数据分块
2.1.3 设置初始 Hash 值
2.2 Hash 计算
2.2.1 SHA-1
2.2.2 SHA-256
2.2.3 SHA-512
- 实现
作者能力有限, 如果您在阅读过程中发现任何错误, 还请您务必联系本人,指出错误, 避免后来读者再学习错误的知识.谢谢!
SHA 算法(英语:Secure Hash Algorithm,缩写为SHA)是一个密码散列函数家族,是FIPS所认证的安全散列算法。能计算出一个数字消息所对应到的,长度固定的字符串(又称消息摘要)的算法。且若输入的消息不同,它们对应到不同字符串的机率很高。
本文我们将介绍以下 SHA 算法: SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256.
其中 SHA-224 和 SHA-256 使用相同的算法, 区别在于初始 Hash 值不同, 最终结果只使用算法输出的数据中的前224/256 bit.
SHA-384, SHA-512, SHA-512/224, SHA-512/256 使用相同的算法, 区别在于初始 Hash 值不同, 最终结果只使用算法输出的数据中的前384/512/224/256 bit.
而 SHA-2* 和 SHA-384,SHA-5* 算法也非常类似, 区别在于采用的字(Word) 长度不同, SHA-2*使用 32-bit 的字, 而 其他算法使用 64-bit 的字. 算法的迭代次数也不一样.
本文中将介绍的 SHA 算法的计算步骤从大体上可以分为两步: 数据准备 和 Hash 计算.
在数据准备阶段, 我们也像 MD5 那样, 需要先将数据填充到特定长度,同时将原始数据长度填充进去,然后对数据进行分块, 因为我们的算法是基于块进行的. SHA 家族中的具体算法的实现大体相同, 只是填充长度的bit数,分块大小略有不同而已.
在数据准备阶段我们需要进行三个操作: 数据填充, 数据分块, 设置初始 Hash 值.
我们使用
M 表示数据数据, 它的长度使用
l 表示.
对于算法 SHA-1, SHA-224, SHA-256, 数据填充方法如下:
先填充 1 bit 的 ‘1’ 到数据末尾, 然后紧接着填充 k 个 ‘0’, 这里 k 需要时最小的非负数且满足
l+1+k≡448mod512, 也即是说需要将原始数据长度填充到差64位就是512的整数倍.
上述操作结束后, 将
l 表示为 64 bit 的bit数组填充到上述步骤所得的数据之后, 此时我们得到一个长度为512整数倍的数据.
举个例子:
假设我们的数据数据为"abc", 它的长度为24(bit). 我们通过计算得到 k 应该是 423(448 - 1 - 24). 此时填充之后的数据应该如下:
a
01100001b
01100010c
01100011100...00
42300...0t=24
11000
64
填充完成之后的长度是512(bit).
扫描二维码关注公众号,回复:
10924839 查看本文章
对于算法 SHA-384, SHA-512, SHA-512/224, SHA-512/256, 数据填充方法如下:
先填充 1 bit 的 ‘1’ 到数据末尾, 然后紧接着填充 k 个 ‘0’, 这里 k 需要时最小的非负数且满足
l+1+k≡896mod1024, 也即是说需要将原始数据长度填充到差128位就是1024的整数倍.
上述操作结束后, 将
l 表示为 128 bit 的bit数组填充到上述步骤所得的数据之后, 此时我们得到一个长度为1024整数倍的数据.
以上述的例子为例:
假设我们的数据数据为"abc", 它的长度为24(bit). 我们通过计算得到 k 应该是 871(896 - 1 - 24). 此时填充之后的数据应该如下:
a
01100001b
01100010c
01100011100...00
87100...0t=24
11000
128
填充完成之后的长度是1024(bit).
填充后的数据需要被分块.
对于算法 SHA-1, SHA-224, SHA-256,
我们将数据分为
N 个 521-bit 的块, 分别表示为
M(1),M(2),...,M(N)
512-bit 的块又可以被划分为 16 个字(32-bit Word), 分别表示为
M0(i),M1(i),...,M15(i)
对于算法 SHA-384, SHA-512, SHA-512/224, SHA-512/256
我们将数据分为
N 个 1024-bit 的块, 分别表示为
M(1),M(2),...,M(N)
1024-bit 的块又可以被划分为 16 个字(64-bit Word), 分别表示为
M0(i),M1(i),...,M15(i)
每个特定的 SHA 算法, 都有相应的初始 Hash 值. 在计算 Hash 之前, 我们需要先将初始值准备好.
为了减少文章篇幅, 这里我们不列出这些初始值和 Hash 计算过程中使用到的常量
K,后边算法实现中会给出相应数据.
SHA-1 算法要求输入数据的长度不能大于
264, 最小长度为0.
伪代码如下:
ROTLn(x)=(x<<n)∪(x>>w−n)
ft(x,y,z) =
⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧Ch(x,y,z)=(x∩y)⨁(¬x∩z),0≤t≤19Parity(x,y,z)=x⨁y⨁z,20≤t≤39Maj(x,y,z)=(x∩y)⨁(x∩z)⨁(y∩z),40≤t≤59Parity(x,y,z)=x⨁y⨁z,60≤t≤79
For i=1 to N:
{
//1. 计算
Wt
Wt =
⎩⎪⎨⎪⎧Mt(0),0≤t≤15ROTL1(Wt−3⨁Wt−8⨁Wt−14⨁Wt−16),16≤t≤79
//2. 初始化工作变量 a, b, c, d, e. 他们用来存储在第 i-1 次迭代式的 Hash 值
// 他们的初始值就是我们在"设置初始 Hash 值"小节中所说的值.
a=H0i−1b=H1i−1c=H2i−1d=H3i−1e=H4i−1
// 3
For t=0 to 79:
{
T=ROTL5(a)+ft(b,c,d)+e+Kt+Wte=dd=cc=ROTL30(b)b=aa=T
}
//4. 计算第
ith 中间 hash 值
Hi
H0(i)=a+H0(i−1)H1(i)=b+H1(i−1)H2(i)=c+H2(i−1)H3(i)=d+H3(i−1)H4(i)=e+H4(i−1)
}
在经过 N 次迭代之后, 最终结果为
H0(N),H1(N),H2(N),H3(N),H4(N) 的字节表示依次连接所组成的字节数组.
SHA-256 算法要求输入数据的长度不能大于
264, 最小长度为0.
SHA-224 算法的计算过程与 SHA-256 相同, 却别在于使用的初始化 Hash 值不同, 且 SHA-224 算法的最终结果是取 SHA-256 算法结果的前 224 bit.
伪代码如下:
SHRn(x)=x>>n
ROTRn(x)=(x>>n)∪(x<<w−n)
Σ0{256}(x)=ROTR2(x)⨁ROTR13(x)⨁ROTR22(x)
Σ1{256}(x)=ROTR6(x)⨁ROTR11(x)⨁ROTR25(x)
σ0{256}(x)=ROTR7(x)⨁ROTR18(x)⨁SHR3(x)
σ1{256}(x)=ROTR17(x)⨁ROTR19(x)⨁SHR10(x)
For i=1 to N:
{
//1. 计算
Wt
Wt =
⎩⎪⎨⎪⎧Mt(0),0≤t≤15σ1{256}(Wt−2)+Wt−7+σ0{256}(Wt−15)+Wt−16,16≤t≤63
//2. 初始化工作变量 a, b, c, d, e, f, g, h. 他们用来存储在第 i-1 次迭代式的 Hash 值
// 他们的初始值就是我们在"设置初始 Hash 值"小节中所说的值.
a=H0i−1b=H1i−1c=H2i−1d=H3i−1e=H4i−1f=H5i−1g=H6i−1h=H7i−1
// 3
For t=0 to 63:
{
T1=h+Σ1{256}(e)+Ch(e,f,g)+K{256}t+WtT2=Σ0{256}(a)+Maj(a,b,c)h=gg=ff=ee=d+T1d=cc=bb=aa=T1+T2
}
//4. 计算第
ith 中间 hash 值
Hi
H0(i)=a+H0(i−1)H1(i)=b+H1(i−1)H2(i)=c+H2(i−1)H3(i)=d+H3(i−1)H4(i)=e+H4(i−1)H5(i)=b+H5(i−1)H6(i)=c+H6(i−1)H7(i)=d+H7(i−1)
}
在经过 N 次迭代之后, 最终结果为
H0(N),H1(N),H2(N),H3(N),H4(N),H5(N),H6(N),H7(N) 的字节表示依次连接所组成的字节数组.
SHA-512 算法要求输入数据的长度不能大于
2128, 最小长度为0.
SHA-384 算法的计算过程与 SHA-512 相同, 却别在于使用的初始化 Hash 值不同, 且 SHA-384 算法的最终结果是取 SHA-512 算法结果的前 384 bit.
SHA-512/224 算法的计算过程与 SHA-512 相同, 却别在于使用的初始化 Hash 值不同, 且 SHA-512/224 算法的最终结果是取 SHA-512 算法结果的前 224 bit.
SHA-512/256 算法的计算过程与 SHA-512 相同, 却别在于使用的初始化 Hash 值不同, 且 SHA-512/256 算法的最终结果是取 SHA-512 算法结果的前 256 bit.
伪代码如下:
Σ0{512}(x)=ROTR28(x)⨁ROTR34(x)⨁ROTR39(x)
Σ1{512}(x)=ROTR14(x)⨁ROTR18(x)⨁ROTR41(x)
σ0{512}(x)=ROTR1(x)⨁ROTR8(x)⨁SHR7(x)
σ1{512}(x)=ROTR19(x)⨁ROTR61(x)⨁SHR6(x)
For i=1 to N:
{
//1. 计算
Wt
Wt =
⎩⎪⎨⎪⎧Mt(0),0≤t≤15σ1{512}(Wt−2)+Wt−7+σ0{512}(Wt−15)+Wt−16,16≤t≤79
//2. 初始化工作变量 a, b, c, d, e, f, g, h. 他们用来存储在第 i-1 次迭代式的 Hash 值
// 他们的初始值就是我们在"设置初始 Hash 值"小节中所说的值.
a=H0i−1b=H1i−1c=H2i−1d=H3i−1e=H4i−1f=H5i−1g=H6i−1h=H7i−1
// 3
For t=0 to 79:
{
T1=h+Σ1{512}(e)+Ch(e,f,g)+K{512}t+WtT2=Σ0{512}(a)+Maj(a,b,c)h=gg=ff=ee=d+T1d=cc=bb=aa=T1+T2
}
//4. 计算第
ith 中间 hash 值
Hi
H0(i)=a+H0(i−1)H1(i)=b+H1(i−1)H2(i)=c+H2(i−1)H3(i)=d+H3(i−1)H4(i)=e+H4(i−1)H5(i)=b+H5(i−1)H6(i)=c+H6(i−1)H7(i)=d+H7(i−1)
}
在经过 N 次迭代之后, 最终结果为
H0(N),H1(N),H2(N),H3(N),H4(N),H5(N),H6(N),H7(N) 的字节表示依次连接所组成的字节数组.
本人使用 go 语言实现了该算法. github:https://github.com/UselezzProgrammer/mycrypto
END!