Skip to content

身份认证 ​

HashNut 使用 HMAC-SHA256 签名来验证 API 请求。部分接口使用请求头签名,另一些则在请求体中使用 accessSign 字段。

HMAC-SHA256 签名流程 ​

签名过程包含四个步骤:

第一步:生成请求元数据 ​

为每个请求生成一个唯一字符串和一个 Unix 时间戳(毫秒)。

第二步:构建签名字符串 ​

将三个部分无分隔符地拼接在一起:

signString = uuid + timestamp + requestBody

其中 requestBody 是请求体的 JSON 字符串。

第三步:计算签名 ​

使用您的 Secret Key 计算 HMAC-SHA256,然后对结果进行 Base64 编码:

signature = base64( hmac_sha256( secretKey, signString ) )

第四步:设置请求头 ​

在 HTTP 请求中包含以下请求头:

请求头说明
hashnut-request-uuid第一步中生成的唯一字符串,每次请求必须不同(重复会被判定为重放并拒绝)
hashnut-request-timestamp第一步中生成的 Unix 毫秒时间戳,与服务端时间偏差不能超过 ±5 分钟
hashnut-request-signBase64 编码的 HMAC-SHA256 签名
Content-Type必须为 application/json

代码示例 ​

java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.UUID;

public class HashNutSign {
    public static String[] sign(String secretKey, String body) throws Exception {
        String reqUUID = UUID.randomUUID().toString();
        String timestamp = String.valueOf(System.currentTimeMillis()); // 毫秒

        String signString = reqUUID + timestamp + body;

        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secretKey.getBytes(), "HmacSHA256"));
        String signature = Base64.getEncoder().encodeToString(
                mac.doFinal(signString.getBytes()));

        return new String[]{reqUUID, timestamp, signature};
    }
}
go
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
	"time"

	"github.com/google/uuid"
)

func sign(secretKey, body string) (reqUUID, timestamp, signature string) {
	reqUUID = uuid.New().String()
	timestamp = fmt.Sprintf("%d", time.Now().UnixMilli()) // 毫秒

	signString := reqUUID + timestamp + body

	mac := hmac.New(sha256.New, []byte(secretKey))
	mac.Write([]byte(signString))
	signature = base64.StdEncoding.EncodeToString(mac.Sum(nil))

	return reqUUID, timestamp, signature
}
js
const crypto = require('crypto');
const { v4: uuidv4 } = require('uuid');

function sign(secretKey, body) {
  const reqUUID = uuidv4();
  const timestamp = Date.now().toString(); // 毫秒

  const signString = reqUUID + timestamp + body;

  const signature = crypto
    .createHmac('sha256', secretKey)
    .update(signString)
    .digest('base64');

  return { reqUUID, timestamp, signature };
}
python
import hmac
import hashlib
import base64
import uuid
import time

def sign(secret_key: str, body: str):
    req_uuid = str(uuid.uuid4())
    timestamp = str(int(time.time() * 1000))  # 毫秒

    sign_string = req_uuid + timestamp + body

    signature = base64.b64encode(
        hmac.new(
            secret_key.encode(),
            sign_string.encode(),
            hashlib.sha256
        ).digest()
    ).decode()

    return req_uuid, timestamp, signature

Access Sign(请求体签名) ​

查询订单和确认支付不走请求头签名, 而是把 accessSign 放在请求体里。它的计算方式和上面的请求头签名完全不同:

accessSign = HMACSHA256(secretKey, 把 payOrderId 和 merchantOrderId 按不区分大小写升序排序后直接拼接)

结果是大写十六进制字符串(64 字符),不是 base64。

IMPORTANT

你不需要自己算它——创建订单的响应里就返回了 data.accessSign。 把它连同 payOrderId 一起落库,后续查询和确认原样回传即可。 因为它只跟这两个 ID 有关、与请求体其余内容无关,所以同一笔订单的 accessSign 是固定值。

接口认证方式 ​

接口认证方式说明
POST /v4.0.0/api/orders/create请求头签名创建订单
POST /v4.0.0/api/orders/cancel请求头签名取消订单
POST /v4.0.0/api/orders/query请求体 accessSign查询订单
POST /v4.0.0/pay/orders/confirm请求体 accessSign确认支付
POST /v4.0.0/api/orders/supplements无需认证查询补款记录,只校验 payOrderId 格式
POST /v4.0.0/api/orders/supplements/latest无需认证同上,只返回最近一条
POST /v4.0.0/config/*无需认证公共配置接口

WARNING

补款记录查询接口不做任何鉴权,拿到合法 payOrderId 就能读到数据。 不要把 payOrderId 暴露在你自己的公开页面或前端可见的位置。

TIP

用 Go SDK 或 Java SDK 时签名是自动处理的: 客户端只需要 secretKey,accessKeyId 是创建订单时逐笔传的参数。