阿里云 V4 签名实战(第3篇)· C++:手写 OSS PutObject V4 签名(CMake + OpenSSL,附完整工程)

简介: 签名算法与语言无关,坑全在字符串处理里。本篇用 C++17 header-only 签名器实现 OSS V4 签名(OSS4-HMAC-SHA256)并经 libcurl 完成 PutObject:二进制安全的 HMAC 密钥链、std::map 规范化头、自实现 URI 编码、UTC 时间生成,CMake 一键构建并把官方文档示例接入 CTest,与 Python 版实现交叉验证逐字节一致,附 7 条 C++ 签名踩坑清单。

「V4 签名实战」系列第 3 篇,语言轮到 C++。签名原理在第 1 篇(Python)已逐字节讲透,本篇聚焦 C++ 落地时的工程问题:header-only 签名器设计、二进制安全的 HMAC 中间值、自实现 URI 编码、CMake + OpenSSL 构建,以及如何把官方文档示例做成 CTest 自测。签名算法本身与第 1 篇完全同源,两份实现共用同一组官方测试向量。

配套完整工程(可直接导入编辑器构建运行):
https://github.com/zhoubiao188/aliyun-v4-signature-examples/tree/main/cpp-oss-putobject-v4

一、C++ 实现签名的四个工程难点

手写 OSS V4 签名在 Python 里是 130 行的事,在 C++ 里真正的难点不在算法,而在工程细节:

  1. HMAC 中间值是二进制:派生密钥链 kDate → kRegion → kService → kSigning 每一步的输出都是 32 字节二进制摘要,要作为下一步的 key。C++ 里必须用 std::string(二进制安全)传递,一旦经过 c_str()、strlen 或 char* 拼接,遇到 0x00 字节就会截断——这是 C++ 版签名最隐蔽的 bug 来源;
  2. 规范化头的排序:签名要求头名小写 + 字典序。C++ 里直接用 std::map<std::string, std::string>,天然按 key 字典序遍历,比手工排序可靠;
  3. URI 编码不能偷懒:libcurl 的 curl_easy_escape 会把空格编成 + 之外还有细节差异,OSS V4 要求"仅 A-Za-z0-9-._~ 不编码、空格为 %20、路径保留 /",自己写 20 行才是正解;
  4. 时间必须 UTC:x-oss-date 形如 20261009T145101Z,要用 gmtime_r + strftime,误用 localtime 在东八区会把签名时间提前 8 小时,直接 RequestTimeTooSkewed。

二、工程结构与构建

cpp-oss-putobject-v4/
├── CMakeLists.txt
├── include/oss_v4_signer.hpp   # header-only 签名器(可直接复用到你的项目)
├── src/main.cpp                # libcurl 发起 PutObject
└── test/test_signature.cpp     # 官方示例向量自测(接入 CTest)

构建与运行(依赖只有一个 OpenSSL Crypto 和 libcurl):

# macOS:  brew install cmake openssl
# Ubuntu: sudo apt install cmake g++ libssl-dev libcurl4-openssl-dev

cmake -B build -DCMAKE_BUILD_TYPE=Release
# macOS 找不到 OpenSSL 时追加: -DOPENSSL_ROOT_DIR=$(brew --prefix openssl)
cmake --build build

ctest --test-dir build --output-on-failure      # ① 官方向量自测(无需网络和真实 AK)
./build/oss_putobject_v4 --dry-run              # ② 打印签名全过程
export ALIBABA_CLOUD_ACCESS_KEY_ID=<AK>
export ALIBABA_CLOUD_ACCESS_KEY_SECRET=<SK>
export OSS_BUCKET=<你的Bucket>
./build/oss_putobject_v4                        # ③ 真实上传,预期 HTTP 200 + ETag

CMakeLists 只有三块值得看:find_package(OpenSSL REQUIRED)、find_package(CURL REQUIRED),以及把自测挂进 CTest:

add_executable(v4_selftest test/test_signature.cpp)
target_include_directories(v4_selftest PRIVATE include)
target_link_libraries(v4_selftest PRIVATE OpenSSL::Crypto)   # 自测不需要 curl

enable_testing()
add_test(NAME v4_selftest COMMAND v4_selftest)

三、核心代码逐段讲解

HMAC 与 SHA256(OpenSSL 的 HMAC 接口返回二进制,用 std::string 原样接住):

inline std::string hmac_sha256(const std::string& key, const std::string& msg) {
   
    unsigned char md[EVP_MAX_MD_SIZE];
    unsigned int md_len = 0;
    HMAC(EVP_sha256(), key.data(), static_cast<int>(key.size()),
         reinterpret_cast<const unsigned char*>(msg.data()), msg.size(), md, &md_len);
    return std::string(reinterpret_cast<char*>(md), md_len);
}

派生密钥链——四步 HMAC,上一步输出直接做下一步 key,全程二进制安全:

