设为首页 加入收藏

TOP

Redis源码中探秘SHA-1算法原理及其编程实现(二)
2015-11-21 01:34:57 来源: 作者: 【 】 浏览:2
Tags:Redis 源码 探秘 SHA-1 算法 原理 及其 编程 实现
----------------------------------------------------------------------------------------------------------------------------------------------
SHA1Update(context, finalcount, 8);  /* Should cause a SHA1Transform() */
很明显,这一句完成的就是附加长度了。根据注释可以看出,这将触发SHA1Transform()函数的调用,该函数的功能就是进行运算,得出160位的消息摘要(message digest)并储存在context-state[ ]中,它是整个SHA-1算法的核心。其实现细节请看下文的: 计算消息摘要一节。
    for (i = 0; i < 20; i++) {
        digest[i] = (unsigned char)
         ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
    }
最后的这步转换将消息摘要转换成单字节序列。用代码来解释就是:将context-state[5]中储存的20个字节(5×4字节)的消息摘要取出,将其存储在20个单字节的数组digest中。并且按大端序存储(与之前分析context->count[ ]到finalcount[ ]转换的思路相同)。SHA-1算法最后要得出的就是这160位(20字节)的数据。

SHA1Update

SHA1Update()前面我提到了,它完成的操作就是将新数据(原始数据、填充位、长度)依次附加到context->buffer[ ]中。
void SHA1Update(SHA1_CTX* context, const unsigned char* data, u_int32_t len);
data就是我们要附加的数据。len是data的长度(单位:字节)
    j = context->count[0];
    if ((context->count[0] += len << 3) < j)
        context->count[1]++;
    context->count[1] += (len>>29);
context->count[ ]存储的是消息的长度,超出context->count[0]的存储范围的部分存储在context->count[1]中。len<<3就是len*8的意思,因为len的单位是字节,而context->count[ ]存储的长度的单位是位,所以要乘以8。 if ((context->count[0] += len << 3) < j) 的意思就是说如果加上len*8个位,context->count[0]溢出了,那么就要:context->count[1]++;进位。 len<<3的单位是位,len>>29(len<<3 >>32)表示的就是len中要存储在context->count[1]中的部分。
    j = (j >> 3) & 63;
j>>3获得的就是字节数,j = (j >> 3) & 63得到的就是低6位的值,也就是代表64个字节(512位)长度的消息。,因为我们每次进行计算都是处理512位的消息数据。
    if ((j + len) > 63) {
        memcpy(&context->buffer[j], data, (i = 64-j));
        SHA1Transform(context->state, context->buffer);
        for ( ; i + 63 < len; i += 64) {
            SHA1Transform(context->state, &data[i]);
        }
        j = 0;
    }
    else i = 0;
    memcpy(&context->buffer[j], &data[i], len - i);
这段代码大致的含义就是:如果j+len的长度大于63个字节,就分开处理,每64个字节处理一次,然后再处理后面的64个字节,重复这个过程;否则就直接将数据附加到buffer末尾。逐句分析一下:
        memcpy(&context->buffer[j], data, (i = 64-j));
        SHA1Transform(context->state, context->buffer);
i=64-j,然后从data中复制i个字节的数据附加到context->buffer[j]末尾,也就是说给buffer凑成了64个字节,然后执行SHA1Transform()来开始一次消息摘要的计算。
        for ( ; i + 63 < len; i += 64) {
            SHA1Transform(context->state, &data[i]);
        }
        j = 0;
然后开始循环,每64个字节处理一次。这里可能有朋友会好奇每次i递增的步长都是64,那么为什么比较的时候是 i + 63 < len;而不是 i + 64 < len;呢?其原因很简单——因为下标是从0计数的。这些细节大家简单琢磨一下就OK啦。 最后j=0,把buffer[ ]的偏移重置到开头。因为已经计算完消息摘要的数据就没有用了。
    else i = 0;
    memcpy(&context->buffer[j], &data[i], len - i);
