首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C語言計算文件 SHA-1 哈希值:標準實現、流式處理與工程封裝

     編輯:關於C語言

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;
}

循環左移優化 (GCC 內聯彙編 / 通用 C)

#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

核心變換函數 (處理 64 字節塊)

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;
}

流式處理:sha1_update / sha1_final

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];
    }
}

文件級封裝:錯誤碼設計與兩種 API 風格

// 風格 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... (該文件自身哈希)

安全性說明與替代建議

純 C 實現 SHA-1 讓你在無 OpenSSL 依賴的嵌入式、引導程序、跨平台工具中擁有標準哈希能力。理解其填充規則、大端序約定、擴展函數邏輯,是掌握所有 Merkle-Damgård 結構哈希算法 (MD5, SHA-2 家族) 的基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
場景推薦算法理由
數字簽名/證書/TLSSHA-256 / SHA-384 / SHA-3SHA-1 碰撞攻擊已實用化 (SHAttered 2017)
Git/版本控制/內容寻址SHA-1 (兼容) / 過渡到 SHA-256Git 內部格式綁定 SHA-1,v2.29+ 支持 SHA-256 倉庫
文件去重/完整性校驗(非敵對)SHA-1 / BLAKE3 / xxHash性能優先,非安全關鍵
密碼存儲Argon2id / bcrypt / PBKDF2需慢哈希+鹽值,禁用快哈希