如何在Swift中生成与Python一致的Kraken API HMAC-SHA512签名?
Kraken API HMAC-SHA512签名Swift与Python实现不一致问题
我尝试用Swift调用需要认证的Kraken API,参考官网的Python示例复刻代码,但生成的HMAC-SHA512签名和Python版本不一致。以下是相关代码:
Python示例代码(来自Kraken API文档)
import urllib.parse import hashlib import hmac import base64 def get_kraken_signature(urlpath, data, secret): postdata = urllib.parse.urlencode(data) encoded = (str(data['nonce']) + postdata).encode() message = urlpath.encode() + hashlib.sha256(encoded).digest() mac = hmac.new(base64.b64decode(secret), message, hashlib.sha512) sigdigest = base64.b64encode(mac.digest()) return sigdigest.decode() api_sec = "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg==" data = { "nonce": "1616492376594", "ordertype": "limit", "pair": "XBTUSD", "price": 37500, "type": "buy", "volume": 1.25 } signature = get_kraken_signature("/0/private/AddOrder", data, api_sec) print("API-Sign: {}".format(signature)) #Output: 4/dpxb3iT4tp/ZCVEwSnEsLxx0bqyhLpdfOpc6fn7OR8+UClSV5n9E6aSS8MPtnRfp32bAb0nmbRn6H8ndwLUQ== #This output is the expected output, as Kraken states on their website
我的Swift代码
import CryptoKit func encryptMsg() -> String { let privateKey = "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg==" let payload = "1616492376594nonce=1616492376594&ordertype=limit&pair=XBTUSD&price=37500&type=buy&volume=1.25" let apiPath = "/0/private/AddOrder" var payload2 = apiPath // Sha256Digest let sha256Digest: SHA256Digest = SHA256.hash(data: Data(payload.utf8)) debugPrint(sha256Digest) // Output: 23a1c1b34c6a11d641af0f24684896cb90f66fb991125c83dc357bdc3dc146f1 // Convert sha256Digest from hex to ascii for element in sha256Digest { payload2.append(Character(UnicodeScalar(Int(element.description)!)!)) } debugPrint(payload2) // Swift : /0/private/AddOrder#¡Á³Lj\u{11}ÖA¯\u{0F}$hHËöo¹\u{12}\\Ü5{Ü=ÁFñ // Python: /0/private/AddOrder#\xa1\xc1\xb3Lj\x11\xd6A\xaf\x0f$hH\x96\xcb\x90\xf6o\xb9\x91\x12\\\x83\xdc5{\xdc=\xc1F\xf1 // Swift output identical to Python output // Private key debugPrint(Data(base64Encoded: privateKey)!.map { String(format: "%02x", $0) }.joined()) // Swift : 9101f91d6ffca75b863958db81603b16e9c09863bc96c4945cdb2eddea30efab33f38435f1f5b19f247304709dde97799c4f6a6bdf47019b6e66e8fa17586e5e // Python: \x91\x01\xf9\x1do\xfca7[\x869X\xdb\x81`;\x16\xe9\xc0\x98c\xbc\x96\xc4\x94\\\xdb.\xdd\xea0\xef\ab3\xf3\x845\xf1\xf5\xb1\x9f$s\x04p\x9d\xde\x97y\x9cOjk\xdfG\x01\x9bnf\xe8\xfa\x17Xn^ // Swift output identical to Python output let key = SymmetricKey(data: Data(base64Encoded: privateKey)!) // SHA512 digest and Hmac construction var hmacSHA512 = HMAC<SHA512>.authenticationCode(for: Data(payload2.utf8), using: key) debugPrint(hmacSHA512) // Swift : e82d03bb76af28c9bb55dfc460bf8a0f64d30d0e55307bc5236f5d0581bf8864bce237238d4b7cc3c832a7e2545b1d0f70794545ce76d1bd656c13b054ea54da // Python: \xe3\xf7i\xc5\xbd\xe2O\x8bi\xfd\x90\x95\x13\x04\xa7\x12\xc2\xf1\xc7F\xea\xca\x12\xe9u\xf3\xa9s\xa7\xe7\xec\xe4|\xf9@\xa5I^g\xf4N\x9aI/\x0c>\xd9\xd1~\x9d\xf6l\x06\xf4\x9ef\xd1\x9f\xa1\xfc\x9d\xdc\x0bQ // Swift output different from Python output let finalSignature = Data(hmacSHA512).base64EncodedString() debugPrint(finalSignature) // Swift : 6C0Du3avKMm7Vd/EYL+KD2TTDQ5VMHvFI29dBYG/iGS84jcjjUt8w8gyp+JUWx0PcHlFRc520b1lbBOwVOpU2g== // Python: 4/dpxb3iT4tp/ZCVEwSnEsLxx0bqyhLpdfOpc6fn7OR8+UClSV5n9E6aSS8MPtnRfp32bAb0nmbRn6H8ndwLUQ== // Final result: Swift output different from Python output return finalSignature }
请问我哪里出错了?
问题分析与修正
你的错误出在构造HMAC输入消息的方式上:Python代码是直接将urlpath的字节数据与SHA256的原始字节摘要拼接,而你的Swift代码却把SHA256摘要转成了Unicode字符再拼接到字符串里,最后又转成UTF-8字节——这个过程中,部分不可打印的ASCII字符会被错误处理,导致最终的消息字节流和Python不一致。
正确的Swift实现
import CryptoKit func getKrakenSignature(urlPath: String, data: [String: Any], secret: String) -> String? { // 1. 编码post数据,和Python的urlencode逻辑对齐 guard let postData = data.urlEncodedString().data(using: .utf8), let nonce = data["nonce"] as? String, let secretData = Data(base64Encoded: secret), let urlPathData = urlPath.data(using: .utf8) else { return nil } // 2. 构造encoded:nonce + postData的字节拼接 let encoded = nonce.data(using: .utf8)! + postData // 3. 计算SHA256摘要 let sha256Digest = SHA256.hash(data: encoded) // 4. 构造HMAC的输入消息:urlPath字节 + SHA256原始字节摘要 let message = urlPathData + Data(sha256Digest) // 5. 计算HMAC-SHA512 let key = SymmetricKey(data: secretData) let hmacDigest = HMAC<SHA512>.authenticationCode(for: message, using: key) // 6. 转成Base64字符串 return Data(hmacDigest).base64EncodedString() } // 辅助方法:模拟Python的urlencode逻辑,处理字典转URL编码字符串 extension Dictionary where Key == String, Value == Any { func urlEncodedString() -> String { return self.compactMap { key, value in guard let encodedKey = key.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let encodedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return nil } return "\(encodedKey)=\(encodedValue)" }.joined(separator: "&") } } // 测试调用 let apiSec = "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg==" let data: [String: Any] = [ "nonce": "1616492376594", "ordertype": "limit", "pair": "XBTUSD", "price": 37500, "type": "buy", "volume": 1.25 ] if let signature = getKrakenSignature(urlPath: "/0/private/AddOrder", data: data, secret: apiSec) { print("API-Sign: \(signature)") // 输出会和Python一致:4/dpxb3iT4tp/ZCVEwSnEsLxx0bqyhLpdfOpc6fn7OR8+UClSV5n9E6aSS8MPtnRfp32bAb0nmbRn6H8ndwLUQ== }
关键修正点
- 直接操作字节数据:不再把SHA256摘要转成字符串,而是直接用
Data(sha256Digest)获取原始字节,和urlPath的字节拼接成HMAC的输入消息,和Python逻辑完全对齐。 - 模拟Python的urlencode:新增了字典转URL编码字符串的辅助方法,确保post数据的编码结果和Python一致(比如处理数字、字符串的转义)。
- 避免字符串转义问题:全程用
Data处理字节流,杜绝Unicode字符转义带来的字节不一致问题。
内容的提问来源于stack exchange,提问作者Marcel Winters
相关产品推荐
相关产品推荐

