签名算法

概述

环能聚合API使用HMAC-SHA256签名算法确保请求数据的完整性和安全性。每个请求都需要包含签名参数,服务器会验证签名的有效性。

签名原理

算法: HMAC-SHA256
密钥长度: 32字节 (256位)
输出长度: 64字符 (256位十六进制)
编码方式: 十六进制小写

签名生成步骤

步骤1:准备签名数据

参与签名的参数

参数名 是否必须 说明 示例
app_key 应用唯一标识 "abc123xyz"
timestamp 当前时间戳(毫秒) 1640995200000
nonce 随机字符串(32位) "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
version API版本号 "1.0"
body 请求体JSON字符串 "{\"name\":\"张三\"}"

步骤2:构造签名串

签名串格式

signString = HTTP_METHOD + "\n" +
             CONTENT_TYPE + "\n" +
             TIMESTAMP + "\n" +
             NONCE + "\n" +
             REQUEST_URI + "\n" +
             SORTED_QUERY_STRING + "\n" +
             BODY_HASH

步骤3:计算签名

签名计算公式

signature = HMAC-SHA256(signString, app_secret)

Python示例

import hmac
import hashlib
import urllib.parse

def generate_signature(method, content_type, timestamp, nonce, uri, params, body, app_secret):
    # 排序参数
    sorted_params = sorted(params.items())
    query_string = urllib.parse.urlencode(sorted_params)
    
    # 计算body hash
    body_hash = hashlib.sha256(body.encode('utf-8')).hexdigest()
    
    # 构造签名串
    sign_string = f"{method}\n{content_type}\n{timestamp}\n{nonce}\n{uri}\n{query_string}\n{body_hash}"
    
    # 计算签名
    signature = hmac.new(
        app_secret.encode('utf-8'),
        sign_string.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    
    return signature

Java示例

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;

public class SignGenerator {
    public static String generateSignature(String method, String contentType, 
                                           String timestamp, String nonce, String uri,
                                           Map params, String body, 
                                           String appSecret) throws Exception {
        // 排序参数
        List keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);
        
        StringBuilder queryString = new StringBuilder();
        for (String key : keys) {
            if (queryString.length() > 0) queryString.append("&");
            queryString.append(key).append("=")
                      .append(URLEncoder.encode(params.get(key), "UTF-8"));
        }
        
        // 计算body hash
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        String bodyHash = bytesToHex(md.digest(body.getBytes(StandardCharsets.UTF_8)));
        
        // 构造签名串
        String signString = String.join("\n", method, contentType, timestamp, 
                                       nonce, uri, queryString.toString(), bodyHash);
        
        // 计算签名
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKey = new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        mac.init(secretKey);
        
        return bytesToHex(mac.doFinal(signString.getBytes(StandardCharsets.UTF_8)));
    }
    
    private static String bytesToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02x", b));
        }
        return result.toString();
    }
}

步骤4:发送请求

请求头示例

POST /api/v1/user/info HTTP/1.1
Host: api.huaneng.com
Content-Type: application/json
X-App-Key: abc123xyz
X-Timestamp: 1640995200000
X-Nonce: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
X-Signature: 7d8f9e2a1b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1

{
    "user_id": 12345
}

签名验证规则

时间戳验证

验证项 规则 时间窗口 错误码
时间戳有效性 绝对值偏差 ≤ 5分钟 ±300秒 4001
nonce唯一性 5分钟内不可重复 300秒 4002
签名有效期 生成后5分钟内有效 300秒 4003

错误码说明

错误码 错误描述 原因 解决方案
4001 时间戳过期 时间偏差超过5分钟 校准本地时间
4002 nonce重复 nonce已被使用 生成新的nonce
4003 签名无效 签名计算错误 检查签名计算步骤
4004 app_key无效 应用不存在或已禁用 联系管理员获取有效app_key
4005 签名算法不支持 使用了不支持的算法 使用HMAC-SHA256算法

测试工具

在线签名验证

测试步骤

  1. 准备测试数据(app_key, app_secret, 请求参数)
  2. 使用提供的SDK生成签名
  3. 发送测试请求到验证接口
  4. 验证签名是否正确

调试技巧

调试工具 用途 推荐工具
签名计算器 验证签名算法 Postman、curl
时间戳同步 校准本地时间 ntpdate、timedatectl
日志分析 排查签名问题 ELK、Splunk
单元测试 自动化验证 pytest、JUnit

安全建议

🔐 安全最佳实践

  • 密钥保护:app_secret必须保密,不要存储在前端代码中
  • HTTPS传输:所有API调用必须使用HTTPS协议
  • 定期更新:建议每90天更换一次app_secret
  • 访问控制:限制IP白名单,防止密钥泄露
  • 监控告警:设置异常调用监控和告警机制
  • 日志脱敏:记录日志时避免存储敏感信息

常见问题

Q&A

Q: 为什么我的签名总是验证失败?

A: 常见原因包括:时间戳不同步、参数排序错误、body格式不匹配、编码问题等。建议使用调试模式逐步验证每个步骤。

Q: nonce有什么要求?

A: nonce必须是32位随机字符串,建议使用UUID或随机数生成器。每次请求必须使用不同的nonce。

Q: 如何处理GET请求的签名?

A: GET请求没有body,body_hash计算为空字符串""的SHA256值。