的是将16个字(32位)的消息块(M)转换成80个字的字块(W)。
编码实现基础
宏:rol
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
将32位整型value进行循环左移bites位。所谓循环左移,就是将左边被移出的位补到数据的右边。汇编中有个ROL指令就是循环左移。
共用体变量:block
block会在接下来介绍的两个宏函数中用到,它是在函数
SHA1Transform()中声明的一个变量(因为宏实际上进行的是编译期间的替换操作,所以可以在未声明的时候前向引用)。
typedef union {
unsigned char c[64];
u_int32_t l[16];
} CHAR64LONG16;
#ifdef SHA1HANDSOFF
CHAR64LONG16 block[1]; /* use array to appear as a pointer */
可以看出block虽然是数组,但只有一个元素。注释中也标注了用数组类型是为了让它能表现的像指针一样。它的大小是64个字节(512位)。SHA1算法中有
“字”(W)概念,一个字是32位,换句话说就是16个字的大小。下文中我会
用W来表示block-l[ ]:
W(i) = block-l[i&15] // 16<= i <= 79
宏:blk0
#if BYTE_ORDER == LITTLE_ENDIAN
#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
? ? |(rol(block->l[i],8)&0x00FF00FF))
#elif BYTE_ORDER == BIG_ENDIAN
#define blk0(i) block->l[i]
blk0的功能实际是就是进行字节序的转换。如果是小端序就将block->l[i] 转换为大端序(上面代码中的第2行),如果是大端序就不操作,直接等价于block->l[i]。
实际上在调用blk0(i)的时候,它参数i的取值范围是0~15。
宏:blk
#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
^block->l[(i+2)&15]^block->l[i&15],1))
实际上在调用blk(i)的时候,它的参数i的取值范围是16~79。 实际上它实现的功能我们在下面会用到,它实际计算的表达式是:
用符号W(i)来表示block-l[i]
W(i) = S^1( W(i-3) XOR W(i-8) XOR W(i-14) XOR W(i-16) ) //S^1()表示循环左移
因为观察上面表达式可知,我们只需要16个字的存储空间来保存就可以了。所以在实现上等价与:
W(i%16) = S^1( W((i-3)%16) XOR W((i-8)%16) XOR W((i-14)%16) XOR W((i-16)%16) )
下面来简单证明一下:
因为a&15等价与a%16
则源码blk(i)完成的操作等价于:
W(i%16) = S^1( W((i+13)%16) XOR W((i+8)%16) XOR W((i+2)%16) XOR W((i%16)) )
设m+n=16
(i+n)%16
= (i+16-m)%16
= ((i-m)%16 + 16%16)%16
= (i-m)%16
当n=13,8,2,0时,m等于3,8,14,16
所以:
W(i%16) = S^1( W((i+13)%16) XOR W((i+8)%16) XOR W((i+2)%16) XOR W((i%16)) )
W(i%16) = S^1( W((i-3)%16) XOR W((i-8)%16) XOR W((i-14)%16) XOR W((i-16)%16) )
等价
开始计算
我们查看sha1.c的源码,就能发现几个宏函数:
/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
这段代码中的R2、R3、R4三个宏函数,所完成的操作就是 t
(t表示轮数,共计80轮运算) 在范围 [20,39]、[40,59]、[60,79]的时候的运算。对应RFC文档中:求解TEMP、给A~E重新赋值。但是我们可以看到当 t 的范围在[0,20]的时候使用了R0和R1这两个宏函数来表示,它们的差别之处在于R0里面计算 z(即TEMP)值的时候,加上的是blk0(i),而R1中加的是blk(i) (和R2、R3、R4一样,加的是blk(i))。造成这个差别的原因是在前面提到了5步运算的前两步中:当 t 取值[0,15]的时候W(t)直接等于M(t),而 t>15以后(这里是t取值[16,19])Wt则需要进行转换才能得到,即
W(t) = S^1(W(t-3) XOR W(t-8) XOR W(t-14) XOR W(t-16))
前面我们已经证明了,blk(i)实现的功能正是这个公式的计算。 大家如果细心的话,可以发现R0和R1中使用的 f 函数是:
(w&(x^y))^y 而RFC文档中给出的是(B&C)|(B&D) 用wxy替换BCD的话就是
(w&x)|(w&y)。那么
(w&(x^y))^y和
(w&x)|(w&y)等价吗?答案是肯定的,逻辑表达式的证明也不是我的强项,不过因为只涉及三个变量所以我们可以用枚举的方法来比较两个逻辑表达式的值,于是我手写了一个小程序来比较它们:
#include
int main(){
for(int w=0;w<2;w++){
for(int x=0;x<2;x++){
for(int y=0;y<2;y++){
printf("-------------\n"); //分割线,使更容易比较
阅读
printf("%d %d %d:%d\n",w,x,y,(w&(x^y))^y);
printf("%d %d %d:%d\n",w,x,y,(w&x)|(~w&y));
}
}
}
}
谈了这么多,是时候引入sha1.c文件的核心函数了——SHA1Transform()
SHA1Transform()
void SHA1Transform(u_int32_t state[5], const unsigned char buffer[64])
该函数的代码行数较多,为节省篇幅,我这里就不全部列出了。 开始部分声明了u_int32_t类型的五个变量:a、b、c、d、e。接