Kraken API返回EAPI: Invalid Key/Signature错误排查求助
Kraken API 认证错误排查与修复
问题根源
你的代码存在两处关键错误,导致返回EAPI: Invalid Key或EAPI: Invalid Signature:
1. HTTP请求头键名格式错误
请求头的键名不应包含冒号和空格,你使用的"API-Key: "、"API-Sign: "不符合HTTP规范,服务器无法正确识别这些字段,因此返回密钥无效错误。正确的键名应为"API-Key"和"API-Sign"。
2. 签名计算逻辑不符合Kraken规范
Kraken签名生成的正确流程是:
- 对
nonce + postData的原始字节计算SHA256哈希(保留二进制结果,而非转为十六进制字符串) - 将API路径(如
/0/private/Balance)与上述SHA256二进制结果拼接 - 用Base64解码后的API密钥作为密钥,对拼接内容计算HMAC-SHA512
- 最后将HMAC结果进行Base64编码得到签名
你的代码错误地将SHA256结果转为十六进制字符串后再参与HMAC计算,导致签名不匹配。
修复后的代码
Trade.h(无需修改)
#pragma warning(disable : 4996) #include <iostream> #include <iomanip> #include <string> #include <cpr/cpr.h> #include <nlohmann/json.hpp> #include <chrono> #include <openssl/sha.h> #include <openssl/hmac.h> #include <openssl/evp.h> #include <openssl/bio.h> #include <openssl/buffer.h> using namespace std; using namespace cpr; using namespace chrono; using json = nlohmann::json; class Trade { private: string key; string secret; string timestamp; string signature; string baseUrl; string postData; public: Trade(); void Authenticate(); void getAccounts(); };
Trade.cpp(修改签名计算和请求头部分)
#include "Trade.h" Trade::Trade() { key = "xxxxxxxxxxxxxxxx"; secret = "xxxxxxxxxxxxxxxxxxx"; postData = ""; timestamp = ""; signature = ""; baseUrl = "https://api.kraken.com/0/private/Balance"; } static std::string b64_decode(const std::string& data) { BIO * b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); BIO* bio = BIO_new_mem_buf(data.c_str(), data.size()); bio = BIO_push(b64, bio); char buffer[512]; int len; std::string decoded; while ((len = BIO_read(bio, buffer, sizeof(buffer))) > 0) { decoded.append(buffer, len); } BIO_free_all(bio); return decoded; } static std::string b64_encode(const std::string& data) { BIO* b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); BIO* mem = BIO_new(BIO_s_mem()); b64 = BIO_push(b64, mem); BIO_write(b64, data.c_str(), data.length()); BIO_flush(b64); BUF_MEM* mem_buf = NULL; BIO_get_mem_ptr(b64, &mem_buf); std::string output(mem_buf->data, mem_buf->length()); BIO_free_all(b64); return output; } // 修改SHA256函数,返回二进制哈希结果 static string sha256_bin(const string& str) { EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); const EVP_MD* md = EVP_sha256(); unsigned char hash[EVP_MAX_MD_SIZE]; unsigned int hash_len; EVP_DigestInit_ex(mdctx, md, NULL); EVP_DigestUpdate(mdctx, str.c_str(), str.size()); EVP_DigestFinal_ex(mdctx, hash, &hash_len); EVP_MD_CTX_free(mdctx); // 返回二进制字符串,而非十六进制格式 return string(reinterpret_cast<char*>(hash), hash_len); } static std::string hmac_sha512(const std::string& data, const std::string& key) { std::string result; HMAC_CTX* hmac_ctx = HMAC_CTX_new(); HMAC_Init_ex(hmac_ctx, key.c_str(), key.length(), EVP_sha512(), NULL); HMAC_Update(hmac_ctx, (const unsigned char*)data.c_str(), data.length()); unsigned char hmac_result[EVP_MAX_MD_SIZE]; unsigned int hmac_len; HMAC_Final(hmac_ctx, hmac_result, &hmac_len); result.assign((const char*)hmac_result, hmac_len); HMAC_CTX_free(hmac_ctx); return result; } void Trade::Authenticate() { const auto p1 = system_clock::now(); // 改用毫秒级时间戳作为nonce,降低重复风险 unsigned long long intTimestamp = duration_cast<milliseconds>(p1.time_since_epoch()).count(); timestamp = to_string(intTimestamp); string path = "/0/private/Balance"; postData = "nonce=" + timestamp; // 计算nonce+postData的二进制SHA256哈希 string nonce_post = timestamp + postData; string sha256_result = sha256_bin(nonce_post); // 拼接API路径与SHA256二进制结果 string hmac_input = path + sha256_result; // 用Base64解码后的密钥计算HMAC,再编码为Base64得到签名 signature = b64_encode(hmac_sha512(hmac_input, b64_decode(secret))); } void Trade::getAccounts() { Response r; Header header; // 修正请求头键名,去掉多余的冒号和空格 header.insert({ "API-Key", key }); header.insert({ "API-Sign", signature }); header.insert({ "User-Agent", "Kraken C++ API Client" }); header.insert({ "Content-Type", "application/x-www-form-urlencoded" }); r = Post(Url{ baseUrl }, Body{postData}, Header{ header }); cout << "Status Code: " << r.status_code << endl; cout << r.text << endl; }
main.cpp(无需修改)
#include<iostream> #include "Trade.h" using namespace std; int main() { Trade trade; trade.Authenticate(); trade.getAccounts(); }
额外注意事项
- 确保API密钥拥有
Balance接口的访问权限 - Nonce必须严格递增,使用毫秒级时间戳可有效避免重复
- 每次请求必须重新计算签名,不能复用之前的签名值
内容的提问来源于stack exchange,提问作者John Doe
相关产品推荐
相关产品推荐

