一:概述
在密码学中,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;
}