一、整体简介
SHA-1(安全哈希算法1) 是一种常见的加密哈希函数,可以把任意长度的输入数据转换成固定长度的输出(160 位 = 20 字节),这个输出叫做哈希值或摘要(digest)。
它常用于:
- 文件完整性校验(看数据有没有被篡改)
- 数字签名
- 认证协议等
但由于它存在安全漏洞(已被证明可被攻击),现代加密系统已经推荐使用 SHA-256 或 SHA-3 替代。
本程序模拟 SHA-1 哈希算法的主要计算过程,对长度不超过 55 字节的输入字符串进行加密计算,并输出结果到屏幕和文件。
SHA-1 的核心目标
将一个任意长度的输入消息,处理成一个 160 位的唯一表示(哈希值),且满足:
- 相同输入 → 相同输出
- 不同输入 → 尽量不同输出
- 无法从输出反推出原文
- 碰撞难以发生(两个不同输入哈希值相同)
SHA-1 的运算流程(5个阶段)
- 填充(padding)
↓ - 分块(每块512位 = 64字节)
↓ - 初始化(五个哈希变量 H0~H4)
↓ - 主循环(80轮迭代处理每个块)
↓ - 合并输出(最终哈希值)
二、核心模块说明
① 消息填充(Padding)
目的:让消息长度变成 512 的倍数,并记录原始长度。
步骤:
- 在原始消息末尾加上一个
0x80
(即二进制10000000
) - 然后补若干个
0x00
,直到最后剩 64位(8字节) - 这最后的8字节,用来表示原始消息的比特长度(大端格式)
🌟 如果原始消息已经接近 512 位,就需要多加一块新分组
② 消息分块(Block Divide)
将填充后的消息按每 512 位(64 字节)划分成一组一组的“块”,每个块会单独处理。
③ 初始化哈希值(Initial State)
SHA-1 维护五个 32 位寄存器(总共 160 位),初始值固定为:
H1 = 0xEFCDAB89
H2 = 0x98BADCFE
H3 = 0x10325476
H4 = 0xC3D2E1F0
这些就是 SHA-1 的“初始种子值”。
④ 主循环(Main Compression Loop)
对每个消息块,进行 80 轮压缩处理。
每轮都做什么?
- 将消息块扩展成 80 个 32 位整数
w[0]~w[79]
- 使用 5 个变量 A, B, C, D, E,初始值为:
H0 = 0x67452301
H1 = 0xefcdab89
H2 = 0x98badcfe
H3 = 0x10325476
H4 = 0xc3d2e1f0
每一轮都执行类似这样的操作:
temp = (A <<< 5) + f(B,C,D) + E + w[i] + k
E = D
D = C
C = B <<< 30
B = A
A = temp
- 每 20 轮使用不同的函数
f
和常数k
,增加非线性复杂度:
轮数 | 函数 f(B,C,D) | 常数 k |
---|---|---|
0~19 | (B & C) | (~B & D) |
20~39 | B ^ C ^ D | 0x6ED9EBA1 |
40~59 | (B & C) | (B & D) |
60~79 | B ^ C ^ D | 0xCA62C1D6 |
每个块处理完后,把结果累加到初始值中:
H0 += A
H1 += B
H2 += C
H3 += D
H4 += E
最终输出的是 H0~H4 连在一起的一串 160 位值。
三、代码展示
这是一段简化后的sha-1算法代码,可用于长度不超过55字节的输入字符加密
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
FILE *w_log, *h_log;
//creat_w 函数的作用是:将输入的 512 位(64 字节)消息块,转换为 80 个 32 位整数 w[0] ~ w[79],供 SHA-1 主循环使用。
void creat_w(unsigned char input[64],unsigned int w[80])
{
int i,j;
unsigned int temp,temp1;
for(i=0;i<16;i++){
j=4*i;
//change to big-endian
w[i]=input[j]<<24 | input[1+j]<<16 | input[2+j]<<8 | input[3+j]<<0;
}
//将每 4 个 unsigned char(8 位)拼接成一个 unsigned int(32 位)
// 每个 w[i] 都对应 4 个字节,顺序从高位到低位(即大端格式)
for(i=16;i<80;i++) {
w[i]=w[i-16]^w[i-14]^w[i-8]^w[i-3];
temp=w[i]<<1;
temp1=w[i]>>31;
w[i]=temp|temp1;
}
for(i=0; i<80; i++) {
fprintf(w_log, "%08x\n", w[i]);
}
}
//--- put string length at byte 60~63
char ms_len(int a,char intput[64])
{
unsigned int temp3,p1;
int i,j;
temp3=0;
p1=~(~temp3<<8);
for(i=0;i<4;i++) {
j=8*i;
intput[63-i]=(char)((a&(p1<<j))>>j); //a[7:0] in intput[63]; a[31:24] in intput[60]
}
return '0';
}
int sha_1(unsigned char *input, unsigned int *res)
{
unsigned int H0=0x67452301,H1=0xefcdab89,H2=0x98badcfe,H3=0x10325476,H4=0xc3d2e1f0;
unsigned int A,B,C,D,E,temp,temp1,temp2,temp3,k,f;
int x;
int n,i,flag;
unsigned int w[80];
n=strlen((const char *) input);
if(n<= 55) {
//pad 0x80 to the end a message
input[n]= 0x80;
//pad 0x00 to byte 59
for(i=n+1;i<60;i++)
input[i]=0;
//put message length to byte 60~63
x=n*8;
ms_len(x,(char*)input);
} else {
printf("input message is too long, not supported in this model.\n");
exit(1);
}
creat_w(input,w);
/*for(i=0;i<80;i++)
printf("%lx,",w[i]);*/
printf("\n");
A=H0;B=H1;C=H2;D=H3;E=H4;
for(i=0;i<80;i++) {
flag=i/20;
switch(flag)
{
case 0: k=0x5a827999;f=(B&C)|(~B&D);break;
case 1: k=0x6ed9eba1;f=B^C^D;break;
case 2: k=0x8f1bbcdc;f=(B&C)|(B&D)|(C&D);break;
case 3: k=0xca62c1d6;f=B^C^D;break;
}
/*printf("%lx,%lx\n",k,f); */
temp1=A<<5;
temp2=A>>27;
temp3=temp1|temp2;
temp=temp3+f+E+w[i]+k;
E=D;
D=C;
temp1=B<<30;
temp2=B>>2;
C=temp1|temp2;
B=A;
A=temp;
//printf("%lx,%lx,%lx,%lx,%lx\n",A,B,C,D,E); //internal result of each cal round
fprintf(h_log, "%08x%08x%08x%08x%08x\n", A,B,C,D,E);
}
H0=H0+A;
H1=H1+B;
H2=H2+C;
H3=H3+D;
H4=H4+E;
printf("output hash value:\n");
printf("%lx%lx%lx%lx%lx\n",H0,H1,H2,H3,H4);
res[0]=H0; res[1]=H1; res[2]=H2;
res[3]=H3; res[4]=H4;
}
void info_log(FILE *din_fp, FILE *dout_fp, unsigned char *input, unsigned int *res)
{
int i;
fwrite(input, sizeof(char), 64, din_fp);
fwrite(res, sizeof(int), 5, dout_fp);
}
int main(int argc, char* argv[])
{
unsigned char test0[64] = "abc"; //first byte in test0[0]
unsigned char test1[64] = "a";
unsigned char test2[64] = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnop"; //maximal byte lenght: 55 byte
unsigned char test3[64] = "01234567012345670123456701234567";
unsigned char test[64]; //first byte in test[0]
unsigned char *input;
unsigned int res[5];
unsigned char tmp;
int i, len, cnt;
FILE *din_fp, *dout_fp;
din_fp = fopen("./sha_in.bin", "wb");
dout_fp = fopen("./sha_out.bin", "wb");
w_log = fopen("./w.log", "w");
h_log = fopen("./h.log", "w");
printf("begin SHA-1 calculation:\n");
//1: fixed pattern test
input = test0;
sha_1(input, res);
info_log(din_fp, dout_fp, input, res);
input = test1;
sha_1(input, res);
info_log(din_fp, dout_fp, input, res);
input = test2;
sha_1(input, res);
info_log(din_fp, dout_fp, input, res);
input = test3;
sha_1(input, res);
info_log(din_fp, dout_fp, input, res);
//2: random test
for(cnt=0; cnt<100; cnt++) {
len = rand() % 56;
if(len == 0) len = 1;
//initial test with random data
for(i=0; i<len; i++) {
tmp = rand() % 256;
if(tmp == 0) tmp = 1;
test[i] = tmp;
}
for(i= len; i<64; i++)
test[i] = 0x00;
input = test;
sha_1(input, res);
info_log(din_fp, dout_fp, input, res);
}
fclose(din_fp);
fclose(dout_fp);
fclose(w_log);
fclose(h_log);
printf("SHA-1 calculation end:\n");
getch();
}