> 技术文档 > 【加解密与C】HASH系列(三)SM3

【加解密与C】HASH系列(三)SM3


SM3算法简介

SM3是中国国家密码管理局发布的密码杂凑算法标准,属于商用密码体系中的哈希算法。其输出为256位(32字节)固定长度的哈希值,安全性对标国际通用的SHA-256,但设计更注重抗碰撞性和效率,适用于数字签名、消息认证等场景。

SM3算法特点

  • 输出长度:256位。
  • 分组处理:输入消息按512位分组处理。
  • 填充规则:采用Merkle-Damgård结构,填充方式与SHA-256类似(附加比特\"1\"、消息长度及填充\"0\")。
  • 运算步骤:包含消息扩展、压缩函数迭代等步骤,使用非线性逻辑函数和移位操作。

SM3算法流程

  1. 消息填充
    对输入消息补足至512位的倍数,填充规则为:

    • 添加一个\"1\"比特。
    • 补\"0\"至长度模512余448。
    • 最后64位写入消息的原始长度(二进制)。

    示例公式

    padded_message = original_message || 0x80 || zeros || length_in_bits
  2. 消息扩展
    将每个512位分组扩展为132个字(每字32位):

    • 前16个字为原始分组。
    • 后续字通过异或、循环移位等操作生成。

    扩展公式

    W_j = P1(W_{j-16} ⊕ W_{j-9} ⊕ (W_{j-3} <<< 15)) ⊕ (W_{j-13} <<< 7) ⊕ W_{j-6}
  3. 压缩函数
    使用8个初始变量(IV)迭代处理扩展后的消息,涉及以下操作:

    • 逻辑函数:FFj、GGj(随轮次变化的布尔函数)。
    • 置换函数:P0、P1(字级混淆)。
    • 循环移位:左移12位或7位等。

    压缩核心公式

    SS1 = (A <<< 12) + E + (T_j <<< j)
  4. 最终哈希值
    将所有分组的压缩结果与初始IV异或,拼接后生成最终哈希值。

应用场景

  • 数字签名:与SM2公钥算法配合使用。
  • 数据完整性验证:如区块链、文件校验。
  • 密码派生:基于哈希的密钥生成(KDF)。

SM3.h

#ifndef _SM3_H_#define _SM3_H_ #include // 定义常量#define SM3_DIGEST_SIZE 32 // 摘要长度(字节)// SM3上下文结构typedef struct { uint32_t state[8]; // 中间状态 uint64_t count; // 已处理消息长度(位) uint8_t buffer[64]; // 当前分组} SM3_CTX;void sm3_init(SM3_CTX* ctx);void sm3_update(SM3_CTX* ctx, const uint8_t* data, size_t len);void sm3_final(SM3_CTX* ctx, uint8_t digest[SM3_DIGEST_SIZE]);#endif

SM3.cpp

#include #include #include \"SM3.h\"#define SM3_BLOCK_SIZE 64 // 分组长度(字节)// 初始向量IV (大端序)static const uint32_t IV[8] = { 0x7380166F, 0x4914B2B9, 0x172442D7, 0xDA8A0600, 0xA96F30BC, 0x163138AA, 0xE38DEE4D, 0xB0FB0E4E};// 循环左移static uint32_t ROTL(uint32_t X, int n) { return (X <> (32 - n));}// 布尔函数FFjstatic uint32_t FFj(uint32_t X, uint32_t Y, uint32_t Z, int j) { return (j < 16) ? (X ^ Y ^ Z) : ((X & Y) | (X & Z) | (Y & Z));}// 布尔函数GGjstatic uint32_t GGj(uint32_t X, uint32_t Y, uint32_t Z, int j) { return (j < 16) ? (X ^ Y ^ Z) : ((X & Y) | (~X & Z));}// 置换函数P0static uint32_t P0(uint32_t X) { return X ^ ROTL(X, 9) ^ ROTL(X, 17);}// 置换函数P1static uint32_t P1(uint32_t X) { return X ^ ROTL(X, 15) ^ ROTL(X, 23);}// 常量Tjstatic uint32_t Tj(int j) { return (j state, IV, sizeof(IV)); ctx->count = 0; memset(ctx->buffer, 0, SM3_BLOCK_SIZE);}// 处理单个512位分组static void sm3_compress(SM3_CTX* ctx, const uint8_t block[SM3_BLOCK_SIZE]) { uint32_t W[68]; // 消息扩展后的字 uint32_t W1[64]; // 用于压缩的字 uint32_t A, B, C, D, E, F, G, H; uint32_t SS1, SS2, TT1, TT2; // 1. 消息扩展 (手动解析大端序) for (int i = 0; i < 16; i++) { W[i] = ((uint32_t)block[i * 4] << 24) | ((uint32_t)block[i * 4 + 1] << 16) | ((uint32_t)block[i * 4 + 2] << 8) | (uint32_t)block[i * 4 + 3]; } for (int j = 16; j < 68; j++) { W[j] = P1(W[j - 16] ^ W[j - 9] ^ ROTL(W[j - 3], 15)) ^ ROTL(W[j - 13], 7) ^ W[j - 6]; } for (int j = 0; j state[0]; B = ctx->state[1]; C = ctx->state[2]; D = ctx->state[3]; E = ctx->state[4]; F = ctx->state[5]; G = ctx->state[6]; H = ctx->state[7]; for (int j = 0; j state[0] ^= A; ctx->state[1] ^= B; ctx->state[2] ^= C; ctx->state[3] ^= D; ctx->state[4] ^= E; ctx->state[5] ^= F; ctx->state[6] ^= G; ctx->state[7] ^= H;}// 更新消息数据void sm3_update(SM3_CTX* ctx, const uint8_t* data, size_t len) { size_t left = ctx->count / 8 % SM3_BLOCK_SIZE; size_t fill = SM3_BLOCK_SIZE - left; ctx->count += len * 8; // 更新位计数 // 处理不完整缓冲区 if (left && len >= fill) { memcpy(ctx->buffer + left, data, fill); sm3_compress(ctx, ctx->buffer); data += fill; len -= fill; left = 0; } // 处理完整分组 while (len >= SM3_BLOCK_SIZE) { sm3_compress(ctx, data); data += SM3_BLOCK_SIZE; len -= SM3_BLOCK_SIZE; } // 存储剩余数据 if (len) { memcpy(ctx->buffer + left, data, len); }}// 生成最终摘要void sm3_final(SM3_CTX* ctx, uint8_t digest[SM3_DIGEST_SIZE]) { size_t last = (ctx->count / 8) % SM3_BLOCK_SIZE; size_t padn = (last count; uint8_t len_bytes[8]; // 手动构造大端序长度 for (int i = 0; i > (56 - 8 * i)) & 0xFF; } sm3_update(ctx, footer, padn); sm3_update(ctx, len_bytes, 8); // 输出大端序摘要 for (int i = 0; i state[i] >> 24) & 0xFF; digest[4 * i + 1] = (ctx->state[i] >> 16) & 0xFF; digest[4 * i + 2] = (ctx->state[i] >> 8) & 0xFF; digest[4 * i + 3] = ctx->state[i] & 0xFF; }}