Marcel Winters
Marcel Winters

Reputation: 21

How to get the same HMAC-SHA512 signature in Swift as in Python?

My goal is to interact with the (authenticated) Kraken API using Swift. On the Kraken API website is an example using Python and I’m trying to replicate that in Swift. However, the final encrypted signature that I’m getting is different from the one in Python. What am I doing wrong? Any help would be much appreciated. I'm using the Swift CryptoKit library by the way.

Python code (copied from the Kraken API documentation):

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

My Swift code:

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
}

Upvotes: 1

Views: 173

Answers (2)

Topaco
Topaco

Reputation: 49121

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==

An alternative to your approach is to forego the decoding and encoding carried out solely for concatenation in favor of a direct concatenation of the binary data, e.g.:

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==

which of course also returns the result of the Python code.

Upvotes: 1

Can
Can

Reputation: 88

To achieve the same HMAC-SHA512 signature in Swift as in Python, you need to ensure that both languages follow the same steps and use the same inputs.

import CryptoKit

func getKrakenSignature() -> String {
    let privateKey = "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg=="
    let payload = "nonce=1616492376594&ordertype=limit&pair=XBTUSD&price=37500&type=buy&volume=1.25"
    let apiPath = "/0/private/AddOrder"
    let nonce = "1616492376594"
    
    // Constructing the message
    let message = (apiPath + SHA256.hash(data: Data(payload.utf8)).compactMap { String(format: "%02x", $0) }.joined()).data(using: .utf8)!
    
    // Creating HMAC-SHA512 signature
    let key = SymmetricKey(data: Data(base64Encoded: privateKey)!)
    let hmacSHA512 = HMAC<SHA512>.authenticationCode(for: message, using: key)
    
    // Converting HMAC to base64-encoded string
    let finalSignature = Data(hmacSHA512).base64EncodedString()
    
    return finalSignature
}

print("API-Sign: \(getKrakenSignature())")

The message used for HMAC should be constructed identically in both Python and Swift. Ensure that the message contains the URL path, followed by the SHA256 digest of the encoded payload.

Upvotes: -2

Related Questions