接 上一篇 博文
4. SHA-2
SHA-2,名称来自于安全散列算法2(英语:Secure Hash Algorithm 2)的缩写,一种密码散列函数(见MD5算法)算法标准,由美国国家安全局研发,由美国国家标准与技术研究院(NIST)在2001年发布。属于SHA算法之一,是SHA-1的后继者。其下又可再分为六个不同的算法标准,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。
4.1 简介
NIST发布了三个额外的SHA变体,这三个函数都将消息对应到更长的消息摘要。以它们的摘要长度(以比特计算)加在原名后面来命名:SHA-256,SHA-384和SHA-512。它们发布于2001年的FIPS PUB 180-2草稿中,随即通过审查和评论。包含SHA-1的FIPS PUB 180-2,于2002年以官方标准发布。2004年2月,发布了一次FIPS PUB 180-2的变更通知,加入了一个额外的变种SHA-224,这是为了匹配双密钥3DES所需的密钥长度而定义。
SHA-256和SHA-512是很新的散列函数,SHA-224以及SHA-384则是前述二种散列函数的截短版,利用不同的初始值做计算。
这些新的散列函数并没有接受像SHA-1一样的公众密码社群做详细的检验,所以它们的密码安全性还不被大家广泛的信任。Gilbert和Handschuh在2003年曾对这些新变种作过一些研究,声称他们没有找到弱点。
我们大概可以将SHA-2分为两类,两类使用不同的偏移量、不同的敞亮、不同的循环次数、不同的数据长度。但是两类的运算结构是相同的。
- SHA-256 和 SHA-224
其中SHA-224是SHA-256 的截短版,运算的数据都是32位(4字节),核心循环次数为64次。
- SHA-512 和 SHA-384
其中SHA-384是SHA-512 的截短版,运算的数据都是64位(8字节),核心循环次数为80次。
在下面一节会继续介绍两类运算的不同,以及相同类之间的两种算法差异。
4.2 SHA-256 算法
上面一节说过,这几种算法的结构是相同的,所以,这里使用SHA-256 算法来解释下运算结构。
1、扩充数据
同MD5 算法、SHA-1 算法,第一步还是要将数据填充为512 bits 的整数倍,也就是64字节的整数倍。这些n 段512bits(64字节)的数据会作为原始信息进行处理
2、循环运算每一段512bits(64 字节)的数据(MainLoop)
- 将512bits数据(16*4字节)扩展为64*4字节
对于SHA-256 算法是64次循环,会将16 * 4 字节(32位)扩展到64 * 4字节(32位)。
相比,SHA-512 算法是80次循环,所以会将16 * 8字节(64位)扩展到80 * 8字节(64位)。
下面是SHA-256 扩展的伪代码:
Extend the sixteen 32-bit words into sixty-four 32-bit words: for i from 16 to 63 s0 := (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor(w[i-15] rightshift 3) s1 := (w[i-2] rightrotate 17) xor (w[i-2] rightrotate 19) xor(w[i-2] rightshift 10) w[i] := w[i-16] + s0 + w[i-7] + s1
从第 17个开始进行扩展,用C 代码表示为:
#define shift(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
#define S0(x) ((shift(x, 25)) ^ (shift(x, 14)) ^ ((x) >> 3))
#define S1(x) ((shift(x, 17)) ^ (shift(x, 19)) ^ ((x) >> 10))
...
String getSHA1() {
...
...
for(unsigned int i=0;i<strlength/16;i++) //512bits 数据分段
{
unsigned int num[64];
unsigned int j;
for(j=0;j<16;j++)
num[j]=strByte[i*16+j]; //得到512bits 数据
for(j=16;j<64;j++)
num[j]=num[j-16] + S0(num[i-15]) + num[i-7] + S1(num[i-2]); //扩展到64个
mainLoop(num); //进入主循环
}
...
}
- 进入主循环,进行64次循环
这里的64次循环跟MD5 算法、SHA-1 算法 有一些区别,这里不会再分几组,而是64次都是相同的操作。
但是还是需要4个函数:
Sigma0(x)=shift(x, 30) ⊕ shift(x, 19) ⊕ shift(x, 10);
Sigma1(x)=shift(x, 26) ⊕ shift(x, 21) ⊕ shift(x, 7);
Maj(x,y,z)=(x & y) ⊕ (x & z) ⊕ (y & z);
Ch(x,y,z)=(x & y) ⊕ (~x & z);
其中,& 是与运算, | 是或运算,~ 是取反运算,⊕ 是异或运算
具体的运算流程如下图所示:
A、B、C、D 、E、F、G、H分别是上一段512bits 处理后留下来的8个整数(第一次运算的时候这8个数为固定的常数)。
在对该512bits 数据运算前需要先把这8个整数临时存起来(后面会使用到)。
表示一个32 bits(4个字节) 的输入数据(512bits 数据其中的32bits),
表示一个32bits 的常数(这个也是固定的)。
将上图运算总结成公式:
s0 = Sigma0(A);
maj = Ma(A, B, C);
s1 = Sigma1(E);
ch = Ch(E, F, G);
t1 = H + s1 + ch + k[i] + M[i];
H = G;
G = F;
F = E;
E = D + t1;
D = C;
C = B;
B = A;
A = t1 + s0 + maj;
将每一组运算后得到最新的8个数A、B、C、D、E、F、G、H作为下一组运算的初始值,一直到64 次循环彻底结束。
- 一段512bits 的64 次循环结束之后,需要为下一段512bits 准备新的A、B、C、D、E、F、G、H。
将上一段64 次循环后最终得到的A、B、C、D、E、F、G、H(也就是上面一步得到的最后的8个数)与循环之前的保存下来的初始值对应相加。
3、最后一段512bits 运算后得到最终的A、B、C、D、E,即为最终的160bits数
因为需要得到最后256bits(64 位16进制)的字符串,所以要将每个4字节的数转换成8位的16进制字符串。
注意:
1、SHA-224 与SHA-256基本相同,除了:
- 8个初始值不同
- SHA-224 输出时截掉H 值,得到最后的56位16进制字符串
2、SHA-512 与SHA-256结构是相同的,但:
- SHA-512所有数都是64位
- SHA-512运行80次循环
- SHA-512的初始值、常量值都是64位
- SHA-512中偏移量和循环中的位移量也是不同的
下面是截取的SHA-512中 几个函数:
#ifndef ROTR
#define ROTR(x, s) (((x) >> s) | (x) << (64 - s))
#endif
#define S0(x) (ROTR((x), 1) ^ ROTR((x), 8) ^ ((x) >> 7))
#define S1(x) (ROTR((x), 19) ^ ROTR((x), 61) ^ ((x) >> 6))
#define Sigma0(x) (ROTR((x), 28) ^ ROTR((x), 34) ^ ROTR((x), 39))
#define Sigma1(x) (ROTR((x), 14) ^ ROTR((x), 18) ^ ROTR((x), 41))
#define Ch(x, y, z) (((x) & (y)) ^ ((~(x)) & (z)))
#define Maj(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
3、SHA-384和SHA-512基本上是相同的,除了:
8个初始值不同
SHA-384输出时截掉F、H两个值,得到最后的96位16进制字符串
4.3 散列举例
一般128位的SHA-256 散列被表示为64位十六进制数字。以下是一个43位长的仅ASCII字母列的SHA-256散列:
SHA256("The quick brown fox jumps over the lazy dog")
= d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592
即使在原文中作一个小变化(比如将dog 换位cog,用c取代d)其散列也会发生巨大的变化:
SHA256("The quick brown fox jumps over the lazy cog")
= e4c4d8f3bf76b692de791a173e05321150f7a345b46484fe427f6acc7ecc81be
空文的散列为:
SHA256("")
= e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
4.4 SHA-256 算法用C 代码实现
#include<iostream>
#include<string>
using namespace std;
#define shift(x, n) (((x) << (n)) | ((x) >> (32-(n))))//右移的时候,高位一定要补零,而不是补充符号位
#define S0(x) ((shift(x, 25)) ^ (shift(x, 14)) ^ ((x) >> 3))
#define S1(x) ((shift(x, 17)) ^ (shift(x, 19)) ^ ((x) >> 10))
#define Sigma0(x) ((shift(x, 30)) ^ (shift(x, 19)) ^ (shift(x, 10)))
#define Sigma1(x) ((shift(x, 26)) ^ (shift(x, 21)) ^ (shift(x, 7)))
#define Ma(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
#define Ch(x,y,z) (((x) & (y)) ^ ((~x) & (z)))
#define A 0x6a09e667
#define B 0xbb67ae85
#define C 0x3c6ef372
#define D 0xa54ff53a
#define E 0x510e527f
#define F 0x9b05688c
#define G 0x1f83d9ab
#define H 0x5be0cd19
//strBaye的长度
unsigned int strlength;
//A,B,C,D的临时变量
unsigned int atemp;
unsigned int btemp;
unsigned int ctemp;
unsigned int dtemp;
unsigned int etemp;
unsigned int ftemp;
unsigned int gtemp;
unsigned int htemp;
static const uint32_t k[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b,
0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01,
0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7,
0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152,
0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08,
0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f,
0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
const char str16[]="0123456789abcdef";
void mainLoop(unsigned int M[])
{
unsigned int s0, maj, s1, ch, t;
unsigned int a = atemp;
unsigned int b = btemp;
unsigned int c = ctemp;
unsigned int d = dtemp;
unsigned int e = etemp;
unsigned int f = ftemp;
unsigned int g = gtemp;
unsigned int h = htemp;
for (unsigned int i = 0; i < 64; i++)
{
s0 = Sigma0(a);
maj = Ma(a, b, c);
s1 = Sigma1(e);
ch = Ch(e, f, g);
t1 = h + s1 + ch + k[i] + M[i];
h = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + s0 + maj;
}
atemp=a+atemp;
btemp=b+btemp;
ctemp=c+ctemp;
dtemp=d+dtemp;
etemp=e+etemp;
ftemp=f+ftemp;
gtemp=g+gtemp;
htemp=h+htemp;
}
/*
*填充函数
*处理后应满足bits≡448(mod512),字节就是bytes≡56(mode64)
*填充方式为先加一个1,其它位补零
*最后加上64位的原来长度
*/
unsigned int* add(string str)
{
unsigned int num=((str.length()+8)/64)+1;//以512位,64个字节为一组
unsigned int *strByte=new unsigned int[num*16]; //64/4=16,所以有16个整数
strlength=num*16;
for (unsigned int i = 0; i < num*16; i++)
strByte[i]=0;
for (unsigned int i=0; i <str.length(); i++)
{
strByte[i>>2]|=(str[i])<<((i%4)*8);//一个整数存储四个字节,i>>2表示i/4 一个unsigned int对应4个字节,保存4个字符信息
}
strByte[str.length()>>2]|=0x80<<(((str.length()%4))*8);//尾部添加1 一个unsigned int保存4个字符信息,所以用128左移
/*
*添加原长度,长度指位的长度,所以要乘8,然后是小端序,所以放在倒数第二个,这里长度只用了32位
*/
strByte[num*16-2]=str.length()*8;
return strByte;
}
string changeHex(int a)
{
int b;
string str1;
string str="";
for(int i=0;i<4;i++)
{
str1="";
b=((a>>i*8)%(1<<8))&0xff; //逆序处理每个字节
for (int j = 0; j < 2; j++)
{
str1.insert(0,1,str16[b%16]);
b=b/16;
}
str+=str1;
}
return str;
}
string getSHA1(string source)
{
atemp=A; //初始化
btemp=B;
ctemp=C;
dtemp=D;
etemp=E;
ftemp=F;
gtemp=G;
htemp=H;
unsigned int *strByte=add(source);
for(unsigned int i=0;i<strlength/16;i++) //512bits 数据分段
{
unsigned int num[80];
unsigned int j;
for(j=0;j<16;j++)
num[j]=strByte[i*16+j]; //得到512bits 数据
for(j=16;j<64;j++)
num[j]=num[j-16] + S0(num[i-15]) + num[i-7] + S1(num[i-2]); //扩展到64个
mainLoop(num); //进入主循环
}
return changeHex(atemp).append(changeHex(btemp)).append(changeHex(ctemp))
.append(changeHex(dtemp)).append(changeHex(etemp)).append(changeHex(ftemp))
.append(changeHex(gtemp)).append(changeHex(htemp));
}
unsigned int main()
{
string ss;
// cin>>ss;
string s=getSHA1("abc");
cout<<s;
return 0;
}
android 中也提供了source code:external/boringssl/src/crypto/fipsmodule/sha/sha256.c
其他算法可以看:数据加密 ---- 总篇
附:
android 实例代码:https://blog.csdn.net/shift_wwx/article/details/84100407