Redis 使用 SHA-1 (Secure Hash Algorithm 1) 生成 160 位訊息摘要,其實現源碼 (sha1.c) 移植自 Valgrind 專案。本文深入剖析 SHA-1 流程、填充規則、大小端處理、核心變換函數 SHA1Transform 及四輪非線性函數 R0-R4 的位運算邏輯。
SHA1_CTXtypedef struct {
u_int32_t state[5]; // H0-H4,160-bit 雜湊值
u_int32_t count[2]; // 訊息總長度 (bit),count[0] 低 32 位,count[1] 高 32 位
unsigned char buffer[64]; // 512-bit 訊息分塊緩衝區
} SHA1_CTX;
SHA1Init — 初始化魔數context->state[0] = 0x67452301;
context->state[1] = 0xEFCDAB89;
context->state[2] = 0x98BADCFE;
context->state[3] = 0x10325476;
context->state[4] = 0xC3D2E1F0;
context->count[0] = context->count[1] = 0;
SHA1Update — 串流式填充分塊count (bit 長度),處理溢位進位。buffer 當前偏移 j = (count[0]>>3) & 63 (byte 偏移)。SHA1Transform → 後續每 64 bytes 直接 Transform → 剩餘資料複製到 buffer 開頭。buffer 末尾。SHA1Final — 完成填充、輸出摘要count 拆解為 8 bytes big-endian 存入 finalcount (端序無關寫法)。0x80 (binary 10000000) — 對應「補 1 後接 0」。0x00 直到 (count[0] & 504) == 448 (即 bit 長度模 512 ≡ 448)。& 504 (0b111111000) 與 & 511 效果相同,因 count 為 8 的倍數,低 3 bits 恆為 0。finalcount (原始長度),觸發最後一次 SHA1Transform。state[5] 拆解為 20 bytes big-endian 輸出至 digest。SHA1Transform — 核心 80 輪運算輸入:state[5] (H0-H4)、buffer[64] (16 個 32-bit 字)。
擴展:16 字 → 80 字 W[0..79]:
W[t] = M[t] (t=0..15)W[t] = ROTL1(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]) (t=16..79)四輪非線性函數 f(t) 與常數 K(t):
| 輪次 | t 範圍 | f(t; B,C,D) | K(t) |
|---|---|---|---|
| R0/R1 | 0-19 | (B&C) | (~B&D) ≡ (W&(X^Y))^Y | 0x5A827999 |
| R2 | 20-39 | B ^ C ^ D | 0x6ED9EBA1 |
| R3 | 40-59 | (B&C) | (B&D) | (C&D) | 0x8F1BBCDC |
| R4 | 60-79 | B ^ C ^ D | 0xCA62C1D6 |
每輪更新 (A,B,C,D,E 對應 v,w,x,y,z):
TEMP = ROTL5(A) + f(t;B,C,D) + E + W[t] + K(t)
E = D; D = C; C = ROTL30(B); B = A; A = TEMP
輪後更新 H:H0+=A, H1+=B, H2+=C, H3+=D, H4+=E。
#define rol(v,b) (((v)<<(b)) | ((v)>>(32-(b)))) // 32-bit 循環左移
// 共用體:buffer 同時視為 64 bytes 或 16 個 32-bit 字
typedef union { unsigned char c[64]; u_int32_t l[16]; } CHAR64LONG16;
// blk0: 小端機器將 buffer[0..15] 轉大端 (網路序);大端機器不動作
#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) | (rol(block->l[i],8)&0x00FF00FF))
// blk: 實現 W[t] = ROTL1(W[t-3]^W[t-8]^W[t-14]^W[t-16]),利用模 16 循環覆寫 l[i&15]
#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15]^block->l[(i+2)&15]^block->l[i&15], 1))
// 四輪運算巨集 (v,w,x,y,z 對應 A,B,C,D,E)
#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5); w=rol(w,30);
#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5); w=rol(w,30);
#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5); w=rol(w,30);
#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5); w=rol(w,30);
#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5); w=rol(w,30);
blk0 (直接取原始分塊字,含端序轉換)。blk (開始擴展生成 W[t]),故分離為 R1。blk 擴展,對應 R2/R3/R4。(context->count[0] >> 24) & 255 等位移操作語義為「除以 2^24 取低 8 位」,與機器儲存順序無關,確保輸出位元組序固定為 Big-Endian (網路序)。
Redis 的 SHA-1 實現展示了經典雜湊演算法的高效 C 語言移植技巧:串流式填充 (Update)、端序無關序列化、共用體零拷貝視圖、巨集展開 80 輪避免函數呼叫開銷、魔數常數優化。雖 SHA-1 已不建議用於密碼學簽名,但理解其結構對學習 SHA-256、BLAKE2 等現代雜湊函數極有幫助。