我的目标是使用Swift与(认证的)Kraken API进行交互. 在Kraken API网站上有一个使用Python的例子,我试图在Swift中复制它.然而,我得到的最终加密签名与Python中的不同.我做错了什么?任何帮助将不胜感激. 我使用的是Swift CryptoKit库.

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\xfc\xa7[\x869X\xdb\x81`;\x16\xe9\xc0\x98c\xbc\x96\xc4\x94\\\xdb.\xdd\xea0\xef\xab3\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
}

推荐答案

In your current approach, the components of the message are concatenated as strings. For this, the SHA256 hash is decoded into a string using Latin1. Therefore, for consistency reasons, a Latin1 encoding must also be used when generating the HMAC in order to reconstruct the original data.
However, the current solution uses a UTF-8 encoding, which irreversibly corrupts the data. If the UTF-8 encoding is changed to a Latin1 encoding, the Swift code returns the result of the Python code:

import Foundation
import Crypto

let privateKey = "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg=="
let payload = "1616492376594nonce=1616492376594&ordertype=limit&pair=XBTUSD&price=37500&type=buy&volume=1.25"
let apiPath = "/0/private/AddOrder"
let payload2 = apiPath

var message = payload2    
let sha256Digest: SHA256Digest = SHA256.hash(data: Data(payload.utf8))
for element in sha256Digest {
    message.append(Character(UnicodeScalar(Int(element.description)!)!))
}
let key = SymmetricKey(data: Data(base64Encoded: privateKey)!)
var hmacSHA512 = HMAC<SHA512>.authenticationCode(for: message.data(using: .isoLatin1)!, using: key) // fix: replace utf-8 with latin1
let finalSignature = Data(hmacSHA512).base64EncodedString()

print(finalSignature) // 4/dpxb3iT4tp/ZCVEwSnEsLxx0bqyhLpdfOpc6fn7OR8+UClSV5n9E6aSS8MPtnRfp32bAb0nmbRn6H8ndwLUQ==

另一种方法是放弃仅为级联而执行的解码和编码,而改用二进制数据的直接级联,例如:

import Foundation
import Crypto

let privateKey = "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg=="
let payload = "1616492376594nonce=1616492376594&ordertype=limit&pair=XBTUSD&price=37500&type=buy&volume=1.25"
let apiPath = "/0/private/AddOrder"
let payload2 = apiPath

var message: [UInt8] = Array(payload2.utf8)
let sha256Digest: SHA256Digest = SHA256.hash(data: Data(payload.utf8))
sha256Digest.withUnsafeBytes {message.append(contentsOf: $0)}
let key = SymmetricKey(data: Data(base64Encoded: privateKey)!)
var hmacSHA512 = HMAC<SHA512>.authenticationCode(for: message, using: key)
let finalSignature = Data(hmacSHA512).base64EncodedString()

print(finalSignature) // 4/dpxb3iT4tp/ZCVEwSnEsLxx0bqyhLpdfOpc6fn7OR8+UClSV5n9E6aSS8MPtnRfp32bAb0nmbRn6H8ndwLUQ==

它当然也会返回Python代码的结果.

Swift相关问答推荐

是否有一个Kotlin等价的Swift s @ AddendableReport注释'

如何在向照片添加文本时定义文本对齐、文本背景的Alpha和在文本之前添加图像

ARRaycast Query和前置摄像头(ARFaceTrackingConfiguration)

RxSwift .debounce,.throttle用于分页

无法创建MKAssociateRegion对象

相互取消任务

为什么这段Swift读写锁代码会导致死锁?

自定义碰撞形状出现在错误的位置

ScreenCaptureKit/CVPixelBuffer 格式产生意外结果

如何延迟 swift 属性 didSet 使其每秒只触发一次

如何在类中打印函数

SwiftUI Grid 中的数组旋转

Swift UI 不重绘 NSViewRepresentable 包装的文本字段

如何删除标签和步进按钮之间的间距是 SwiftUI Stepper?

EXC_BREAKPOINT 崩溃的原因范围

如何从 Button 获取标签名称?

Swift 3.0 的 stringByReplacingOccurencesOfString()

快速覆盖属性

自定义 Google 登录按钮 - iOS

iOS:如何检测用户是否订阅了自动更新订阅