全部例程

硬件加密中实现SHA (SHA-1 / SHA-256)

W55MH32

更新于 2026年9月12日

本篇总结

使用硬件 SHA 引擎,对数据生成 SHA-1 / SHA-224 / SHA-256 摘要。

🔑 KEIL MDK、UART调试、SHA

硬件平台:本例程基于 W55MH32L-EVB 开发板运行。

采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB

WIZnet W55MH32 集成硬件加密引擎,支持 SHA-1(160 位摘要)、SHA-224(224 位摘要)和 SHA-256(256 位摘要)。
本示例演示如何调用硬件引擎完成三种 SHA 算法的哈希运算,并通过串口打印结果。

前置工具准备

开始本例程前,请确认以下工具已安装:

硬件连接

加密引擎为片内外设,无需额外连线。结果通过 USART1 串口打印,USART1 的 TX(PA9) 和 RX(PA10) 通过板载 USB 转串口芯片连接到 DEBUG USB 口。仅需一根 Micro USB 线连接开发板 DEBUG USB 口到 PC 即可。

引脚功能方向模式
PA9USART1_TX开发板 → PC复用推挽输出 (AF_PP)
PA10USART1_RXPC → 开发板浮空输入 (IN_FLOATING)

工程结构

工程位于 SDK 内 1.SDK\ModuleDemo\ 下硬件加密例程目录,核心文件如下:

硬件加密例程/
├── USER/
│   ├── main.c            ← 主流程:时钟/USART 初始化 + 调用 SHA 测试
│   ├── sha_test.c        ← SHA_Update_Test() / SHA_Func_Test() 实现
│   ├── w55mh32_conf.h    ← 外设头文件集中包含
│   ├── w55mh32_it.c/h    ← 中断服务函数
│   ├── system_w55mh32.c/h← 系统初始化(时钟配置)
│   └── startup_w55mh32.s ← 启动文件
└── SYSTEM/
    └── delay/
        └── delay.c/h      ← SysTick 延时函数

SHA 简介

安全散列算法(SHA)是一种密码学散列函数,可将任意长度的输入数据转换成固定长度的摘要(哈希值)。W55MH32 通过硬件加密引擎支持 SHA-1(160 位 / 20 字节摘要)、SHA-224(224 位 / 28 字节摘要)和 SHA-256(256 位 / 32 字节摘要),实现加速、低功耗的哈希运算。

SHA 硬件引擎提供两种调用方式:三阶段(Start-Update-Finish) 和 一次性(WIZSHA_Sha)。

  • 三阶段:Start 初始化并选择算法 → Update 分段输入数据(可多次调用)→ Finish 输出摘要。适合大文件、流式数据、增量哈希
  • 一次性:WIZSHA_Sha() 一个调用完成全部运算,适合小数据、一次性处理

三阶段的优势

三阶段结构支持大文件、流数据或网络数据包的分段处理,无需一次性载入全部数据,降低内存占用。此外还支持 Clone 操作——在任意时刻拷贝当前上下文,得到中间状态的摘要快照,不打断主流程。

在 SSL/TLS 协议中,SHA 主要用于消息完整性校验(确保传输未被篡改)、证书验证(配合数字签名)和密钥派生(在 PRF 中协助生成会话密钥)。此外还可用于安全固件升级、密码存储、数字签名与数据完整性检查。

步骤1:包含头文件

使用 SHA 功能时,需包含 WIZnet 加密库的 wiz_sha.h 头文件。

#include "w55mh32.h"
#include "wiz_sha.h"
#include <stdio.h>
#include <string.h>

步骤2:SHA 三阶段流程(Start / Update / Finish / Clone)

SHA 哈希运算通过三组函数完成:Start 启动、Update 喂数据、Finish 出结果。此外还支持 Clone 拷贝中间状态。

WIZSHA1_Context ctx;     /* SHA-1 上下文 */
WIZSHA2_Context ctx2;    /* SHA-2 系列(224/256)上下文 */

/* Start:启动 SHA 引擎 */
WIZSHA1_Starts(&ctx);       // SHA-1
WIZSHA256_Starts(&ctx2);    // SHA-256

/* Update:分段输入数据(可多次调用) */
WIZSHA1_Update(&ctx, TestSHA, 100);   // SHA-1,输入 100 字节
WIZSHA256_Update(&ctx2, buf0, 0x40);  // SHA-256,输入 64 字节
WIZSHA256_Update(&ctx2, buf1, 0x40);  // SHA-256,继续输入 64 字节

