一、SHA算法简介
SHA (Secure Hash Algorithm,译作安全散列算法) 是美国国家安全局 (NSA) 设计,美国国家标准与技术研究院 (NIST) 发布的一系列密码散列函数。正式名称为 SHA 的家族第一个成员发布于 1993年。然而现在的人们给它取了一个非正式的名称 SHA-0 以避免与它的后继者混淆。两年之后, SHA-1,第一个 SHA 的后继者发布了。 另外还有四种变体,曾经发布以提升输出的范围和变更一些细微设计: SHA-224, SHA-256, SHA-384 和 SHA-512 (这些有时候也被称做 SHA-2)。
最初载明的算法于 1993年发布,称做安全散列标准 (Secure Hash Standard),FIPS PUB 180。这个版本现在常被称为 "SHA-0"。它在发布之后很快就被 NSA 撤回,并且以 1995年发布的修订版本 FIPS PUB 180-1 (通常称为 "SHA-1") 取代。根据 NSA 的说法,它修正了一个在原始算法中会降低密码安全性的错误。然而 NSA 并没有提供任何进一步的解释或证明该错误已被修正。1998年,在一次对 SHA-0 的攻击中发现这次攻击并不能适用于 SHA-1 — 我们不知道这是否就是 NSA 所发现的错误,但这或许暗示我们这次修正已经提升了安全性。SHA-1 已经被公众密码社群做了非常严密的检验而还没发现到有不安全的地方,它现在被认为是安全的。
在 CRYPTO 98 上,两位法国研究者展示了一次对 SHA-0 的攻击 (Chabaud and Joux, 1998): 散列碰撞可以复杂到 261 时被发现;小于 280 是理想的相同大小散列函数。2004年时,Biham 和 Chen 发现了 SHA-0 的近似碰撞 — 两个讯息可以散列出相同的数值;在这种情况之下,142 和 160 位元是一样的。他们也发现了 SHA-0 在 80 次之后减少到 62 位元的完整碰撞。2004年8月12日,Joux, Carribault, Lemuet 和 Jalby 宣布了完整 SHA-0 算法的散列碰撞。这是归纳 Chabaud 和 Joux 的攻击所完成的结果。发现这个碰撞要复杂到 251 并且用一台有 256 颗 Itanium2 处理器的超级电脑耗时大约 80,000 CPU 工作时 。2004年8月17日,在 CRYPTO 2004 的 Rump 会议上,Wang, Feng, Lai, 和 Yu 宣布了攻击 MD5、SHA-0 和其他散列函数的初步结果。他们对 SHA-0 攻击复杂到 240 位元,这意谓的他们攻击的成果比 Joux 还有其他人所做的更好。请参见 MD5 安全性。该次 Rump 会议的简短摘要可以在 这里找到,而他们在 sci.crypt 的讨论,例如: 这些结果建议计划使用 SHA-1 作为新的密码系统的人需要重新考虑。
二、密码学实验系统实现SHA-1(略)
三、SHA-1伪码描述
四、SHA-1算法的实现方法
步骤一(消息填充):
读入一串字符串作为消息(最大长度为 位,消息长度用64位二进制数描述),将字符串转化为二进制,添加填充位(一个1或若干个0)。在消息最后添加适当的填充位使得数据的长度满足=448mod512,剩余64位用于长度描述。
算法描述:
- void w(char p[],int w[][32]) //将输入的字符串化为二进制并分组
- {
- int i,j,flag,n,l,e=0,k=0;
- int b[512],s[64];
- for(i=0;p[i];i++) //将字符串化为二进制
- {
- unsigned int j=0x80,tmp=*(p+i);
- for(;j;j>>=1)
- if(j&tmp) b[k++]=1;
- else b[k++]=0;
- }
- flag=k; //flag为输入字符串的位数
- b[k++]=1; //填充一个1其余位补0
- for(;k<512;k++) b[k]=0;
- n=turn(flag,s,2);
- for(l=511;l>511-n;l--) //将长度添加到数据块
- b[l]=s[e++];
- l=0;
- for(i=0;i<16;i++) //将512个字分为16(*32)组
- for(j=0;j<32;j++)
- {
- w[i][j]=b[l++];
- }
- for(;i<80;i++) //产生其余64组
- {
- for(j=0;j<32;j++)
- w[i][j]=w[i-3][j]^w[i-8][j]^w[i-14][j]^w[i-16][j];
- rotl(w[i],1);
- }
- }
void w(char p[],int w[][32]) //将输入的字符串化为二进制并分组 { int i,j,flag,n,l,e=0,k=0; int b[512],s[64]; for(i=0;p[i];i++) //将字符串化为二进制 { unsigned int j=0x80,tmp=*(p+i); for(;j;j>>=1) if(j&tmp) b[k++]=1; else b[k++]=0; } flag=k; //flag为输入字符串的位数 b[k++]=1; //填充一个1其余位补0 for(;k<512;k++) b[k]=0; n=turn(flag,s,2); for(l=511;l>511-n;l--) //将长度添加到数据块 b[l]=s[e++]; l=0; for(i=0;i<16;i++) //将512个字分为16(*32)组 for(j=0;j<32;j++) { w[i][j]=b[l++]; } for(;i<80;i++) //产生其余64组 { for(j=0;j<32;j++) w[i][j]=w[i-3][j]^w[i-8][j]^w[i-14][j]^w[i-16][j]; rotl(w[i],1); } }
步骤二(循环运算):
以512位数据块为单位处理消息。算法的核心是一个包含四人循环的模块,每个循环由20个处理步骤组成(见教材P153)。其中四个基本逻辑函数定义如下:
算法描述:
- void sha(int ht[],int k[][32],int h[][32],int w[][32]) //求A、B、C、D、E的值
- {
- int i,j,t=0;
- int temp[5][32],f[32],m[32],n[5][32];
- for(i=0;i<5;i++)
- for(j=0;j<32;j++)
- n[i][j]=h[i][j];
- for(i=0;i<80;i++)
- {
- //temp=rotl(A,5)+f(t,B,C,D)+E+Wt+Kt
- for(j=0;j<32;j++)
- m[j]=n[0][j];
- rotl(n[0],5);
- f1(i,n[1],n[2],n[3],f);
- add(n[0],f,temp[0]);
- add(n[4],w[i],temp[1]);
- add(temp[0],temp[1],temp[2]);
- if(i>=0&&i<=19) add(temp[2],k[0],temp[3]);
- else if(i>=20&&i<=39) add(temp[2],k[1],temp[3]);
- else if(i>=40&&i<=59) add(temp[2],k[2],temp[3]);
- else if(i>=60&&i<=79) add(temp[2],k[3],temp[3]);
- for(j=0;j<32;j++) //E=D
- n[4][j]=n[3][j];
- for(j=0;j<32;j++) //D=C
- n[3][j]=n[2][j];
- rotl(n[1],30);
-
for (j=0;j<32;j++) //C=rotl(B,30)
- n[2][j]=n[1][j];
- for(j=0;j<32;j++) //B=A
- n[1][j]=m[j];
- for(j=0;j<32;j++) //A=temp
- n[0][j]=temp[3][j];
- }
- for(i=0;i<5;i++) //H0=H0+A,…H4=H4+E
- add(n[i],h[i],temp[i]);
- for(i=0;i<5;i++) //将五个寄存器的值组合成消息摘要
- for(j=0;j<32;j++)
- ht[t++]=temp[i][j];
- }