不用 SDK:Node.js 手写华为云 API 的 SDK-HMAC-SHA256 签名

举报
yd_223504780 发表于 2026/10/26 19:32:27 2026/10/26
【摘要】 官方 SDK 未覆盖 devstation 开放接口时,用 Node.js 内置 crypto 手写 AK/SK 签名:规范请求、待签字符串、HMAC 三步实现,以及 URI 末尾斜杠、body 一致性、时间格式、临时凭证、头部排序五个坑和排错方法。

背景

调试开发者空间云开发环境时,我需要直接调用 devstation 的开放接口查询和关闭实例(例如 GET /open-api-public/v1/devenvs/current、POST /open-api-public/v1/devenvs/{id}/close)。官方 SDK 没有覆盖这几个接口,只能用 AK/SK 自己签名。本文记录用 Node.js 内置 crypto、不依赖任何第三方库手写华为云 SDK-HMAC-SHA256 签名的过程,以及几个容易出错的细节。

签名流程概览

华为云 API 网关的 AK/SK 认证分四步:

  1. 构造规范请求(CanonicalRequest):方法、规范 URI、规范查询串、规范头、参与签名的头列表、请求体 SHA256,用换行拼接。
  2. 构造待签字符串(StringToSign):SDK-HMAC-SHA256、请求时间、规范请求的 SHA256 十六进制,用换行拼接。
  3. 用 SK 对待签字符串做 HMAC-SHA256,得到十六进制签名。
  4. 把 AK、SignedHeaders、Signature 放进 Authorization 头:SDK-HMAC-SHA256 Access=AK, SignedHeaders=host;x-sdk-date, Signature=...。

最小实现

import crypto from "node:crypto";
const sha = (d) => crypto.createHash("sha256").update(d).digest("hex");
const hmac = (k, d) => crypto.createHmac("sha256", k).update(d).digest("hex");

function sign({ ak, sk, method, url, body = "" }) {
  const u = new URL(url);
  const ts = new Date().toISOString().replace(/[-:]/g, "").replace(/\.\d+/, "");
  // 规范 URI:逐段编码,并且以 / 结尾
  const uri = "/" + u.pathname.split("/").filter(Boolean)
    .map(encodeURIComponent).join("/") + "/";
  // 规范查询串:按 key 排序
  const query = [...u.searchParams].sort(([a], [b]) => a < b ? -1 : 1)
    .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
  const headers = `host:${u.host}\nx-sdk-date:${ts}\n`;
  const signed = "host;x-sdk-date";
  const canonical = [method, uri, query, headers, signed, sha(body)].join("\n");
  const toSign = `SDK-HMAC-SHA256\n${ts}\n${sha(canonical)}`;
  return {
    "X-Sdk-Date": ts,
    Authorization: `SDK-HMAC-SHA256 Access=${ak}, SignedHeaders=${signed}, Signature=${hmac(sk, toSign)}`,
    "Content-Type": "application/json",
  };
}

调用时把签名用的 body 字符串原样作为 fetch 的 body 发送:

const body = JSON.stringify({ source: "CLI" });
const r = await fetch(url, { method: "POST", headers: sign({ ak, sk, method: "POST", url, body }), body });

踩过的坑

1. 规范 URI 末尾的斜杠。华为云的规范 URI 要求以 / 结尾,即使真实请求路径没有斜杠。这一点和 AWS SigV4 不同,照搬 AWS 的实现会一直报签名不匹配。注意只是签名里加斜杠,实际请求的 URL 不要改。

2. 签名的 body 和发送的 body 必须是同一个字符串。我一开始对对象调用了两次 JSON.stringify,中间有人往对象里加了字段,导致哈希不一致。稳妥做法是先序列化成字符串,签名和发送都用这一个变量。GET 请求的 body 哈希是空字符串的 SHA256(e3b0c442...),不能省略这一行。

3. 时间格式和时钟偏差。X-Sdk-Date 是 UTC 的 YYYYMMDDTHHMMSSZ,不能带毫秒和冒号。本机时间偏差过大时网关会拒绝请求,报错和签名错误很像,排查时先对一下系统时间。

4. 临时凭证要多签一个头。使用 STS 临时 AK/SK 时,需要额外带 X-Security-Token,并且把 x-security-token 同时写进规范头和 SignedHeaders,否则返回 401。

5. 规范头必须小写并排序。SignedHeaders 列表与规范头部分都用小写名,按字典序排列,每行以换行结尾;最后一行后面也有换行,拼接时再加一个分隔换行,最容易少写一个。

排错建议

  • 把 canonical 和 toSign 打印出来,和官方 SDK(例如 Python 版 apig-sdk)对同一请求生成的结果逐行比对,差异一般一眼就能看出。
  • 先用只读的 GET 接口验证签名,确认通过后再调用会改变资源状态的 POST 接口。
  • AK/SK 只放在本地配置文件或环境变量里,脚本和日志都不要打印 SK;用完的临时密钥及时在 IAM 删除,删除后复查一次接口返回 401,确认已经失效。

小结

SDK-HMAC-SHA256 本质上是“规范化请求 + 两次哈希 + 一次 HMAC”,几十行代码就能实现。真正耗时的是规范化细节:URI 末尾斜杠、body 一致性、时间格式和头部排序。把中间串打印出来逐行比对,是最快的排错方式。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。