----------------------------------------------------------------------------------------------------------------------------------------------
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)的转换,这样做的目