身份验证
HashNut API v3.0.0 对所有 API 请求使用基于 HMAC-SHA256 签名的身份验证。这确保了安全、防篡改的 API 通信。
所需凭证
在发出 API 请求之前,您需要:
accessKeyId: 您的商户访问密钥 ID (公共标识符)apiKey: 您的 API 密钥 (用于请求签名的私密密钥)responseKey: 您的响应密钥 (用于 webhook 签名验证,可以与apiKey相同)
请求签名生成
所有经过身份验证的 API 请求必须包含以下标头:
| 标头 | 描述 | 示例 |
|---|---|---|
hashnut-request-uuid | 请求的唯一 UUID | 550e8400-e29b-41d4-a716-446655440000 |
hashnut-request-timestamp | 时间戳 (nonce),以毫秒为单位 | 1704067200000 |
hashnut-request-sign | HMAC-SHA256 签名 (Base64 编码) | <base64-encoded-signature> |
Content-Type | 请求内容类型 | application/json |
签名算法
逐步过程
算法步骤:
- 生成 UUID: 创建唯一 UUID (例如,
550e8400-e29b-41d4-a716-446655440000) - 获取时间戳: 当前时间戳,以毫秒为单位 (例如,
1704067200000) - 字符串化请求体: 将请求体转换为 JSON 字符串 (无空格,精确格式)
- 连接: 组合
uuid + timestamp + body(无分隔符) - 计算 HMAC: 使用
apiKey作为密钥进行 HMAC-SHA256 - Base64 编码: 将 HMAC 结果编码为 Base64
- 设置标头: 使用 Base64 字符串作为
hashnut-request-sign标头值
代码示例
- JavaScript/TypeScript
- Python
- Java
import crypto from 'crypto';
function generateSignature(apiKey: string, body: object): {
uuid: string;
timestamp: string;
signature: string;
} {
// Step 1: Generate UUID
const uuid = crypto.randomUUID();
// Step 2: Get timestamp
const timestamp = Date.now().toString();
// Step 3: Stringify body (no whitespace)
const bodyString = JSON.stringify(body);
// Step 4: Concatenate
const signBody = uuid + timestamp + bodyString;
// Step 5 & 6: HMAC-SHA256 + Base64
const signature = crypto
.createHmac('sha256', apiKey)
.update(signBody)
.digest('base64');
return {
uuid,
timestamp,
signature
};
}
// Usage
const { uuid, timestamp, signature } = generateSignature(
'your-api-key',
{
accessKeyId: 'your-access-key-id',
merchantOrderId: 'order-123',
chainCode: 'erc20',
coinCode: 'usdt',
amount: 0.01
}
);
// Set headers
const headers = {
'hashnut-request-uuid': uuid,
'hashnut-request-timestamp': timestamp,
'hashnut-request-sign': signature,
'Content-Type': 'application/json'
};
import hmac
import hashlib
import base64
import json
import uuid
import time
def generate_signature(api_key: str, body: dict) -> dict:
# Step 1: Generate UUID
request_uuid = str(uuid.uuid4())
# Step 2: Get timestamp
timestamp = str(int(time.time() * 1000))
# Step 3: Stringify body (no whitespace)
body_string = json.dumps(body, separators=(',', ':'))
# Step 4: Concatenate
sign_body = request_uuid + timestamp + body_string
# Step 5 & 6: HMAC-SHA256 + Base64
signature = base64.b64encode(
hmac.new(
api_key.encode('utf-8'),
sign_body.encode('utf-8'),
hashlib.sha256
).digest()
).decode('utf-8')
return {
'uuid': request_uuid,
'timestamp': timestamp,
'signature': signature
}
# Usage
result = generate_signature(
'your-api-key',
{
'accessKeyId': 'your-access-key-id',
'merchantOrderId': 'order-123',
'chainCode': 'erc20',
'coinCode': 'usdt',
'amount': 0.01
}
)
# Set headers
headers = {
'hashnut-request-uuid': result['uuid'],
'hashnut-request-timestamp': result['timestamp'],
'hashnut-request-sign': result['signature'],
'Content-Type': 'application/json'
}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.UUID;
import com.fasterxml.jackson.databind.ObjectMapper;
public class HashnutAuth {
private static final String HMAC_SHA256 = "HmacSHA256";
public static AuthHeaders generateSignature(String apiKey, Object body) throws Exception {
// Step 1: Generate UUID
String uuid = UUID.randomUUID().toString();
// Step 2: Get timestamp
String timestamp = String.valueOf(System.currentTimeMillis());
// Step 3: Stringify body (no whitespace)
ObjectMapper mapper = new ObjectMapper();
String bodyString = mapper.writeValueAsString(body);
// Step 4: Concatenate
String signBody = uuid + timestamp + bodyString;
// Step 5 & 6: HMAC-SHA256 + Base64
Mac mac = Mac.getInstance(HMAC_SHA256);
SecretKeySpec secretKey = new SecretKeySpec(
apiKey.getBytes(StandardCharsets.UTF_8),
HMAC_SHA256
);
mac.init(secretKey);
byte[] hash = mac.doFinal(signBody.getBytes(StandardCharsets.UTF_8));
String signature = Base64.getEncoder().encodeToString(hash);
return new AuthHeaders(uuid, timestamp, signature);
}
}
// Usage
AuthHeaders headers = HashnutAuth.generateSignature(
"your-api-key",
new CreateOrderRequest(
"your-access-key-id",
"order-123",
"erc20",
"usdt",
new BigDecimal("0.01")
)
);
重要注意事项
请求体字符串化
⚠️ 关键: 请求体必须完全按照发送时的格式进行字符串化:
- 无额外空格: 使用
JSON.stringify(obj)或等效方法,不进行格式化 - 一致的顺序: 使用相同的键顺序 (或使用排序的键)
- 精确格式: 匹配将在请求中发送的确切 JSON 格式
时间戳要求
- 格式: Unix 纪元以来的毫秒数 (例如,
1704067200000) - 新鲜度: 时间戳应该是最近的 (5 分钟内)
- 唯一性: 与 UUID 结合以防止重放攻击
UUID 要求
- 格式: 标准 UUID v4 格式
- 唯一性: 每个请求必须具有唯一的 UUID
- 生成: 使用加密安全的随机 UUID 生成
完整请求示例
curl -X POST 'https://testnet.hashnut.io/api/v3.0.0/pay/createPayOrderOnSplitWalletWithApiKey' \
-H 'hashnut-request-uuid: 550e8400-e29b-41d4-a716-446655440000' \
-H 'hashnut-request-timestamp: 1704067200000' \
-H 'hashnut-request-sign: <base64-encoded-hmac-sha256>' \
-H 'Content-Type: application/json' \
-d '{
"accessKeyId": "YOUR_ACCESS_KEY_ID",
"merchantOrderId": "order-123",
"chainCode": "erc20",
"coinCode": "usdt",
"amount": 0.01
}'
错误响应
无效签名
{
"code": -2,
"msg": "Invalid signature or credentials",
"data": null
}
常见原因:
- 用于签名的
apiKey不正确 - 请求体字符串化不正确 (空格、键顺序)
- 缺少或错误的标头
- 时间戳格式不正确
缺少标头
{
"code": -2,
"msg": "Missing required headers",
"data": null
}
必需的标头:
hashnut-request-uuidhashnut-request-timestamphashnut-request-signContent-Type: application/json
安全最佳实践
-
安全密钥存储:
- 永远不要将 API 密钥提交到版本控制
- 使用环境变量或密钥管理
- 定期轮换密钥
-
签名验证:
- 始终验证签名是否匹配预期格式
- 使用恒定时间比较进行签名验证
- 记录身份验证失败以进行监控
-
请求安全:
- 对所有 API 请求使用 HTTPS
- 实现请求超时处理
- 验证所有请求参数
-
密钥管理:
- 为测试网和生产环境使用不同的密钥
- 实现密钥轮换策略
- 监控密钥使用以发现异常
测试身份验证
测试签名生成
// Test with known values
const testBody = { accessKeyId: "test", amount: 1.0 };
const { uuid, timestamp, signature } = generateSignature("test-key", testBody);
console.log("UUID:", uuid);
console.log("Timestamp:", timestamp);
console.log("Signature:", signature);
本地验证签名
// Verify your signature generation
function verifySignature(apiKey, uuid, timestamp, body, expectedSignature) {
const signBody = uuid + timestamp + JSON.stringify(body);
const computed = crypto
.createHmac('sha256', apiKey)
.update(signBody)
.digest('base64');
return computed === expectedSignature;
}
下一步
- 了解 API 端点
- 设置 Webhook 身份验证
- 审查 错误代码
准备进行 API 调用? 查看 订单端点 →