/* Clone:拷贝当前上下文,得到中间摘要(不打断主流程) */
WIZSHA256_Clone(&ctx2, digest);       // 复制 ctx2 到 digest 缓冲区
ouputRes("SHA256 Clone result: ", digest, 32);

/* Finish:完成运算,输出最终哈希摘要 */
WIZSHA1_Finish(&ctx, digest);         // SHA-1 → digest
WIZSHA256_Finish(&ctx2, digest);      // SHA-256 → digest

如果你想自定义

  • 选择算法 → 用 WIZSHA1_* 系列(20 字节摘要)或 WIZSHA256_* 系列(32 字节摘要)
  • 分段处理大数据 → 多次调用 Update,每次传入部分数据块,无需一次性载入全部
  • 获取中间摘要 → 用 Clone 拷贝上下文,主流程可继续 Update/Finish
  • 摘要缓冲区大小 → SHA-1 需至少 20 字节,SHA-224 需 28 字节,SHA-256 需 32 字节

步骤3:三阶段测试代码 SHA_Update_Test()

函数:SHA_Update_Test() — 演示 SHA-1 对 100 字节数据、SHA-256 对两段 64 字节数据的哈希运算,并用 Clone 获取中间摘要。

void SHA_Update_Test()
{
    uint16_t i;
    WIZSHA1_Context ctx;
    WIZSHA2_Context ctx2;
    uint8_t buf0[0x40];
    uint8_t buf1[0x40];
    uint8_t t0[] = "12";
    unsigned char TestSHA[100] =
        "012345678901234567890123456789012345678901234567890123456789012345678901"
        "2345678901234567890123456789";
    uint8_t digest[32] = {0};

    printf("Test SHA function...\r\n");

    /* ---- SHA-1 ---- */
    WIZSHA1_Starts(&ctx);
    WIZSHA1_Update(&ctx, TestSHA, 100);
    WIZSHA1_Finish(&ctx, digest);
    ouputRes("SHA160 result: ", digest, 16);

    /* ---- SHA-256(分两段输入 + Clone 中间摘要) ---- */
    memset(digest, 0, 32);
    memset(buf0, 'a', 0x40);
    memset(buf1, 'b', 0x40);

    WIZSHA256_Starts(&ctx2);
    WIZSHA256_Update(&ctx2, buf0, 0x40);
    WIZSHA256_Update(&ctx2, buf1, 0x40);
    WIZSHA256_Finish(&ctx2, digest);
    ouputRes("SHA256 result: ", digest, 32);

    /* ---- 重新计算,中途 Clone 拿中间摘要 ---- */
    memset(digest, 0, 32);
    memset(buf0, 'a', 0x40);
    memset(buf1, 'b', 0x40);
    WIZSHA256_Starts(&ctx2);
    WIZSHA256_Update(&ctx2, buf0, 0x40);
    WIZSHA256_Update(&ctx2, buf1, 0x40);

    WIZSHA256_Clone(&ctx2, digest);
    ouputRes("SHA256 Clone result: ", digest, 32);
    memset(digest, 0, 32);

    /* 主流程继续 Update 并 Finish,得到新摘要 */
    WIZSHA256_Update(&ctx2, buf0, 0x40);
    WIZSHA256_Update(&ctx2, buf1, 0x40);
    WIZSHA256_Finish(&ctx2, digest);
    ouputRes("SHA256 result: ", digest, 32);
}

SHA-256 部分演示了 分段 Update 与 Clone 的用法:先输入 buf0(64 个 'a'),再输入 buf1(64 个 'b'),Clone 得到当前状态的摘要;主流程继续追加 buf0+buf1 后 Finish,得到新的摘要。两次摘要不同,因为 Clone 只反映前两段数据,Finish 反映全部四段数据。

步骤4:一次性测试代码 SHA_Func_Test()

函数:SHA_Func_Test() — 用一次性函数 WIZSHA_Sha() 分别计算 SHA-1、SHA-224、SHA-256,并与预设摘要 memcmp 校验,通过 r_printf() 打印测试结果。