如果前面的if不成立,那么也就是说原始数据context->buffer加上新的数据data的长度还不足以凑成64个字节,所以直接附加上data就行了。相当于:memcpy(&context->buffer[j], &data[i], 0); 如果前面的if成立,那么j是等于0的,而 i 所指向的偏移位置是 (└ len/64┘×64,len)之间。 └ ┘表示向下取整。

初始化散列缓冲区

SHA-1算法需要使用两个5个字的缓冲区,第一个5字缓冲区被在RFC文档中被标识为A、B、C、D、E,第二个5字缓冲区被被标志为H0~H4。在后面的每一轮的计算开始的时候,都要把H0~H4依次赋值给A~E。然后在每轮计算结束之后再更新H0~H4的值。下面是H0~H4的初始值:
      H0 = 0x67452301

      H1 = 0xEFCDAB89

      H2 = 0x98BADCFE

      H3 = 0x10325476

      H4 = 0xC3D2E1F0
在开始计算消息摘要之前,要先初始化这5个字的缓冲区,也就是按照上面的数值赋值。这步操作体现在sha1.c文件的 SHA1Init()函数中。
void SHA1Init(SHA1_CTX* context)
{
    /* SHA1 initialization constants */
    context->state[0] = 0x67452301;
    context->state[1] = 0xEFCDAB89;
    context->state[2] = 0x98BADCFE;
    context->state[3] = 0x10325476;
    context->state[4] = 0xC3D2E1F0;
    context->count[0] = context->count[1] = 0;
}


计算消息摘要

理论基础

将长度512的消息块(M1、M2、……Mn)依次进行处理,处理每个消息块Mi都需要运行一个80轮运算的函数,每一轮都把160比特缓冲区的值ABCDE作为输入,并更新缓冲区的值。 每一轮需要用到一个非线性的函数f(t): 下面内容取自官方RFC文档,注意括号中的t并不是输入参数。可以理解为f函数的下标。共有四种f函数。
      f(t;B,C,D) = (B AND C) OR ((NOT B) AND D)         ( 0 <= t <= 19)

      f(t;B,C,D) = B XOR C XOR D                        (20 <= t <= 39)

      f(t;B,C,D) = (B AND C) OR (B AND D) OR (C AND D)  (40 <= t <= 59)

      f(t;B,C,D) = B XOR C XOR D                        (60 <= t <= 79).
每一轮还会用到一个附加常数K(t):
      K(t) = 0x5A827999         ( 0 <= t <= 19)

      K(t) = 0x6ED9EBA1         (20 <= t <= 39)

      K(t) = 0x8F1BBCDC         (40 <= t <= 59)

      K(t) = 0xCA62C1D6         (60 <= t <= 79)
共有5步:
1. M(t) = W(t) (0<= t<= 15)
2. W(t) = S^1(W(t-3) XOR W(t-8) XOR W(t-14) XOR W(t-16)) (16<= t <= 79,S^1()表示循环左移1位)
3. A = H0, B = H1, C = H2, D = H3, E = H4. 
4. 对于(0<= t <= 79)开始执行80轮变换
    TEMP = S^5(A) + f(t;B,C,D) + E + W(t) + K(t); 
    E = D; D = C; C = S^30(B); B = A; A = TEMP;
5. H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E.
上面的数学表达式改编自RFC文档, 在经过80轮运算之后的H0~H4就是SHA-1算法要生成的160比特(位)的消息摘要。里面使用了ABCDE这5个符号,Redis源码中使用的是v表示符号A;w、x、y代表上文中的B、C、D,z表示上文中的TEMP。在5步之中的前面两步中,进行了消息块M(i)到W(i)的转换,这样做的目
首页 上一页 1 2 3 4 下一页 尾页 2/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇MyBtis注解方式注册异常 下一篇数据库-关系代数与关系运算

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: