SHA-1 雖已不推薦用於數字簽名/證書,但在文件完整性校驗、去重、版本控制 (Git) 等場景仍廣泛使用。本文提供符合 FIPS 180-1 的純 C 實現、流式文件處理封裝、錯誤碼設計與命令行工具示例。
#include <stdint.h>
typedef uint32_t u32;
typedef struct {
u32 h[5]; // 五個 32 位鏈接變量
u32 nblocks; // 已處理 64 字節塊數
uint8_t buf[64]; // 輸入緩衝區
int count; // 緩衝區當前字節數
} SHA1_CTX;
// 初始常量 (FIPS 180-1)
#define H0 0x67452301
#define H1 0xEFCDAB89
#define H2 0x98BADCFE
#define H3 0x10325476
#define H4 0xC3D2E1F0
void sha1_init(SHA1_CTX *ctx) {
ctx->h[0] = H0; ctx->h[1] = H1; ctx->h[2] = H2;
ctx->h[3] = H3; ctx->h[4] = H4;
ctx->nblocks = 0; ctx->count = 0;
}
#if defined(__GNUC__) && defined(__i386__)
static inline u32 rotl(u32 x, int n) {
__asm__("roll %%cl, %0" : "=r"(x) : "0"(x), "c"(n));
return x;
}
#else
#define rotl(x,n) (((x) << (n)) | ((x) >> (32-(n))))
#endif
static void sha1_transform(SHA1_CTX *ctx, const uint8_t *data) {
u32 w[80];
// 大端序讀取 16 字
for (int i = 0; i < 16; ++i) {
w[i] = (data[4*i] << 24) | (data[4*i+1] << 16) |
(data[4*i+2] << 8) | (data[4*i+3]);
}
// 擴展至 80 字
for (int i = 16; i < 80; ++i)
w[i] = rotl(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1);
u32 a = ctx->h[0], b = ctx->h[1], c = ctx->h[2],
d = ctx->h[3], e = ctx->h[4];
// 80 輪主循環 (展開為 4 段 20 輪)
#define R(a,b,c,d,e,f,k,w) do { \
e += rotl(a,5) + f(b,c,d) + k + w; \
b = rotl(b,30); \
} while(0)
#define F1(x,y,z) (z ^ (x & (y ^ z)))
#define F2(x,y,z) (x ^ y ^ z)
#define F3(x,y,z) ((x & y) | (z & (x | y)))
#define F4(x,y,z) (x ^ y ^ z)
#define K1 0x5A827999
#define K2 0x6ED9EBA1
#define K3 0x8F1BBCDC
#define K4 0xCA62C1D6
// 輪 0-19
for (int i = 0; i < 20; ++i) R(a,b,c,d,e,F1,K1,w[i]);
// 輪 20-39
for (int i = 20; i < 40; ++i) R(e,a,b,c,d,F2,K2,w[i]);
// 輪 40-59
for (int i = 40; i < 60; ++i) R(d,e,a,b,c,F3,K3,w[i]);
// 輪 60-79
for (int i = 60; i < 80; ++i) R(c,d,e,a,b,F4,K4,w[i]);
ctx->h[0] += a; ctx->h[1] += b; ctx->h[2] += c;
ctx->h[3] += d; ctx->h[4] += e;
}
void sha1_update(SHA1_CTX *ctx, const uint8_t *data, size_t len) {
while (len) {
int space = 64 - ctx->count;
int take = len < space ? len : space;
memcpy(ctx->buf + ctx->count, data, take);
ctx->count += take;
data += take; len -= take;
if (ctx->count == 64) {
sha1_transform(ctx, ctx->buf);
ctx->count = 0; ctx->nblocks++;
}
}
}
void sha1_final(SHA1_CTX *ctx, uint8_t out[20]) {
// 填充:1 bit + 0 bits + 64-bit 大端長度(bit)
ctx->buf[ctx->count++] = 0x80;
if (ctx->count > 56) { // 不夠放長度
while (ctx->count < 64) ctx->buf[ctx->count++] = 0;
sha1_transform(ctx, ctx->buf);
ctx->count = 0;
}
while (ctx->count < 56) ctx->buf[ctx->count++] = 0;
// 位長度 = (nblocks*64 + count) * 8
uint64_t bitlen = ((uint64_t)ctx->nblocks << 9) + ((uint64_t)ctx->count << 3);
for (int i = 0; i < 8; ++i)
ctx->buf[56+i] = (bitlen >> (56 - 8*i)) & 0xFF;
sha1_transform(ctx, ctx->buf);
// 大端序輸出
for (int i = 0; i < 5; ++i) {
out[4*i] = ctx->h[i] >> 24;
out[4*i+1] = ctx->h[i] >> 16;
out[4*i+2] = ctx->h[i] >> 8;
out[4*i+3] = ctx->h[i];
}
}
// 風格 1:控制台直接輸出
void print_file_sha1(const char *path) {
FILE *fp = fopen(path, "rb");
if (!fp) { perror(path); return; }
SHA1_CTX ctx; sha1_init(&ctx);
uint8_t buf[4096]; size_t n;
while ((n = fread(buf, 1, sizeof(buf), fp))) sha1_update(&ctx, buf, n);
if (ferror(fp)) { perror("read"); fclose(fp); return; }
fclose(fp);
uint8_t digest[20]; sha1_final(&ctx, digest);
for (int i = 0; i < 20; ++i) printf("%02x", digest[i]);
putchar('\n');
}
// 風格 2:函數返回值 + 錯誤輸出參數 (庫級 API)
int file_sha1(const char *path, char out_hex[41], char *errbuf, size_t errsz) {
if (!path || !out_hex) return -1;
FILE *fp = fopen(path, "rb");
if (!fp) { snprintf(errbuf, errsz, "open failed: %s", path); return -2; }
SHA1_CTX ctx; sha1_init(&ctx);
uint8_t buf[4096]; size_t n;
while ((n = fread(buf, 1, sizeof(buf), fp))) sha1_update(&ctx, buf, n);
int ferr = ferror(fp); fclose(fp);
if (ferr) { snprintf(errbuf, errsz, "read error"); return -3; }
uint8_t digest[20]; sha1_final(&ctx, digest);
for (int i = 0; i < 20; ++i) sprintf(out_hex + 2*i, "%02x", digest[i]);
out_hex[40] = '\0';
return 0;
}
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "用法: %s <文件路徑>\n", argv[0]);
return 1;
}
char hex[41], err[256];
if (file_sha1(argv[1], hex, err, sizeof(err)) == 0)
puts(hex);
else
fprintf(stderr, "錯誤: %s\n", err);
return 0;
}
$ gcc -std=c99 -O2 sha1_file.c -o sha1_file $ echo -n "abc" | ./sha1_file /dev/stdin a9993e364706816aba3e25717850c26c9cd0d89d # 標準測試向量匹配 $ ./sha1_file sha1_file.c 3b7f... (該文件自身哈希)
| 場景 | 推薦算法 | 理由 |
|---|---|---|
| 數字簽名/證書/TLS | SHA-256 / SHA-384 / SHA-3 | SHA-1 碰撞攻擊已實用化 (SHAttered 2017) |
| Git/版本控制/內容寻址 | SHA-1 (兼容) / 過渡到 SHA-256 | Git 內部格式綁定 SHA-1,v2.29+ 支持 SHA-256 倉庫 |
| 文件去重/完整性校驗(非敵對) | SHA-1 / BLAKE3 / xxHash | 性能優先,非安全關鍵 |
| 密碼存儲 | Argon2id / bcrypt / PBKDF2 | 需慢哈希+鹽值,禁用快哈希 |