C++ SHA-1 算法实现原理

一:概述

在密码学中,SHA-1(Secure Hash Algorithm 1,安全哈希算法1)是一种密码学哈希函数,它接受任意长度的输入数据,并生成一个 160 位(20 字节) 的哈希值,称为消息摘要,通常以 40 个十六进制字符表示。SHA-1 由美国国家安全局(NSA)设计,并被采纳为美国联邦信息处理标准(FIPS)。

然而,SHA-1 已被密码学界成功破解。自 2005 年起,SHA-1 即被认为无法抵御具备充足计算资源的攻击者;到 2010 年,多个组织已明确建议停止使用该算法。美国国家标准与技术研究院(NIST)于 2011 年正式弃用 SHA-1,并在 2013 年禁止其用于数字签名,同时宣布应在 2030 年前彻底淘汰该算法。截至 2020 年,针对 SHA-1 的选择前缀碰撞攻击仍然是可行的。因此,业界普遍建议尽快在产品和系统中移除 SHA-1,并使用 SHA-2 或 SHA-3 作为替代方案;在涉及数字签名的场景下,这一替换尤为紧迫。

自从 2017 年起,所有主流浏览器厂商已停止接受使用 SHA-1 签名的 SSL 证书。同年 2 月,阿姆斯特丹 CWI 研究所与 Google 联合公布了对 SHA-1 的一次实际碰撞攻击,成功构造了两个内容不同但具有相同 SHA-1 哈希值的 PDF 文件,进一步证明了该算法在安全性上的缺陷。

需要注意的是,SHA-1 在 HMAC 结构中仍被认为是安全的,这是由于 HMAC 的安全性并不完全依赖于底层哈希函数的抗碰撞性。

此外,微软于 2020 年 8 月 3 日停止了对 Windows 更新中 SHA-1 代码签名的支持。这一决定实际上终止了尚未升级至 SHA-2 的旧版 Windows 系统(如 Windows 2000 至 Vista,以及 Windows Server 2000 至 Server 2003)继续接收安全更新的能力。

二:算法原理

SHA-1 是一种密码学哈希算法,用于将任意长度的输入消息映射为一个固定长度为 160 位的消息摘要。其算法过程主要包括消息填充、消息分块、消息扩展、迭代压缩和结果输出五个阶段。

首先,对输入消息进行填充处理。算法在消息末尾添加一个比特值为 1 的填充位,随后追加若干个比特值为 0 的填充位,使得填充后的消息长度满足模 512 余 448。最后,在消息末尾附加一个 64 位的整数,用于表示原始消息的长度(以比特为单位)。经过该步骤后,消息的总长度成为 512 位的整数倍。

接着,将填充后的消息划分为若干个长度为 512 位的消息块。SHA-1 算法依次对每一个消息块进行处理,每个消息块的处理结果都会影响最终的哈希值。

在处理单个消息块时,算法首先将该消息块划分为 16 个 32 位字,并通过特定的异或和循环左移操作将这 16 个字扩展为 80 个 32 位字,形成消息调度序列。该扩展过程增强了输入消息中各比特之间的关联性,提高了算法的扩散效果。

随后,算法进入核心压缩阶段。SHA-1 内部维护五个 32 位的工作寄存器,用于保存中间计算状态。在每个消息块的处理中,这些寄存器首先被初始化为当前的哈希状态值。然后算法执行 80 轮迭代运算,每一轮都会使用不同的非线性逻辑函数和固定常量,对工作寄存器的值进行更新。通过循环左移、按位逻辑运算以及模 2³² 加法等操作,输入消息的微小变化会被迅速扩散到整个状态中。

在完成 80 轮迭代后,算法将得到的五个工作寄存器的值分别与当前的哈希状态相加,从而更新全局哈希状态。随后,算法继续处理下一个消息块,直到所有消息块均被处理完成。

最后,当所有消息块处理完毕后,算法将最终得到的五个 32 位哈希状态值按大端序连接起来,形成一个长度为 160 位的消息摘要。该摘要通常以 40 个十六进制字符的形式输出,作为输入消息的 SHA-1 哈希值。

三:示例

cpp 复制代码
#include <array>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <string>
#include <vector>

namespace hashing {
namespace sha1 {

// 循环左移
uint32_t leftRotate32bits(uint32_t n, std::size_t rotate) {
    return (n << rotate) | (n >> (32 - rotate));
}

// 计算 SHA-1(字节流)
std::array<uint8_t, 20> hash_bs(const void* input_bs, uint64_t input_size) {
    const uint8_t* input = static_cast<const uint8_t*>(input_bs);

    uint32_t h0 = 0x67452301;
    uint32_t h1 = 0xEFCDAB89;
    uint32_t h2 = 0x98BADCFE;
    uint32_t h3 = 0x10325476;
    uint32_t h4 = 0xC3D2E1F0;

    uint64_t padded_size;
    if (input_size % 64 < 56)
        padded_size = input_size + 64 - (input_size % 64);
    else
        padded_size = input_size + 128 - (input_size % 64);

    std::vector<uint8_t> padded(padded_size, 0);
    std::memcpy(padded.data(), input, input_size);

    padded[input_size] = 0x80;

    uint64_t bit_len = input_size * 8;
    for (int i = 0; i < 8; ++i) {
        padded[padded_size - 1 - i] = bit_len & 0xFF;
        bit_len >>= 8;
    }

    std::array<uint32_t, 80> w{};

    for (uint64_t chunk = 0; chunk < padded_size; chunk += 64) {
        for (int i = 0; i < 16; ++i) {
            w[i] = (padded[chunk + i * 4] << 24) |
                   (padded[chunk + i * 4 + 1] << 16) |
                   (padded[chunk + i * 4 + 2] << 8) |
                   (padded[chunk + i * 4 + 3]);
        }

        for (int i = 16; i < 80; ++i) {
            w[i] = leftRotate32bits(
                w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
        }

        uint32_t a = h0, b = h1, c = h2, d = h3, e = h4;

        for (int i = 0; i < 80; ++i) {
            uint32_t f, k;
            if (i < 20) {
                f = (b & c) | (~b & d);
                k = 0x5A827999;
            } else if (i < 40) {
                f = b ^ c ^ d;
                k = 0x6ED9EBA1;
            } else if (i < 60) {
                f = (b & c) | (b & d) | (c & d);
                k = 0x8F1BBCDC;
            } else {
                f = b ^ c ^ d;
                k = 0xCA62C1D6;
            }

            uint32_t temp =
                leftRotate32bits(a, 5) + f + e + k + w[i];
            e = d;
            d = c;
            c = leftRotate32bits(b, 30);
            b = a;
            a = temp;
        }

        h0 += a;
        h1 += b;
        h2 += c;
        h3 += d;
        h4 += e;
    }

    std::array<uint8_t, 20> digest{};
    uint32_t hs[5] = {h0, h1, h2, h3, h4};

    for (int i = 0; i < 5; ++i) {
        digest[i * 4]     = (hs[i] >> 24) & 0xFF;
        digest[i * 4 + 1] = (hs[i] >> 16) & 0xFF;
        digest[i * 4 + 2] = (hs[i] >> 8) & 0xFF;
        digest[i * 4 + 3] = hs[i] & 0xFF;
    }

    return digest;
}

// string 接口
std::array<uint8_t, 20> hash(const std::string& msg) {
    return hash_bs(msg.data(), msg.size());
}

// 转 hex
std::string to_hex(const std::array<uint8_t, 20>& digest) {
    const char* hex = "0123456789abcdef";
    std::string out;
    for (uint8_t b : digest) {
        out.push_back(hex[b >> 4]);
        out.push_back(hex[b & 0xF]);
    }
    return out;
}

}  // namespace sha1
}  // namespace hashing

int main() {
    std::string msg = "abc";
    auto digest = hashing::sha1::hash(msg);

    std::cout << "Input: " << msg << std::endl;
    std::cout << "SHA-1: " << hashing::sha1::to_hex(digest) << std::endl;

    return 0;
}
相关推荐
寻寻觅觅☆8 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
偷吃的耗子9 小时前
【CNN算法理解】:三、AlexNet 训练模块(附代码)
深度学习·算法·cnn
化学在逃硬闯CS9 小时前
Leetcode1382. 将二叉搜索树变平衡
数据结构·算法
ceclar12310 小时前
C++使用format
开发语言·c++·算法
Gofarlic_OMS10 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
夏鹏今天学习了吗10 小时前
【LeetCode热题100(100/100)】数据流的中位数
算法·leetcode·职场和发展
忙什么果11 小时前
上位机、下位机、FPGA、算法放在哪层合适?
算法·fpga开发
董董灿是个攻城狮11 小时前
AI 视觉连载4:YUV 的图像表示
算法
ArturiaZ12 小时前
【day24】
c++·算法·图论
大江东去浪淘尽千古风流人物12 小时前
【SLAM】Hydra-Foundations 层次化空间感知:机器人如何像人类一样理解3D环境
深度学习·算法·3d·机器人·概率论·slam