SHA2算法笔记

1. 简介

SHA2:

  • 于2002年8月发布于FIPS 180-2,取代SHA1 (FIPS 180-1),增加了SHA-256、SHA-512、SHA-384;
  • 2004年2月,发布FIPS 180-2 (with Change Notice 1),增加SHA-224,以适应3DES密钥长度;
  • 2015年8月,FIPS 180-4增加了SHA-512/224, SHA-512/256。

以下为SHA系列算法表格,长度单位bits:

AlgorithmMessage SizeBlock SizeWord SizeMessage Digest SizeSteps
SHA-1< 2^645123216080
SHA-224< 2^645123222464
SHA-256< 2^645123225664
SHA-384< 2^12810246438480
SHA-512< 2^12810246451280
SHA-512/224< 2^12810246422480
SHA-512/256< 2^12810246425680

2. 算法流程

SHA2家族的流程可以套用同一个模板。

本部分参考FIPS 180-4整理,标号与文档保持一致。

4.1 FUNCTIONS

公用部分:

#define CH(x,y,z)       (((x) & (y)) ^ (~(x) & (z)))
#define MAJ(x,y,z)      (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))

#define ROTR32(n, x)    (((x)>>(n)) | ((x)<<(32-(n))))
#define ROTR64(n, x)    (((x)>>(n)) | ((x)<<(64-(n))))
#define SHR(n,x)        ((x)>>(n))

它们各有4个sigma函数,区别在于字的位数和移位位数。

4.1.2 SHA-224 and SHA-256 Functions

#define ROTR32(n, x)    (((x)>>(n)) | ((x)<<(32-(n))))
#define SHR(n,x)        ((x)>>(n))
#define SIGMA_0_256(x)    (ROTR32(2,x)  ^ ROTR32(13,x) ^ ROTR32(22,x))
#define SIGMA_1_256(x)    (ROTR32(6,x)  ^ ROTR32(11,x) ^ ROTR32(25,x))
#define sigma_0_256(x)    (ROTR32(7,x)  ^ ROTR32(18,x) ^ SHR(3,x))
#define sigma_1_256(x)    (ROTR32(17,x) ^ ROTR32(19,x) ^ SHR(10,x))

4.1.3 SHA-384, SHA-512, SHA-512/224 and SHA-512/256 Functions

#define SIGMA_0_512(x)    (ROTR64(28,x) ^ ROTR64(34,x) ^ ROTR64(39,x))
#define SIGMA_1_512(x)    (ROTR64(14,x) ^ ROTR64(18,x) ^ ROTR64(41,x))
#define sigma_0_512(x)    (ROTR64(1,x)  ^ ROTR64(8,x)  ^ SHR(7,x))
#define sigma_1_512(x)    (ROTR64(19,x) ^ ROTR64(61,x) ^ SHR(6,x))

4.2 CONSTANTS

4.2.2 SHA-224 and SHA-256 Constants

使用64个4字节整型常量。这些常量表示前64个质数立方根小数部分的前32位,以提高随机性。

4.2.3 SHA-384, SHA-512, SHA-512/224 and SHA-512/256 Constants

使用80个8字节整型常量。这些常量表示前80个质数立方根小数部分的前64位。

如何生成这些常数序列,可参考我的另一篇文章:如何生成SHA2常数序列_CodeStarr的博客

5. PREPROCESSING

5.1 Padding the Message

SHA-224 和SHA-256的填充方法与SHA1完全一致。而SHA-512系列和它们的区别是位数需要加倍。

第一步,填充数据,使位长度比1024的倍数小128。

bitsLen == 896 mod 1024
bytesLen == 112 mod 128

填充方法:

  1. 一个1,后续用0;
  2. 至少填充1 bit,至多1024 bits (128 bytes)。

第二步,添加128 bits消息长度。

经过这一步,消息长度就是1024 bits (128 bytes == 32 words)的倍数了。

代码层面,SHA-512要使用unsigned long long[2]类型。

5.2 Parsing the Message

SHA-224 和SHA-256的消息处理方法与SHA1完全一致,分块长512 bit。

而SHA-512系列分块长度加倍为1024 bits即可。

在代码层面,消息是以单字节数组形式存储的,在计算时,则是需要以大端序转换成4字节或8字节整型。

5.3 Setting the Initial Hash Value

SHA2的hash数组长度都是8,最后拼接为hash值,只不过需要区分字长。

#if WORD_SIZE 4
typedef uint32_t sha2_word_t;
#elif WORD_SIZE==8
typedef uint64_t sha2_word_t;
#endif

sha2_word_t h[8] = {
#ifdef SHA224
    0xc1059ed8,
    0x367cd507,
    0x3070dd17,
    0xf70e5939,
    0xffc00b31,
    0x68581511,
    0x64f98fa7,
    0xbefa4fa4
#elif SHA256
	0x6a09e667,
    0xbb67ae85,
    0x3c6ef372,
    0xa54ff53a,
    0x510e527f,
    0x9b05688c,
    0x1f83d9ab,
    0x5be0cd19
#elif SHA384
    0xcbbb9d5dc1059ed8ULL,
    0x629a292a367cd507ULL,
    0x9159015a3070dd17ULL,
    0x152fecd8f70e5939ULL,
    0x67332667ffc00b31ULL,
    0x8eb44a8768581511ULL,
    0xdb0c2e0d64f98fa7ULL,
    0x47b5481dbefa4fa4ULL
#elif SHA512
	0x6a09e667f3bcc908ULL,
    0xbb67ae8584caa73bULL,
    0x3c6ef372fe94f82bULL,
    0xa54ff53a5f1d36f1ULL,
    0x510e527fade682d1ULL,
    0x9b05688c2b3e6c1fULL,
    0x1f83d9abfb41bd6bULL,
    0x5be0cd19137e2179ULL
#elif SHA512_224
    0x8C3D37C819544DA2ULL,
    0x73E1996689DCD4D6ULL,
    0x1DFAB7AE32FF9C82ULL,
    0x679DD514582F9FCFULL,
    0x0F6D2B697BD44DA8ULL,
    0x77E36F7304C48942ULL,
    0x3F9D85A86A1D36C8ULL,
    0x1112E6AD91D692A1ULL
#elif SHA512_256
	0x22312194FC2BF72CULL,
    0x9F555FA3C84C64C2ULL,
    0x2393B86B6F53B151ULL,
    0x963877195940EABDULL,
    0x96283EE2A88EFFE3ULL,
    0xBE5E1E2553863992ULL,
    0x2B0199FC2C85B8AAULL,
    0x0EB72DDC81C52CA2ULL
#endif
};

	

其中,SHA-384的初始值是取第9到16个素数的平方根的小数部分获取的(与常数部分类似),生成脚本如下:

import gmpy2
from gmpy2 import mpfr, floor, next_prime
def convert_primes_root_fractional_part_to_hex_constant(prime, hex_chars=8):

    cube_root = mpfr(prime)**(1/mpfr(2))
    frac_part = cube_root - floor(cube_root)

    strRet = "{0:1a}".format(floor(frac_part*(16**hex_chars)))
    # 0xc.19bf174p+28
    strRet = strRet[2:]
    strRet = strRet.replace(".","")
    # c19bf174p+28
    strRet = strRet[:strRet.index("p+")]
    # c19bf174
    while(len(strRet) < 8):
        strRet = "0" + strRet
    return strRet;


def generate_n_primes(n=64):
    p = 2
    i = 0
    while i < n:
        yield p
        p = next_prime(p)
        i += 1


gmpy2.get_context().precision=100
listPrimes = list(enumerate(generate_n_primes(16), 8))
# print(listPrimes)
for i,p in listPrimes[8:]:
    if i % 4 == 0:
        print("")
    print(convert_primes_root_fractional_part_to_hex_constant(p, hex_chars=16), end=" ")

而SHA-512/224和SHA-512/256的初始值是通过SHA-512/t IV Generation Function计算出来的:

for i in range(8):
	new_iv[i] = H_sha512[i] ^ 0xa5a5a5a5a5a5a5a5;
	h_512_t[i] = sha512("SHA-512/t") # using new_iv[8] as IV[8]

生成代码: https://github.com/C0deStarr/CryptoImp/tree/main/Hash/sha/sha512.c sha512_t_iv_generator()函数。

6.2.2 SHA-256 Hash Computation

Prepare the message schedule

#define SCHEDULE(t) (sigma_1_256(W[t-2])    \
    + W[t-7]        \
    + sigma_0_256(W[t-15])  \
    + W[t-16])

Initialize the eight working variables

a = h[0];
b = h[1];
c = h[2];
d = h[3];
e = h[4];
f = h[5];
g = h[6];
h = h[7];

64 Steps

与SHA1类似,区别是SHA1有80轮,而SHA256有64轮。

uint32_t W[64] = {0};

if ( 0 <= t && t <= 15)
{
    W[t] = M[t];
}
else if (16 <= t && t <= 63)
{
    T1 = h + SIGMA_1_256(e) + CH(e,f,g) + K[t]  + W[t]; 
    T2 = SIGMA_0_256(a) + MAJ(a,b,c); 
    h = g
    g = f
    f = e
    e = d + T1
    d = c
    c = b
    b = a
    a = T1 + T2 
}

需要强调,W是uint32_t类型数组,M则是单字节数组,SHA算法是按照大端序存储整型的。

另外对于t>=16的情况,其实可以优化。忽略掉变量置换以及临时变量T1、T2,只关注加法,其实可以看做如下逻辑:

#define CYCLE(a,b,c,d,e,f,g,h,t) \
    h += SIGMA_1_256(e) + CH(e,f,g) + K[t]  + W[t]; \
    d += h; \
    h += SIGMA_0_256(a) + MAJ(a,b,c);

而所有64步,只需要不停变换参数即可实现置换的效果:

CYCLE(a,b,c,d,e,f,g,h, 0);
CYCLE(h,a,b,c,d,e,f,g, 1);
//...
CYCLE(b,c,d,e,f,g,h,a, 63);

常量的个数与计算步骤数是一致的,每一次计算都会取用K[t]。在SHA-512中,有80个常量,所以有80轮计算。

Compute the intermediate hash value

h[0] += a;
h[1] += b;
h[2] += c;
h[3] += d;
h[4] += e;
h[5] += f;
h[6] += g;
h[7] += h;

3. 实现

SHA-224可以嵌套调用SHA-256,SHA-384, SHA-512/224, SHA-512/256也可以嵌套调用SHA-512。

https://github.com/C0deStarr/CryptoImp/tree/main/Hash/sha

参考资料

https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf

https://csrc.nist.gov/publications/detail/fips/180/2/archive/2002-08-01

https://csrc.nist.gov/publications/detail/fips/180/2/archive/2004-02-25

https://csrc.nist.gov/CSRC/media/Publications/fips/180/2/archive/2002-08-01/documents/fips180-2.pdf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值