std::string k_date    = hmac_sha256("aliyun_v4" + sk, date);        // 日期取 x-oss-date 前 8 位
std::string k_region  = hmac_sha256(k_date, region);
std::string k_service = hmac_sha256(k_region, "oss");
std::string k_signing = hmac_sha256(k_service, "aliyun_v4_request");
std::string signature = to_hex(hmac_sha256(k_signing, string_to_sign));

规范化请求——std::map 自动字典序,每行头以 \n 结尾,五段拼接:

std::string canonical_headers;
for (const auto& kv : headers)                       // std::map 天然按 key 排序
    canonical_headers += kv.first + ":" + trim(kv.second) + "\n";

std::string canonical_request = method + "\n" + canonical_uri + "\n" + canonical_query +
                                "\n" + canonical_headers + "\n" + additional_str + "\n" +
                                "UNSIGNED-PAYLOAD";  // 当前 V4 仅支持此固定值

待签串与 Authorization:

std::string scope = date + "/" + region + "/oss/aliyun_v4_request";
std::string string_to_sign = "OSS4-HMAC-SHA256\n" + x_oss_date + "\n" + scope + "\n" +
                             sha256_hex(canonical_request);
std::string authorization = "OSS4-HMAC-SHA256 Credential=" + ak + "/" + scope +
                            ",Signature=" + signature;

发起请求(libcurl,把签名相关头逐个挂上):

curl_slist* hdrs = nullptr;
hdrs = curl_slist_append(hdrs, ("Authorization: " + r.authorization).c_str());
hdrs = curl_slist_append(hdrs, "x-oss-content-sha256: UNSIGNED-PAYLOAD");
hdrs = curl_slist_append(hdrs, ("x-oss-date: " + std::string(x_oss_date)).c_str());
hdrs = curl_slist_append(hdrs, ("content-type: " + content_type).c_str());
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.data());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs);

四、官方向量自测:跨语言交叉验证

test/test_signature.cpp 使用与第 1 篇 Python 完全相同的官方文档测试向量,构建后 ctest 输出:

[PASS] CanonicalRequest 的 SHA256(官方示例值)
       c46d96390bdbc2d739ac9363293ae9d710b14e48081fcb22cd8ad54b63136eca
[PASS] StringToSign(用文档 SigningKey HMAC = 官方签名)
       053edbf550ebd239b32a9cdfd93b0b2b3f2d223083aa61f75e9ac16856d61f23
[PASS] 本工程 SigningKey(官方 SDK 公式,对照项)
       8a01ff4efcc65ca2cbc75375045c61ab5f3fa8b9a2d84f0add27ef16a25feb3c

第三项的 SigningKey 与 Python 篇实现算出的值逐字节相同——两门语言、两份独立实现、同一个官方公式,这就是"测试向量背书"的价值:它证明的不是代码跑通了,而是每一处换行、排序、编码都与官方规范一致。CMake 已把自测挂进 CTest,cmake --build 后顺手 ctest 即可回归。

五、踩坑清单

  1. HMAC 中间值过 c_str()/strlen:二进制摘要含 0x00,C 字符串函数会截断,密钥链输出全错;全程 std::string;
  2. localtime 而非 gmtime_r:UTC 时间错 8 小时,RequestTimeTooSkewed;
  3. 用 curl_easy_escape 做 URI 编码:它的转义集与 OSS V4 规范不一致(空格、波浪号等差异),自行实现 20 行才可靠;
  4. std::map 被换成 std::unordered_map:迭代顺序不再是字典序,规范化头排序直接破坏;
  5. macOS 找不到 OpenSSL 头文件:Apple 自带 LibreSSL 且不在默认路径,brew install openssl 后加 -DOPENSSL_ROOT_DIR=$(brew --prefix openssl);
  6. 自测没接入构建系统:签名代码改一行没回归就上线是最危险的,CTest 里挂上官方向量,改坏当场爆;
  7. EVP_MAX_MD_SIZE 忘了用:摘要缓冲区按固定 32 字节硬编码虽然对 HMAC-SHA256 恰好不错,但读 md_len 才是正途。

六、小结与下一篇预告

本篇把 OSS V4 签名在 C++ 里完整落地:header-only 签名器 + libcurl 上传 + CTest 官方向量回归,签名器只有两个头文件依赖,可直接拷进任何 C++ 项目。三种语言写完同一套签名后你会发现:算法永远不变,变的只是字符串与字节的处理习惯——而签名 bug 几乎全部藏在字符串处理里。

下一篇(第 4 篇)回到 Python:手写 ACS3-HMAC-SHA256 签名调用 CDN DescribeCdnDomainLogs,对比 OSS 专用签名与全系 OpenAPI 通用签名的结构差异。欢迎收藏仓库:https://github.com/zhoubiao188/aliyun-v4-signature-examples

声明:本文为作者个人实践笔记,签名细节以阿里云官方签名文档为准;文中无任何真实密钥。

