1. 简介
Secure Hash Algorithm(SHA)是美国联邦信息处理标准(FIPS)认证的安全散列算法,对应FIPS 180系列标准。
SHA1:
- 于4/17/1995发布,取代SHA0 (FIPS 180);
- 设计上借鉴了MD4/5;
- 摘要长度:160bits == 20 bytes, H0~H4,比MD5多4个字节(1 word);
- 消息长度:<2^64 bits
- 分组长度:512 bits == 64 bytes
- 字(word)长:32 bits == 4 bytes
- 运算步骤数:80,比MD5多16步;
- 强抗碰撞性于2005年被王小云攻破;
2. 算法流程
本部分参考FIPS 180-4整理,标号与文档保持一致。
4. FUNCTIONS AND CONSTANTS
4.1.1 SHA-1 FUNCTIONS
// f(x,y,z)
// x, y, z : 4-byte words
// t: operation index
#define CH(x,y,z) ((x & y) ^ (~x & z)) /** 0 <= t <= 19 **/
#define PARITY(x,y,z) (x ^ y ^ z) /** 20 <= t <= 39 and 60 <= t <= 79 **/
#define MAJ(x,y,z) ((x & y) ^ (x & z) ^ (y & z)) /** 40 <= t <= 59 **/
在FIPS 180-1中,宏函数命名为F0, F1, F2, F3
, FIPS 180-2才使用了上述3个命名。
SHA1一共80轮运算,参数t
即代表当前是第几轮。
4.2.1 SHA-1 CONSTANTS
#define Kx 0x5a827999 /** 0 <= t <= 19 **/
#define Ky 0x6ed9eba1 /** 20 <= t <= 39 **/
#define Kz 0x8f1bbcdc /** 40 <= t <= 59 **/
#define Kw 0xca62c1d6 /** 60 <= t <= 79 **/
5. PREPROCESSING
5.1 Padding the Message
该部分填充方法,适用于MD5, SHA-1, SHA-224 and SHA-256。
第一步,填充数据,使位长度比512的倍数小64。
bitsLen == 448 mod 512
bytesLen == 56 mod 64
填充方法:
- 一个1,后续用0;
- 至少填充1 bit,至多512 bits (64 bytes)。
第二步,添加64 bits消息长度。
经过这一步,消息长度就是512 bits (16 words)的倍数了。
以消息"abcde"为例:
61626364 65
经过前两步后,数据如下:
61626364 65800000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000028.
5.2 Parsing the Message
对于SHA-1, SHA-224,SHA-256,可将数据分为N个长512 bit的分组(block),每块可根据个人编码习惯表示为如下形式:
#define BLOCK_SIZE 64
uint8_t M[BLOCK_SIZE];
uint32_t M[BLOCK_SIZE/4]; // 16 32-bit words,
5.3 Setting the Initial Hash Value
uint32_t h[5];
h[0] = 0x67452301;
h[1] = 0xefcdab89;
h[2] = 0x98badcfe;
h[3] = 0x10325476;
h[4] = 0xc3d2e1f0;
该初始hash会随之数据块的处理而更新,最终结果就是sha1。
FIPS 180将它们的副本命名为a,b,c,d,e
。
6.1.2 SHA-1 Hash Computation
Prepare the message schedule
根据64字节数据块生成数组W:
uint32_t W[80] = {0};
if ( 0 <= t && t <= 15)
{
W[t] = M[t];
}
else if (16 <= t && t <= 79)
{
W[t]=ROTL1(
W[t-3]
^ W[t-8]
^ W[t-14]
^ W[t-16]
);
}
W数组长80,但在实际运行中,16个元素足以,可以优化一下:
// 可以把W想象成一个滑动窗口
uint32_t W[16] = {0};
if ( 0 <= t && t <= 15)
{
W[t] = M[t];
}
else if (16 <= t && t <= 79)
{
// index = t mod 0xFF
W[t&0xFF]=ROTL1(
W[(t-3)&0xFF]
^ W[(t-8)&0xFF]
^ W[(t-14)&0xFF]
^ W[t&0xFF]
);
}
这里处理时有个注意事项。消息块M应该是这样定义的:
uint8_t M[64];
而W则是uint32_t类型数组。根据FIPS 180-4 3.1章节的说明,SHA算法都是按照大端序存储整型的。
3.1 Bit Strings and Integers
Throughout this specification, the “big-endian” convention is used when expressing
both 32- and 64-bit words, so that within each word, the most significant bit is stored
in the left-most bit position.
Initialize the five working variables
a = h[0];
b = h[1];
c = h[2];
d = h[3];
e = h[4];
80 x f(x,y,z)
伪代码:
For t=0 to 79:
{
Temp = ROTL5(a) + f(b,c,d) + e + Kt + W[t];
e = d;
d = c;
c = ROTL30(b); // 循环左移
b = a;
a = Temp;
}
80次运算,根据W数组和f函数(常数K与f分段一致),可以分成5轮:
W[t] = M[t]
F = CH
Round 0 -15
W[t]=ROTL1(...)
F = CH
Round 16-19
F = PARITY
Round 20-39
F = MAJ
Round 40-59
F = PARITY
Round 60-79
Compute the intermediate hash value
80次运算后,更新中间hash值:
h[0] += a;
h[1] += b;
h[2] += c;
h[3] += d;
h[4] += e;
拼接输出sha1
处理完所有消息后,拼接h数组即为最终sha1:
for (i=0; i < 5; ++i) {
printf("%02X", h[i]);
}
3. 实现
https://github.com/C0deStarr/CryptoImp/tree/main/Hash/sha
参考资料
https://csrc.nist.gov/publications/detail/fips/180/4/final
https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf