SHA-256算法中,使用64个4字节整型常量。这些常量表示前64个质数立方根小数部分的前32位。
要取立方根小数部分,需要了解double类型(8字节==64位)的内存布局,对应标准IEEE 754,以下为double类型64位从高到低的解释
- 1 bit 符号位 S
- 11 bit 阶码位 E
- 52 bit 小数位 M
转为十进制的公式:
( − 1 ) S × 2 E − 1023 × [ 1 + ∑ i = 1 52 ( M i × 2 − i ) ] (-1)^S \times 2^{E-1023} \times [1+\sum_{i=1}^{52}(M_i \times 2^{-i}) ] (−1)S×2E−1023×[1+i=1∑52(Mi×2−i)]
以质数2为例,它的小数部分如下:
0.259921049894873 ≈ 4 × 1 6 − 1 + 2 × 1 6 − 2 + 8 × 1 6 − 3 + . . . 0.259921049894873 \approx 4 \times 16 ^{-1} + 2 \times 16 ^{-2} + 8 \times 16 ^{-3} + ... 0.259921049894873≈4×16−1+2×16−2+8×16−3+...
对应的十六进制为0x428a2f98。
需要注意的则是阶码部分,转换为十进值是乘以2的次方,反过来则是除以2的次方(右移)。
C
#include <stdio.h>
#include <math.h>
int main(int argc, char* argv[])
{
int arrPrimeNum[80] = {0};
int i = 0, j = 0;
double dNum = 0;
char bFlag = 0;
int nCountPrime = 0;
int nNum = 1;
unsigned int nTmp = 0;
long long int llNum = 0;
while (80 != nCountPrime)
{
nNum += 1;
bFlag |= 1;
j = sqrt(nNum);
for (i = 2; i <= j; ++i)
{
if (nNum % i == 0)
{
bFlag &= 0;
break;
}
}
if (bFlag)
{
arrPrimeNum[nCountPrime++] = nNum;
}
}
for (i = 0; i < nCountPrime; ++i)
{
dNum = exp(log(arrPrimeNum[i]) / 3);
llNum = *(long long*)&dNum;
/*
IEEE 754
fractional part: 0-51 from left to right
SHA-256 need 32-bit word
>> (52-4*8) == 20
SHA-512 need 64-bit word
Big integer library is needed to implement
eg.
arrPrimeNum[0] == 2
step code == b01111111111-1023 == 0
shift right (20 - 0)
0 01111111111 01000010 10001010 00101111 10011000 11010111 00101000 1011
01000010 10001010 00101111 10011000
arrPrimeNum[4] == 11
step code == b10000000000-1023 == 1
shift right (20 - 1)
0 10000000000 00011100 10101011 01100001 00101101 11111001 10100100 0110
0011100 10101011 01100001 00101101 1
*/
nTmp = 20 - (((llNum >> 52) & 0x7FF) - 0x3FF );
printf("0x%08llx, ", (llNum >> nTmp) & 0xFFFFFFFF);
if ((i + 1) % 8 == 0)
{
printf("\n");
}
}
getchar();
return 0;
}
python
上面用c语言生成了sha-256的4字节常数序列,而sha-512的常数序列与之类似,使用80个8字节整型常量,这些常量表示前80个质数立方根小数部分的前64位。
小数部分取64位,就不能利用double类型实现了,因为double类型一共就64位,根据IEEE 754,小数部分只有52位。所以需要借助大数库来生成sha-512的常数序列,这里借助gmpy库来实现。
import gmpy2
from gmpy2 import mpfr, floor, next_prime
def convert_primes_cube_fractional_part_to_hex_constant(prime, hex_chars=8):
"""
Note if you want the first 8 decimal (base=10) digits of a number,
you multiply the fractional part by 10**8 and then look at the integer part
In this case we want first 8 hex digits, so multiply fractional part by 16**8
and then look at integer part (and return in hexadecimal).
"""
cube_root = mpfr(prime)**(1/mpfr(3))
frac_part = cube_root - floor(cube_root)
# format_str = '%%0%dx' % hex_chars
# format_str will be '%08x' if hex_chars=8 so always emits
# 8 zero-padded hex digits
# return format_str % floor(frac_part*(16**hex_chars))
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
# SHA-256 constants
# for i,p in enumerate(generate_n_primes(64)):
# if i % 8 == 0:
# print("")
# print(convert_primes_cube_fractional_part_to_hex_constant(p, hex_chars=8), end=" ")
# SHA-512 constants
gmpy2.get_context().precision=100
for i,p in enumerate(generate_n_primes(80)):
if i % 4 == 0:
print("")
print(convert_primes_cube_fractional_part_to_hex_constant(p, hex_chars=16), end=" ")
参考资料
https://crypto.stackexchange.com/questions/41496/how-to-generate-the-sha-2-constants