复习SHA。很久以前实现过,没记笔记,现在又忘光了。
SecureHashAlgorithm
概要
关于
SHA-1
SHA-384
SHA-256
SHA-512
算法的规定与实现。
fips
标准是几个混在一起讲的。
规范
参数名
$$
a,b,c,…h
$$
临时变量,是w
比特位宽的,在计算哈希值H
时使用。
$$
H^{(i)}
$$
第i
个哈希值H
。因为哈希计算需要多轮。H^{0}
是初始哈希值。H^{N}
是最终哈希值。
$$
H^{(i)}_{j}
$$
从左往右,一个哈希值H^{i}
中第j
个字。
$$
K_t
$$
用于t
迭代的常量值。
$$
k
$$padding
时需要添加0的个数。
$$
l
$$
消息M
的比特长度。
$$
m
$$
一个消息需要拆成若干定长消息块分别进行哈希。m
为一个消息块的比特长度。
$$
M
$$
待哈希的消息。
$$
M^{(i)}
$$
第i
个消息块。
$$
M^{(i)}_j
$$
第i
个消息块中从左到右的第j
个字。
$$
n
$$
出现于ROTL(x, n)
等运算中,表示了要进行位移或旋转的比特个数。
$$
N
$$
消息需要拆成消息块,但是消息的长度不一定是消息块长度的整数倍。所以需要padding
填充0。
N
代表了填充后消息块的个数。
$$
T
$$
临时变量,w
比特位宽的字。
$$
w
$$
一个字的比特个数。一般是32(SHA1,SHA256)或64(SHA384,SHA512)
$$
W_t
$$
消息队列(message schedule)中的第t
个字。
比特串与整数
1 | 0b0000-0b1111 = 0x0-0xF = '0'-'F' |
比如32位比特串
1 | 1010 0001 0000 0011 1111 1110 0010 0011 |
可以表示为
1 | "a103fe23" |
$$
0\le Z\lt 2^{64},Z=2^{32}X+Y
$$
64位长整型数Z
可以表示为两个32位整数X
与Y
的拼接,即可记为一对(x, y)
。此方法用于SHA1与SHA256。
同理,128位可以拆成2个64位。用于SHA384与SHA512。
位宽与消息块长度
- SHA1与SHA256:消息块长
m
为512位,64字节;字长w
为32位,4字节。 - SHA384与SHA512:消息块长
m
为1024位,128字节;字长w
为64位,8字节。
字上的操作
即以一个字为单元的运算。即一般C语言运算。
1 | // 位宽32 |
SHA1
函数
3个函数Ch
,Parity
,Maj
;由func_t
函数封装。
1 | // SHA-1函数 |
常量
使用了80个32位整数,可以在C语言中被定义为
1 | // 返回SHA-1常量 |
预处理
填充(padding)
设消息M
的长度是l
。
添加比特1,
然后添加k
个0比特,其中k
要满足
$$
l+1+k=448\ mod\ 512
$$
此式最小的非负整数。
然后再添加64比特长的len(l)
。
比如M
为abc
,则len(l)
为3*8=24,即添加0b11000(前面得跟上若干0比特来凑成64位长整型)。
实现:
1 | // 返回`M`的比特长度 |
解析
padded message
还需要拆分成若干512比特长的消息块
$$
M^{(N)}
$$
每个消息块又有16个字,所以每个字可以从左往右记作
$$
M^{(N)}_j
$$
我的实现:
1 | typedef struct { |
初始化哈希值
对于SHA1,哈希值由5个32位字组成。
1 | typedef struct { |
哈希运算
遍历每个消息块
$$
M^{(1)},M^{(2)},…,M^{(N)}
$$
1 | for(int i=0; i<N; i++) |
1
初始化W_t
1 | // 递归版,符合fips原标准,但是效率太低 |
1 | // 打表法,无递归,速度显著提升 |
2
初始化5个工作变量a
,b
,c
,d
,e
,使用第i-1
个哈希值
1 | a = H.h[0]; |
3
1 | For t = 0 to 79: |
1 | for(int t=0; t<=79; t++){ |
4
更新哈希值
$$
H^{(i)}=a+H^{(i-1)}
$$
1 | // update |
输出
步过N
次后,就可以输出160位的哈希值了
$$
H_0|H_1|H_2|H_3|H_4
$$
源码
纯手工打造,仅做了少量优化,效率一般
1 | /* |
SHA256
SHA256和SHA1在一些设定上类似,比如一个消息块512比特,一个字32比特。
函数
数学式子懒得再打出来了。Sigma
就是求和的意思,Σ。lSigma
是小写希腊字母σ。
1 |
|
常量
选取了前64个素数的立方根的分数部分的首32比特。
1 | uint32_t SHA256_Constants[64] = { |
预处理
填充
与SHA1一模一样
解析
与SHA1一模一样
初始化哈希值
由8个字组成。
1 | typedef struct { |
哈希运算
遍历每个消息块
$$
M^{(1)},M^{(2)},…,M^{(N)}
$$
1 | for(int i=0; i<N; i++) |
1
准备W_t
1 | uint32_t* compileW(M* pm, int i){ |
2
初始化8个变量
1 | a = H.h[0]; |
3
1 | for(int t=0; t<=63; t++){ |
4
更新哈希值
1 | H.h[0] = a + H.h[0]; |
输出
8个字拼一块就好了。
源码
1 | /* |
SHA512
函数
1 |
|
常量
1 | uint64_t K[80] = { |
预处理
$$
l+1+k=896\ mod\ 1024
$$
1 | uint64_t getK_1(const char* message){ |
哈希运算
1 | uint64_t* compileW(M* pm, int i){ |
输出
输出8个64比特字即可。
SHA384
基本与SHA256一致,除了初始化哈希值,以及最后输出的比特数。
常量
1 | typedef struct { |
输出
只需要输出前面6个64比特字即可。
源码
1 | /* |
参考
https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
fips180-2
- 本文作者: Taardis
- 本文链接: https://taardisaa.github.io/2022/03/17/SHA/
- 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!