用 OpenSSL 的 HMAC() 函数计算 HMAC-SHA1 最稳妥:传 EVP_sha1()、准确的 key 指针与长度、20 字节输出缓冲;大文件需流式处理,用 HMAC_CTX_new() + HMAC_Update();对比前确保双方均为原始字节(统一 hex 或 base64 解码),并排除换行/BOM/路径差异。

怎么用 OpenSSL 在 C++ 里算 HMAC-SHA1 签名
直接调 HMAC() 函数最稳,别自己拼接 key 和数据再喂给 SHA1() —— 那不是 HMAC,是错的。OpenSSL 的 HMAC() 会自动处理 key 填充、两次哈希等细节。
常见错误:传入的 key 长度超过 SHA1 分块大小(64 字节),没做预哈希;或者把 base64 编码后的签名当原始字节比对。
-
HMAC()第一个参数必须是EVP_sha1(),不是SHA1() - key 指针和长度要准确:
const unsigned char*+int,别传std::string::c_str()后忘掉.length() - 输出缓冲区至少留 20 字节(SHA1 输出长度),别开 16 字节——那是 MD5 的
unsigned char hmac_out[20]; unsigned int hmac_len; HMAC(EVP_sha1(), key, key_len, data, data_len, hmac_out, &hmac_len); // hmac_len 一定是 20,但还是要传地址给它填
验证文件签名时,怎么读文件又不崩内存
大文件不能一次性 fread() 到内存再算 HMAC —— 不是算不准,是可能 OOM 或触发系统限制。HMAC 支持流式更新,得用 HMAC_CTX(OpenSSL 1.1.1+ 推荐用 HMAC_CTX_new())。
容易踩的坑:在循环读文件时,反复调 HMAC_Init_ex() 重置上下文,结果只算最后一块;或者忘了在 HMAC_Final() 后调 HMAC_CTX_free(),导致句柄泄漏。
立即学习“C++免费学习笔记(深入)”;
- 初始化一次
HMAC_CTX,然后循环HMAC_Update() - 每块数据用
fread(buf, 1, BUFSIZ, fp)读,别用fgets()(会截断二进制) - Windows 下打开文件必须用
"rb"模式,否则换行符被转义,签名必然不匹配
对比签名时,为什么 memcmp 总是失败
不是算法问题,大概率是编码或截断惹的祸。原始 HMAC 输出是 20 字节二进制,如果服务端返回的是 base64 或 hex 字符串,你本地没解码就直接 memcmp(),肯定失败。
另一个高频问题:签名字符串末尾有不可见字符(比如换行、BOM、空格),std::string 构造时没 trim。
- 确认双方都操作「原始字节」:要么全走 hex(
01a2b3...),要么全走 base64(AbC12+/=),别混用 - hex 解码示例:用
sscanf()或std::stoi(..., nullptr, 16)逐字节转,别手写循环漏了大小写 - 用
std::vector<unsigned char></unsigned>存原始签名,别用std::string—— 它可能在遇到\0时截断
跨平台签名不一致?检查这三处
Linux 和 Windows 算出来不一样,90% 是文件内容本身就不一样,不是算法问题。重点盯住换行、BOM、路径分隔符带来的隐式修改。
比如用 std::ofstream 写文件时没设 std::ios::binary,Windows 自动把 \n 变成 \r\n;或者用 std::filesystem::path 拼路径时,在 Windows 返回 dir\file,但你又手动替换成 /,结果路径字符串参与了签名计算(不该参与)。
- 签名对象必须是纯文件内容,排除文件名、修改时间、权限位等元信息
- 用
xxd -p file.bin | tr -d '\n'(Linux)或certutil -hashfile file.bin SHA1(Windows)手动验基准值 - 调试时打印前 8 字节十六进制:
printf("%02x%02x%02x...", hmac_out[0], hmac_out[1], ...),比直接std::cout 更可靠
真正麻烦的从来不是 HMAC 本身,而是你怎么定义“同一个文件”——二进制一字不差,才是签名验证成立的前提。其他所有步骤,都是为守住这个前提服务的。



