void SHA_Func_Test()
{
    uint8_t TestSHA[100] =
        "01234567890123456789012345678901234567890123456789012"
        "34567890123456789012345678901234567890123456789";
    uint8_t Cipher_SHA256[] = {0x9c, 0xfe, 0x7f, 0xaf, 0xf7, 0x05, 0x42, 0x98,
                               0xca, 0x87, 0x55, 0x7e, 0x15, 0xa1, 0x02, 0x62,
                               0xde, 0x8d, 0x3e, 0xee, 0x77, 0x82, 0x74, 0x17,
                               0xfb, 0xdf, 0xea, 0x1c, 0x41, 0xb9, 0xec, 0x23};
    uint8_t Cipher_SHA1[] = {0x29, 0xb0, 0xe7, 0x87, 0x82, 0x71, 0x64,
                             0x5f, 0xff, 0xb7, 0xee, 0xc7, 0xdb, 0x4a,
                             0x74, 0x73, 0xa1, 0xc0, 0x0b, 0xc1};
    uint8_t Cipher_SHA224[] = {0x2C, 0x09, 0xD5, 0x93, 0x30, 0x73, 0x6C,
                               0x5D, 0x53, 0x19, 0x0B, 0x36, 0x7D, 0x2D,
                               0x91, 0xCE, 0x54, 0xA5, 0x4A, 0xD9, 0x59,
                               0xA1, 0x6D, 0x40, 0x01, 0x83, 0xF0, 0x7F};
    uint8_t au8Cipher[32] = {0};

    // SHA-1 测试
    memset(au8Cipher, 0, 32);
    WIZSHA_Sha(WIZSHA1, TestSHA, sizeof(TestSHA), au8Cipher, 20);
    r_printf((0 == memcmp(Cipher_SHA1, au8Cipher, 20)), "WIZSHA_Sha 160 test\n");

    // SHA-224 测试
    memset(au8Cipher, 0, 32);
    WIZSHA_Sha(WIZSHA224, TestSHA, sizeof(TestSHA), au8Cipher, 28);
    r_printf((0 == memcmp(Cipher_SHA224, au8Cipher, 28)),
             "WIZSHA_Sha 224 test\n");

    // SHA-256 测试
    memset(au8Cipher, 0, 32);
    WIZSHA_Sha(WIZSHA256, TestSHA, sizeof(TestSHA), au8Cipher, 32);
    r_printf((0 == memcmp(Cipher_SHA256, au8Cipher, 32)),
             "WIZSHA_Sha 256 test\n");
}

关键点:WIZSHA_Sha() 通过第一个参数选择算法(WIZSHA1 / WIZSHA224 / WIZSHA256),最后两个参数是输出缓冲区和摘要长度。

步骤5:主函数调用

文件:USER/main.c | 函数:main() — 配置系统时钟 72MHz、初始化 USART1、打印加密库版本,然后调用 SHA_Test() 执行全部测试。

void SHA_Test() {
  SHA_Update_Test();   /* 三阶段流程测试 + Clone */
  SHA_Func_Test();     /* 一次性流程测试(SHA-1/224/256) */
}

void main(int args) {
  uint32_t ver = 1;
  RCC_ClocksTypeDef clocks;

  RCC_DeInit();
  RCC_HSEConfig(RCC_HSE_ON);
  RCC_WaitForHSEStartUp();

  RCC_PLLCmd(DISABLE);
  RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);  // 8MHz × 9 = 72MHz
  RCC_PLLCmd(ENABLE);
  while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
  RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
  RCC_HCLKConfig(RCC_SYSCLK_Div1);
  RCC_PCLK1Config(RCC_HCLK_Div2);
  RCC_PCLK2Config(RCC_HCLK_Div1);

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
  SysTick->CTRL |= 0x04;

  /* 使能加密引擎时钟/配置寄存器 */
  *(uint32_t *)(0x400210F0) = 0x01;
  *(uint32_t *)(0x40016C00) = 0xCDED3526;
  *(uint32_t *)(0x40016CCC) = 0x07;

  UART_Configuration(115200);
  WIZRAND_Init((uint32_t *)&u8RngBuff, sizeof(u8RngBuff));

  ver = wiz_crypt_version();
  DBG_PRINT("WIZnet CARD Secure Test Demo V1.0, secure lib version is "
            "V%02x.%02x.%02x.%02x\n",
            ver >> 24, (ver >> 16) & 0xFF, (ver >> 8) & 0xFF, ver & 0xFF);
  DBG_PRINT("WIZnet CARD Crypt Test V1.0 start......\r\n");

  /* 仅运行 SHA 测试,其余算法测试保留但被屏蔽 */
  SHA_Test();

  // AES_Test();
  // DES_Test();
  // TDES_Test();
  // SM1_Test();
  // SM3_Test();
  // SM4_Test();
  // SM7_Test();

  DBG_PRINT("SHA test finished, program halted.\r\n");

  while (1) {}
}

