SHA1算法的C语言实现(支持输入字符串长度超过56)

前言

最近参与的一个关于物联网的小项目需要在单片机上使用SHA1算法对上传的数据进行加密。首次接触物联网项目和SHA1加密算法,在CSDN上找到的SHA1的代码要求输入字符串长度小于56,否则加密错误,因此对其源码进行了更改。
第一次写博客,必有错误,如有发现请指教。

SHA1算法

SHA1算法理论基础

SHA1算法是Hash算法的一种。SHA1算法的最大输入长度小于2^64比特的消息,输入消息(明文)以512比特的分组为单位处理,输出160比特的消息摘要(密文)。当接收到消息的时候,这个消息摘要可以用来验证数据的完整性。在传输的过程中,数据很可能会发生变化,那么这时候就会产生不同的消息摘要。 SHA1有如下特性:不可以从消息摘要中复原信息;两个不同的消息不会产生同样的消息摘要。

对于任意长度的明文,SHA1首先对其进行分组,使得每一组的长度为512位,然后对这些明文分组反复重复处理。
对于任意长度的明文,首先需要对明文添加位数,使明文总长度为448(mod512)位。在明文后添加位的方法是第一个添加位是1,其余都是0。然后将真正明文的长度(没有添加位以前的明文长度)以64位表示,附加于前面已添加过位的明文后,此时的明文长度正好是512位的倍数。

经过添加位数处理的明文,其长度正好为512位的整数倍,然后按512位的长度进行分组(block),可以划分成L份明文分组,我们用Y0,Y1,……YL-1表示这些明文分组Yq。对于每一个明文分组,都要重复反复的处理。

整个算法的核心是一个包含4轮循环的模块,每轮循环由20个步骤组成。
如下图:
算法核心
其中Yq代表分组后的原始明文, CVq代表链接变量,CV1代表初始化的链接变量,CYq+1是下一组Yq+1的链接变量,若是最后一组,则CYq+1就是密文。
其中,第四轮最后一个步骤的A,B,C,D,E输出,将分别与CVq的A′,B′,C′,D′,E′中的数值求和运算。其结果将作为输入成为下一个512位明文分组的链接变量A,B,C,D,E,当最后一个明文分组计算完成以后,A,B,C,D,E中的数据就是最后散列函数值。

初始化的链接变量输入:

H[0] = 0x67452301, H[1]= 0xEFCDAB89, H[2] = 0x98BADCFE, H[3] =0x10325476, H[4] = 0xC3D2E1F0

C语言代码

算法实现的版本比较多,以下代码修改自:
http://download.csdn.net/detail/zhangrulzu/2936159
https://blog.csdn.net/testcs_dn/article/details/25771377

#include <stdio.h>
#include <string.h>

unsigned long H[5] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0}; // 初始链接变量

void creat_w(unsigned char input[64], unsigned long w[80]) // 获得W
{
    int i, j;
    unsigned long temp, temp1;
    for (i = 0; i < 16; i++)
    {
        j = 4 * i;
        w[i] = ((long)input[j]) << 24 | ((long)input[1 + j]) << 16 | ((long)input[2 + j]) << 8 | ((long)input[3 + j]) << 0;
    }
    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;
    }
}
void SHA1_group(unsigned char input[64], unsigned long hash[5]) // 对512位的消息组进行加密
{
    unsigned long w[80];
    unsigned long A, B, C, D, E, temp, temp1, temp2, temp3, k, f;
    int i, flag;

    creat_w(input, w);
    A = hash[0];
    B = hash[1];
    C = hash[2];
    D = hash[3];
    E = hash[4];
    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;
        }
        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;
    }
    hash[0] = hash[0] + A; // 将获得的160位变量依旧放在hash数组中,可作为下一组明文的链接变量
    hash[1] = hash[1] + B;
    hash[2] = hash[2] + C;
    hash[3] = hash[3] + D;
    hash[4] = hash[4] + E;
}
void SHA1(unsigned char *input, unsigned long hash[5]) // SHA1算法
{
    int n = strlen(input);   // 字符串长度
    int m;                   // 明文组数 - 1
    int i, j;                // 循环计数
    unsigned char group[64]; // 将原始明文进行分组
    unsigned long long temp;

    for (i = 0; i < 5; i++) // 获得初始链接变量
    {
        hash[i] = H[i];
    }

    m = n / 64;             // 获得明文组数 - 1;64*8=512
    for (i = 0; i < m; i++) // 处理前m组的明文
    {
        for (j = 0; j < 64; j++)
        {
            group[j] = input[i * 64 + j]; // 将原始明文赋值给group
        }
        SHA1_group(group, hash); // 对明文进行加密
    }

    if (n % 64 >= 56) // 原始明文长度对512求余大于448 bit,对原始明文的补位加一组;56*8=448;
    {
        for (j = 0; j < 64; j++) // 将原始明文赋值给group,并补位,第一位补 1 ,其余位补 0
        {
            if (m * 64 + j < n)
                group[j] = input[m * 64 + j]; // 将原始明文赋值给group
            else if (m * 64 + j == n)         // 补位,第一位补 1
                group[j] = 0X80;              // 1000 0000 B
            else
                group[j] = 0; // 其余位补 0
        }
        SHA1_group(group, hash); // 加密,并且将获得的160位变量依旧放在hash数组中,作为下一组明文的链接变量
        m++;                     // 对原始明文的补位加一组
    }

    for (j = 0; j < 64; j++) // 处理最后一组明文
    {
        if (m * 64 + j < n)
            group[j] = input[m * 64 + j]; // 将原始明文赋值给group
        else if (m * 64 + j == n)         // 补位,第一位补 1
            group[j] = 0X80;
        else if (j < 56) // 其余位补 0
            group[j] = 0;
        else
            break;
    }
    temp = ~(~temp << 8);// 给 明文的最后64bit 赋值原始明文的长度,单位bit
    n = n * 8;
    for (i = 0; i < 8; i++)
    {
        j = 8 * i;
        group[63 - i] = (char)((n & (temp << j)) >> j);
    }
    SHA1_group(group, hash); // 加密最后一组明文,获得密文
}

int main()
{
    unsigned char input[300]; // 输入的明文
    unsigned long hash[5];

    printf("input message:\n");
    scanf("%s", input); // 不要有空格和中文

    SHA1(input, hash); // 加密

    printf("\noutput hash value:\n");
    printf("%08X%08X%08X%08X%08X", hash[0], hash[1], hash[2], hash[3], hash[4]); // 获得SHA1加密

    return 0;
}

SHA1在线加密工具:https://www.iamwawa.cn/jiami.html

参考资料:
[1]: https://blog.csdn.net/Y_peak/article/details/116998207
[2]: https://blog.csdn.net/bamboo_cqh/article/details/86703635
[3]: https://blog.csdn.net/testcs_dn/article/details/25771377
[4]: https://blog.csdn.net/guodongxiaren/article/details/44926823

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