# HMAC 签名认证

# 认证方式

包车对外标准接口采用 HMAC-SHA256 签名认证方式,保证请求来源可信,防止重放攻击。

# 认证凭证

接入前需要向嘟嘟巴士申请以下凭证:

  • partner_id:合作伙伴标识(如 hellobike
  • access_key:访问密钥标识
  • secret_key:签名密钥(请妥善保管,不要泄露)

# 签名流程

# 1. 构造请求头

每个请求必须包含以下 HTTP 请求头:

Content-Type: application/json
Authorization: HMAC-SHA256 Credential={access_key}, SignedHeaders={signed_headers}, Signature={signature}
X-Dudu-Partner-Id: {partner_id}
X-Dudu-Timestamp: {timestamp}
X-Dudu-Nonce: {nonce}
X-Dudu-Content-SHA256: {content_sha256}

字段说明:

字段 说明 示例
Authorization 签名认证信息 HMAC-SHA256 Credential=abc123, SignedHeaders=x-dudu-content-sha256;x-dudu-nonce;x-dudu-partner-id;x-dudu-timestamp, Signature=xxx
X-Dudu-Partner-Id 合作伙伴标识 hellobike
X-Dudu-Timestamp 请求时间戳(ISO8601 格式) 2026-05-20T09:00:00Z
X-Dudu-Nonce 32位随机字符串(防重放) a1b2c3d4e5f6...
X-Dudu-Content-SHA256 请求体的 SHA256 哈希值 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

# 2. 计算 Content-SHA256

const crypto = require('crypto');
const body = JSON.stringify(requestBody);
const contentSha256 = crypto.createHash('sha256').update(body).digest('hex');

# 3. 构造规范化请求(Canonical Request)

POST
{uri_path}
{query_string}
{canonical_headers}
{signed_headers}
{content_sha256}

示例:

POST
/api/charter/partner_create_order

x-dudu-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-dudu-nonce:a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
x-dudu-partner-id:hellobike
x-dudu-timestamp:2026-05-20T09:00:00Z

x-dudu-content-sha256;x-dudu-nonce;x-dudu-partner-id;x-dudu-timestamp
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

注意事项:

  • 每行以 \n 换行
  • canonical_headers 按字母序排列,格式为 header-name:header-value\n
  • signed_headers 是参与签名的 header 名称列表,用 ; 分隔,按字母序排列
  • query_string 为空时留空行

# 4. 构造待签字符串(String to Sign)

HMAC-SHA256
{timestamp}
{sha256(canonical_request)}

示例:

HMAC-SHA256
2026-05-20T09:00:00Z
7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b

# 5. 计算签名

const crypto = require('crypto');
const signature = crypto
  .createHmac('sha256', secret_key)
  .update(stringToSign)
  .digest('hex');

# 6. 组装 Authorization 头

HMAC-SHA256 Credential={access_key}, SignedHeaders={signed_headers}, Signature={signature}

# 完整示例(Node.js)

const crypto = require('crypto');
const axios = require('axios');

// 认证凭证
const partner_id = 'hellobike';
const access_key = 'your_access_key';
const secret_key = 'your_secret_key';

// 请求参数
const url = 'http://apitest.dudubashi.com/api/charter/partner_car_type_list';
const body = {
  city_name: '深圳市'
};

// 1. 生成时间戳和随机数
const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
const nonce = crypto.randomBytes(16).toString('hex');

// 2. 计算 Content-SHA256
const bodyStr = JSON.stringify(body);
const contentSha256 = crypto.createHash('sha256').update(bodyStr).digest('hex');

// 3. 构造规范化请求
const path = new URL(url).pathname;
const canonicalHeaders = [
  `x-dudu-content-sha256:${contentSha256}`,
  `x-dudu-nonce:${nonce}`,
  `x-dudu-partner-id:${partner_id}`,
  `x-dudu-timestamp:${timestamp}`
].join('\n');

const signedHeaders = 'x-dudu-content-sha256;x-dudu-nonce;x-dudu-partner-id;x-dudu-timestamp';

const canonicalRequest = [
  'POST',
  path,
  '',
  canonicalHeaders,
  '',
  signedHeaders,
  contentSha256
].join('\n');

// 4. 构造待签字符串
const canonicalRequestHash = crypto.createHash('sha256').update(canonicalRequest).digest('hex');
const stringToSign = [
  'HMAC-SHA256',
  timestamp,
  canonicalRequestHash
].join('\n');

// 5. 计算签名
const signature = crypto.createHmac('sha256', secret_key).update(stringToSign).digest('hex');

// 6. 发送请求
const authorization = `HMAC-SHA256 Credential=${access_key}, SignedHeaders=${signedHeaders}, Signature=${signature}`;

axios.post(url, body, {
  headers: {
    'Content-Type': 'application/json',
    'Authorization': authorization,
    'X-Dudu-Partner-Id': partner_id,
    'X-Dudu-Timestamp': timestamp,
    'X-Dudu-Nonce': nonce,
    'X-Dudu-Content-SHA256': contentSha256
  }
}).then(response => {
  console.log(response.data);
}).catch(error => {
  console.error(error.response?.data || error.message);
});

# 安全建议

  1. 妥善保管密钥secret_key 不要硬编码在客户端代码中,应存储在服务端环境变量
  2. 时间同步:确保服务器时间准确,签名有效期为 5 分钟
  3. Nonce 唯一性:每次请求使用不同的随机数,防止重放攻击
  4. HTTPS 传输:生产环境建议使用 HTTPS 加密传输

# 常见错误

错误码 错误信息 解决方案
1001 缺少 Authorization 请求头 检查请求头是否完整
1001 签名验证失败 检查签名计算流程,确认 secret_key 正确
1001 请求已过期或时间偏差过大 同步服务器时间,确保时间戳准确
1001 nonce 已使用,疑似重放攻击 每次请求使用新的随机数
1001 请求体哈希不匹配 检查 Content-SHA256 计算是否正确