注意:调用加密引擎前必须执行那三行寄存器写入(0x400210F0、0x40016C00、0x40016CCC),这是 W55MH32 加密引擎的时钟与配置寄存器初始化,缺失会导致运算结果全 0。

运行验证

打开 WIZ UartTool V1.0,选择开发板对应 COM 口,波特率 115200 / 8N1,打开串口。程序运行后串口依次打印库版本、测试开始,以及 SHA 各项测试结果。预期输出如下:

WIZnet CARD Secure Test Demo V1.0, secure lib version is V03.01.04.00
WIZnet CARD Crypt Test V1.0 start......

Test SHA function...

SHA160 result: 29B0E7878271645FFFB7EEC7DB4A7473
SHA256 result: FA0DAFBF43F1F551E536353E9D1A942A8E86E41A0B58DFEAF264EF217F6B862A
SHA256 Clone result: FA0DAFBF43F1F551E536353E9D1A942A8E86E41A0B58DFEAF264EF217F6B862A
SHA256 result: 1F53BFD93CDB00487FF49F0E288DDBF29CE80D0EE0CF1097B6E3A0763E57E7F
Pass WIZSHA_Sha 160 test
Pass WIZSHA_Sha 224 test
Pass WIZSHA_Sha 256 test
SHA test finished, program halted.
WIZ UartTool 串口输出 — SHA 硬件加密测试结果
用于验证 SHA-1/SHA-256 计算结果的 Python 脚本
Python 脚本运行输出 SHA-1/SHA-256 哈希值对比结果


常见问题

Q: 编译报错 "cannot open source file wiz_sha.h"

检查 Project → Options → C/C++ → Include Paths,确保 WIZnet 加密库的头文件目录已添加。

Q: 哈希结果全 0

检查两点:① 加密引擎初始化那三行寄存器写入是否执行(0x400210F0 / 0x40016C00 / 0x40016CCC);② 三阶段调用顺序为 Start → Update → Finish,三者缺一不可。

Q: 分段 Update 后摘要与单次输入不一致

检查各段数据拼接后是否与单次输入完全相同。分段处理不改变哈希结果——只要总输入数据一致,摘要必定相同。确认分段时数据没有丢失或重复。

Q: Clone 出来的摘要与 Finish 的不一样

Clone 只反映当前已 Update 的数据对应的摘要,Finish 反映全部数据处理完的摘要。若 Clone 后主流程又 Update 了新数据,两次摘要必然不同。这是正常的——Clone 用于获取中间状态快照,不修改主流程上下文。

Q: SHA-1 与 SHA-256 结果混淆

SHA-1 摘要 20 字节,SHA-224 摘要 28 字节,SHA-256 摘要 32 字节,打印循环次数不同。确认 ouputRes() 或 memcmp 的长度参数与算法匹配,且 Context 变量未混用。

Q: 串口输出乱码

先确认 PC 端波特率与 UART_Configuration(115200) 一致。若哈希摘要中的十六进制字符乱码,检查 printf 重定向的 fputc() 是否正确补 \r。

总结

本文基于 W55MH32 硬件加密引擎实现了 SHA-1 / SHA-224 / SHA-256 哈希运算:

  • 硬件加速:SHA 运算由片内引擎完成,主 CPU 零参与,显著提升处理速度与能效
  • 两种调用方式:三阶段(Starts/Update/Finish)支持分段与流式;一次性(WIZSHA_Sha)适合小数据
  • Clone 支持:三阶段流程中可在任意时刻拷贝上下文获取中间摘要,主流程不受影响
  • 三种算法:SHA-1 出 20 字节摘要,SHA-224 出 28 字节,SHA-256 出 32 字节,通过对应的 Context 与函数组区分
  • 典型应用:SSL/TLS 消息完整性校验、固件升级验证、密码存储、数字签名——单向不可逆,验完整性防篡改

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Andy

W5500 技术支持专家