概述
单向散列函数,常称为哈希函数,为消息产生一个“指纹”,用来检测消息的完整性。常见的哈希算法有MD5、SHA2家族(SHA256/SHA384/SHA512)和SM3等。
单向散列函数具有以下性质:
- 输入长度可变,输出长度固定
- 高效率,能够快速计算出哈希值
- 消息不同,散列值也不同
- 单向性,不能通过哈希值推算出消息
应用场景
假定Alice要向Bob传送表白信,具体过程如下:
- Alice准备好表白信
- Alice使用哈希函数对该信计算消息摘要
- Alice将信和消息摘要一并发给Bob
- Bob收到信后使用相同的哈希函数计算摘要值
- Bob比对收到的消息摘要和计算的消息摘要是否一致,一致则说明信未被篡改
无法解决的问题
假设主动攻击者Mallory伪装成Alice,向Bob发送了另外一封信及其散列值,Bob只能通过哈希函数检查这封信的完整性,但是无法判断发送者的身份到底是不是Alice。也即单向散列函数能够辨别出“篡改”,但无法辨别出“伪装”。
当Bob需要确认这封信是否真的属于Alice时,仅靠完整性检测是不够的,还需要进行认证。用于认证的技术包括消息认证码和数字签名。
SHA256
SHA256哈希算法的输入为小于264比特长度的任意消息,分组长度为512比特(64字节),消息摘要的长度为固定256比特(32字节)。SHA256算法的规范参考NIST FIPS 180-4,接下来介绍一下算法的原理,主要包括预处理和哈希计算两个阶段。
函数和常量
SHA256计算过程涉及到一些变换操作:
其中运算符的描述定义如下:
SHA256在计算过程中还会用到一些常量值,K0(256),K1(256),…K63(256),16进制表示如下:
预处理
预处理包括三个步骤:对消息进行填充,将消息分隔成若干块,设置初始的哈希值。
消息填充
填充的目的是保证消息长度为512比特的整数倍。假定消息M
的长度为l
比特,消息填充是依次将一个比特1,k
个比特0,消息长度l
的二进制表示(64比特)追加到消息后面,并需要满足以下条件:
l
+
1
+
k
≡
448
m
o
d
512
l+1+k\equiv448mod512
l+1+k≡448mod512
例如,3个8位ASCII字符组成的消息abc
,消息原始长度为3*8=24比特,因此首先在abc
后填充一个比特1,然后是448-(24+1)=423个比特0,最后是消息长度24比特的二进制表示,长度为64比特,填充之后的消息就满足是512比特的整数倍:
消息分隔
消息填充后,需要将其分隔成N个512比特的块,M(1),M(2),…M(N)。由于512比特块可以由16个32位的字组成,因此第i
个消息块可以表示成为:
M
(
i
)
=
(
M
0
(
i
)
M
1
(
i
)
…
M
15
(
i
)
)
M^{(i)}=(M^{(i)}_{0}M^{(i)}_{1}…M^{(i)}_{15})
M(i)=(M0(i)M1(i)…M15(i))
设置初始哈希值
对于SHA256算法,初始哈希值H(0)为8个32位比特的值,16进制表示如下:
哈希计算
预处理完成后,开始进行哈希计算,SHA256算法使用64个32位的变量进行消息调度,8个32位的变量作为工作寄存器以及8个32位的变量存储中间哈希值,SHA256算法的最终结果为256比特的消息摘要值。
消息调度的变量表示为W0,W1,…W63,8个工作寄存器表示为a,b,c,d,e,f,g,h,中间哈希值表示为H0(i),Hi(i),…H7(i)。
消息调度
消息调度使用预处理阶段分隔的消息块,将每个消息块M(i)作为输入,即16个32位的字:
可以看出,W0,W1,…W15为原始的输入消息块,其余的W15,W16,…W63是通过前面的值计算的。
初始化工作寄存器
初始化8个32位的工作寄存器a,b,c,d,e,f,g,h。其值等于上一次的哈希值:
当i=1时,工作寄存器的值为预处理阶段设置的初始哈希值。
更新工作寄存器
初始化工作寄存器后,需要对其进行更新,总共为64轮,计算过程如下:
计算中间哈希值
更新工作寄存器后,可通过8个工作寄存器的值计算出中间哈希值:
重复执行以上4个步骤,直至完成N个消息块的哈希计算,最终的消息摘要值为最后一次的中间哈希值:
示例
使用openssl
计算消息"abc"的哈希值。
$ echo -n "abc" > msg.txt
$ openssl dgst -sha256 msg.txt
SHA2-256(msg.txt)= ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad