444 字
1 分钟
sha256从原理到实现
sha256从原理到cpp实现
一、原理
我们直接来看流程图
flowchart TD
%% 样式定义
classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8;
classDef input fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a,rx:10,ry:10;
classDef preprocess fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e;
classDef init fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f;
classDef condition fill:#fce7f3,stroke:#db2777,stroke-width:2px,color:#831843;
classDef compress fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d;
classDef update fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87;
classDef final fill:#d1fae5,stroke:#059669,stroke-width:2px,color:#064e3b,rx:10,ry:10;
classDef output fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#4c1d95,rx:10,ry:10;
A["输入消息 M"]:::input
B["步骤1: 消息填充 Padding"]:::preprocess
C["步骤2: 分块 512 bit"]:::preprocess
D["步骤3: 初始化 8 个 32bit 寄存器<br/>H0 ~ H7"]:::init
E{"还有 512bit 块吗?"}:::condition
F["步骤4: 消息扩展<br/>16 → 64 字"]:::compress
G["步骤5: 64 轮压缩"]:::compress
H["步骤6: 更新 H0 ~ H7"]:::update
I["步骤7: 拼接 H0 ~ H7"]:::final
J["输出 256bit 摘要"]:::output
A --> B
B --> C
C --> D
D --> E
E -- 是 --> F
F --> G
G --> H
H --> E
E -- 否 --> I
I --> J
%% 连线样式
linkStyle default stroke:#6b7280,stroke-width:2px;
我们从图中可以直观对比一下sha256与md5和sha1
| 对比项 | MD5 | SHA-1 | SHA-256 |
|---|---|---|---|
| 寄存器数量 | 4 个 32 位 (A, B, C, D) | 5 个 32 位 (A, B, C, D, E) | 8 个 32 位 (H0 ~ H7) |
| 消息扩展 | 无 | 有 | 有 |
| 扩展后字数 | 16 字 (不扩展) | 80 字 (W0 ~ W79) | 64 字 (W0 ~ W63) |
| 压缩轮数 | 64 轮 (4 轮 × 16 步) | 80 轮 (4 轮 × 20 步) | 64 轮 (64 步) |
| 最终输出位数 | 128 位 | 160 位 | 256 位 |
填充
flowchart LR
A["原始消息 M<br/>(L bits)"] --> B["追加 1 bit"]
B --> C["追加 k 个 0<br/>使 L+1+k ≡ 448 mod 512"]
C --> D["追加 64 bit 大端 L"]
D --> E["总长是 512 的倍数"]
这里和sha1完全一样,追加的八字节也是大端序。
初始化寄存器
这个跟之前不同了。这些是前 8 个素数(2,3,5,7,11,13,17,19)平方根小数部分的前 32 位。
H0 = 0x6a09e667 H1 = 0xbb67ae85H2 = 0x3c6ef372 H3 = 0xa54ff53aH4 = 0x510e527f H5 = 0x9b05688cH6 = 0x1f83d9ab H7 = 0x5be0cd19消息扩展
flowchart TD
%% 样式定义
classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8;
classDef input fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a,rx:10,ry:10;
classDef loop fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f;
classDef sigma0 fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e;
classDef sigma1 fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d;
classDef formula fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#4c1d95;
classDef output fill:#d1fae5,stroke:#059669,stroke-width:2px,color:#064e3b,rx:10,ry:10;
A["W0..W15<br/>= 大端读入的<br/>16 个 32bit 字"]:::input
B["for t = 16..63"]:::loop
C["σ0 = rotr(Wt-15, 7)<br/>^ rotr(Wt-15, 18)<br/>^ shr(Wt-15, 3)"]:::sigma0
D["σ1 = rotr(Wt-2, 17)<br/>^ rotr(Wt-2, 19)<br/>^ shr(Wt-2, 10)"]:::sigma1
E["Wt = Wt-16 + σ0<br/>+ Wt-7 + σ1"]:::formula
F["得到 W0..W63"]:::output
A --> B
B --> C
C --> D
D --> E
E --> B
B -->|结束| F
%% 连线样式
linkStyle default stroke:#6b7280,stroke-width:2px;
注意这里 σ0/σ1 用的是 rotr(循环右移) 和 shr(逻辑右移) 的组合,与 SHA-1 的简单 rotl 不同。
64轮压缩
用到的辅助函数Ch(x,y,z) = (x & y) ^ (~x & z)Maj(x,y,z) = (x & y) ^ (x & z) ^ (y & z)Σ0(x) = rotr(x,2) ^ rotr(x,13) ^ rotr(x,22)Σ1(x) = rotr(x,6) ^ rotr(x,11) ^ rotr(x,25)σ0(x) = rotr(x,7) ^ rotr(x,18) ^ shr(x,3)σ1(x) = rotr(x,17) ^ rotr(x,19) ^ shr(x,10)
每一轮的公式:T1 = h + Σ1(e) + Ch(e,f,g) + K[t] + W[t]T2 = Σ0(a) + Maj(a,b,c)h = gg = ff = ee = d + T1d = cc = bb = aa = T1 + T2flowchart TD
%% 样式定义
classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8;
classDef init fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a,rx:10,ry:10;
classDef t1 fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e;
classDef t2 fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d;
classDef update fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87;
classDef condition fill:#fce7f3,stroke:#db2777,stroke-width:2px,color:#831843;
classDef final fill:#d1fae5,stroke:#059669,stroke-width:2px,color:#064e3b,rx:10,ry:10;
A["a, b, c, d, e, f, g, h"]:::init
B["T1 = h + Σ1(e) + Ch(e,f,g)<br/>+ Kt + Wt"]:::t1
C["T2 = Σ0(a) + Maj(a,b,c)"]:::t2
D["h = g<br/>g = f<br/>f = e<br/>e = d + T1"]:::update
E["d = c<br/>c = b<br/>b = a<br/>a = T1 + T2"]:::update
F{"还有轮次?"}:::condition
G["H0 += a<br/>H1 += b<br/>...<br/>H7 += h"]:::final
A --> B
A --> C
B --> D
C --> D
D --> E
E --> F
F -->|是| A
F -->|否| G
%% 连线样式
linkStyle default stroke:#6b7280,stroke-width:2px;
累加
H0 += a; H1 += b; ... H7 += h可以看到,大体上和前两个没啥区别,但是细看的话,内部实现确实一套新的逻辑,正所谓只可远观而不可亵玩焉。
二、实现
依旧从应用层入手,结构完全一样,我们直接看主要函数。
// ============================================================// 一次性接口// ============================================================std::string sha256(const std::string& input) { SHA256Context ctx; sha256Update(ctx, reinterpret_cast<const uint8_t*>(input.data()), input.size());
uint8_t digest[32]; sha256Final(ctx, digest);
std::ostringstream oss; for (int i = 0; i < 32; i++) { oss << std::hex << std::setw(2) << std::setfill('0') << (int)digest[i]; } return oss.str();}1.context
// ============================================================// SHA-256 上下文// ============================================================struct SHA256Context { uint32_t state[8]; // H0..H7 uint64_t bitCount; // 已处理比特数 uint8_t buffer[64]; // 未处理完的块 size_t bufferLen;
SHA256Context() { init(); }
void init() { // 前 8 个素数平方根小数部分前 32 位(FIPS 180-4) state[0] = 0x6a09e667; state[1] = 0xbb67ae85; state[2] = 0x3c6ef372; state[3] = 0xa54ff53a; state[4] = 0x510e527f; state[5] = 0x9b05688c; state[6] = 0x1f83d9ab; state[7] = 0x5be0cd19; bitCount = 0; bufferLen = 0; }};2.update
// ============================================================// 更新// ============================================================static void sha256Update(SHA256Context& ctx, const uint8_t* data, size_t len) { ctx.bitCount += (uint64_t)len * 8;//计算已经处理的字节数,第一次调用就是原始消息长度
while (len > 0) { size_t space = 64 - ctx.bufferLen; //计算缓冲区剩余空间 size_t copy = (len < space) ? len : space;
std::memcpy(ctx.buffer + ctx.bufferLen, data, copy); ctx.bufferLen += copy; data += copy; //移动指针 len -= copy;
if (ctx.bufferLen == 64) { sha256Transform(ctx.state, ctx.buffer); ctx.bufferLen = 0; } }}3.transform
工具函数
// ============================================================// SHA-256 常数 K[0..63]// 来源:前 64 个素数立方根小数部分的前 32 位(FIPS 180-4)// ============================================================static const uint32_t K[64] = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
// ============================================================// 大端序读写工具(SHA-256 规定大端)// ============================================================static inline uint32_t read_be32(const uint8_t* p) { return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3];}
static inline void write_be32(uint8_t* p, uint32_t v) { p[0] = (uint8_t)((v >> 24) & 0xff); p[1] = (uint8_t)((v >> 16) & 0xff); p[2] = (uint8_t)((v >> 8) & 0xff); p[3] = (uint8_t)( v & 0xff);}
// 循环右移static inline uint32_t rotr(uint32_t x, uint32_t n) { return (x >> n) | (x << (32 - n));}
// ============================================================// 6 个辅助函数// ============================================================static inline uint32_t Ch (uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (~x & z);}static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (x & z) ^ (y & z);}static inline uint32_t Sigma0(uint32_t x) { // 大写 Σ0 return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22);}static inline uint32_t Sigma1(uint32_t x) { // 大写 Σ1 return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25);}static inline uint32_t sigma0(uint32_t x) { // 小写 σ0 return rotr(x, 7) ^ rotr(x, 18) ^ (x >> 3);}static inline uint32_t sigma1(uint32_t x) { // 小写 σ1 return rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10);}// ============================================================// 处理一个 512bit 块// ============================================================static void sha256Transform(uint32_t state[8], const uint8_t block[64]) { uint32_t W[64];
// 1. 前 16 字:大端读入 for (int i = 0; i < 16; i++) { W[i] = read_be32(block + i * 4); }
// 2. 扩展到 64 字 for (int i = 16; i < 64; i++) { W[i] = sigma1(W[i - 2]) + W[i - 7] + sigma0(W[i - 15]) + W[i - 16]; }
// 3. 加载工作变量 uint32_t a = state[0], b = state[1], c = state[2], d = state[3]; uint32_t e = state[4], f = state[5], g = state[6], h = state[7];
// 4. 64 轮 for (int i = 0; i < 64; i++) { uint32_t T1 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; uint32_t T2 = Sigma0(a) + Maj(a, b, c);
h = g; g = f; f = e; e = d + T1; d = c; c = b; b = a; a = T1 + T2; }
// 5. 累加回 state state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; state[5] += f; state[6] += g; state[7] += h;}4.dofinal
最后再看看
// ============================================================// 收尾:填充 + 长度 + 输出 32 字节// ============================================================static void sha256Final(SHA256Context& ctx, uint8_t digest[32]) { uint8_t padding[64]; std::memset(padding, 0, sizeof(padding)); padding[0] = 0x80;
uint64_t bits = ctx.bitCount;
size_t padLen = (ctx.bufferLen < 56) ? (56 - ctx.bufferLen) : (120 - ctx.bufferLen);
sha256Update(ctx, padding, padLen);
uint8_t lenBytes[8]; for (int i = 0; i < 8; i++) { lenBytes[i] = (uint8_t)((bits >> (8 * (7 - i))) & 0xff); } sha256Update(ctx, lenBytes, 8);
for (int i = 0; i < 8; i++) { write_be32(digest + i * 4, ctx.state[i]); }}ok,水完了。
64轮变换在transform里,我这种密码学菜鸟暂时碰不得。
所以目前比较简单的能够识别的魔改点就是k表,初始的八个寄存器值。😭
三、唠嗑
欢乐的时光总是短暂,又到了吃饭时间,我去吃饭了886,咱们下期再见。
下期讲讲HMAC。
部分信息可能已经过时









