数据加密 ---- SHA-2 加密

上一篇 博文

 

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个整数临时存起来(后面会使用到)。

W_t表示一个32 bits(4个字节) 的输入数据(512bits 数据其中的32bits),K_t 表示一个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

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

私房菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值