相关文章
|
1天前
|
人工智能 安全 小程序
阿里云服务器199元1年购买与使用常见问题:2核4G5M带宽,续费同价,企业用户专享
阿里云199元/年通用算力型u1实例(ecs.u1-c1m2.large)面向企业实名认证用户,配置2核4G、5Mbps固定公网带宽、80GB ESSD Entry云盘,主打"续费同价",活动延至2029年3月31日,最长可同价使用5年,适合建站、开发测试、后台管理、小程序及跨境出海等通用场景;个人账号无法购买,同一企业主体最多保有一台,适合预算敏感的中小企业长期选用。
|
2天前
|
弹性计算 Java 开发工具
阿里云 V4 签名实战(第1篇)· Python:手写 OSS PutObject V4 签名(附完整工程)
为什么 SDK 之外还要手写签名?本篇用约 130 行纯 Python 实现阿里云 OSS V4 签名(OSS4-HMAC-SHA256)完成 PutObject 上传:四步流程图解、规范化请求与派生密钥链逐段讲解,配套完整工程可直接导入运行,并用官方文档示例做离线测试向量证明实现逐字节正确,附 7 条签名踩坑清单。
67 27
|
24天前
|
弹性计算 运维 数据挖掘
2026年阿里云便宜云服务器汇总:38元、68元、99元和199元配置整理及购买条件说明
2026年阿里云最便宜四款云服务器:38元/年(轻量秒杀,限新用户)、68元/年(轻量常规款)、99元/年(ECS经济型e,新老可用)、199元/年(ECS通用型u1,仅企业)。配置从2核2G到2核4G,带宽含峰值与固定选项,均支持同价续费。
|
24天前
|
存储 弹性计算 Serverless
阿里云免费中心:上百款云产品免费用,包括云服务器、云数据库、Tokens、免费存储等云产品,0元直达
阿里云免费中心提供超176款云产品新用户免费试用,涵盖ECS、OSS、RDS、FC、无影云电脑等,含300–660元额度、500GB云盘、120小时云电脑等福利;学生还可享12个月ECS。注意:仅限未购过该产品的账号,停机仍计费,到期未释放将产生费用。(239字)
304 1
|
17天前
|
人工智能 自然语言处理 数据挖掘
阿里云千问办公企业积分包:多少钱、怎么买、怎么用?
千问办公是阿里推出的AI办公平台,支持自然语言驱动的数据分析、PPT生成、视频剪辑、网页搭建等。企业版席位每月附赠2000积分,额度不足时可增购积分包(100元起/2000分,有效期12个月),需配合有效席位使用。(239字)
|
20天前
|
人工智能 PyTorch 云栖大会
亮点抢先看!AMD、中兴通讯等企业大咖解码下一代 OS|2026 云栖大会
一览来自 AMD、中兴通讯、阿里云、英特尔,以及上海交通大学等企业/高校专家的精彩金句和重磅议题。
|
1月前
|
人工智能 IDE API
阿里云Qoder CN服务平台完整功能解读:从本地IDE配置、终端CLI使用、云端部署到开放API全链路解读
软件研发工作包含需求拆解、代码编写、单元测试、故障排查、文档撰写、项目重构等大量重复性工作。传统开发模式下,开发者需要手动翻阅项目源码、查阅接口文档、调试报错堆栈,大型项目代码体量庞大,新人熟悉项目架构要耗费大量时间。普通代码补全工具只能完成单行片段生成,无法理解完整工程上下文,不能独立处理跨多文件的复杂开发任务。Qoder CN,前身是通义灵码,是面向软件研发全流程的AI智能体产品套件,不再局限于简单的代码片段补全,而是以编程Agent智能体为核心,覆盖本地桌面开发、终端命令行、云端托管运行、数字员工等多种形态,深度对接百炼平台Coding Plan、Token Plan订阅体系,支持Qwe
415 1
|
2月前
|
人工智能 文字识别 API
百炼 + RPA 实战:从零搭建可操作系统界面的 AI 智能体——内网离线部署与 EXE 打包分发完整方案
本文基于阿里云百炼平台与本地桌面自动化引擎,构建“大模型决策→API触发→界面操作→结果回调”闭环。提供可复用的提示词模板、Function Calling配置、MCP对接代码及生产踩坑实录,实现AI从“会说”到“能做”的跃迁。
|
2月前
|
数据采集 人工智能 算法
为什么你的品牌在AI里查无此人?GEO优化的五个关键动作
本文为技术实践分享,介绍AI搜索时代企业亟需的GEO(生成式引擎优化)——不同于SEO,GEO聚焦让AI“认识、信任并推荐”品牌。文章提炼罗小军提出的五大关键动作:诊断AI可见度、结构化知识资产、建设权威信源、布局场景词矩阵、建立持续监测机制,助力企业抢占AI决策入口。(239字)

热门文章

最新文章