Swift: HMAC SHA-512 for coinspot API - swift

I am trying to connect to the coinspot API which has very shoddy documentation. Link below:
https://www.coinspot.com.au/api
It requires an HMAC SHA512 hash attached to the user's secret key, then a nonce key In the parameters of a POST request which is a timestamp turned to an integer to stay unique at each request. The header requires both the API key and the signature.
I use Alamofire's JSON encoded post request through coinspot's private API URL https://www.coinspot.com.au/api
Seems I get a success message for connecting but get an {status = invalid;} response.
The only 2 things I can think of are a wrong POST request (which doesn't seem the case, as if I don't add the nonce key in the parameters, I get a proper response requesting for one from the server), and the HMAC SHA-512 hash. I've taken the code from
HMAC SHA512 using CommonCrypto in Swift 3.1
And modified it slightly to just include the SHA512 hashing.
Code below:
extension String{
var sha512:String{
return HMAC.hash(inp: self, algo: HMACAlgo.SHA512)
}
}
public struct HMAC{
static func hash(inp: String, algo: HMACAlgo)->String{
if let stringData = inp.data(using: String.Encoding.utf8, allowLossyConversion: false){
return hexStringFromData(input: digest(input: stringData as NSData, algo: algo))
}
return ""
}
private static func digest(input: NSData, algo: HMACAlgo)-> NSData{
let digestLength = algo.digestLength()
var hash = [UInt8](repeating: 0, count: digestLength)
CC_SHA512(input.bytes, UInt32(input.length), &hash)
return NSData(bytes: hash, length: digestLength)
}
private static func hexStringFromData(input: NSData)-> String{
var bytes = [UInt8](repeating: 0, count: input.length)
input.getBytes(&bytes, length: input.length)
var hexString = ""
for byte in bytes{
hexString += String(format: "%02x", UInt8(byte))
}
return hexString
}
}
enum HMACAlgo{
case SHA512
func digestLength()->Int{
var result:CInt = 0
switch self{
case .SHA512:
result = CC_SHA512_DIGEST_LENGTH
}
return Int(result)
}
}
The CommonCrypto library has been properly added with an objectiveC bridging file, and printing the result gives me a proper hashed signature.
I've contacted coinspot but they are swarmed with requests and they only have a cookie template response in regards to any technical support for their API.
Here's the Alamofire segment I placed under a function to call any command to the coinspot server:
func request(_ command: Router, params: [String: AnyObject]?, completion: #escaping (Result<JSON>)-> Void){
var parameters = ["nonce": Int(Date().timeIntervalSince1970*1000) as AnyObject]
if params != nil{
parameters = parameters + params!
}
let sign = customerSecret.sha512
print(sign)
let headers:HTTPHeaders = ["key": APIKey, "sign": sign]
do{
try Alamofire.request( command.asURL(), method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseJSON{(response) in
guard let json = response.result.value else {
completion(.failure(CoinSpotError.networkError(error: response.result.error)))
return
}
print(response)
}
} catch {
print("error")
}
}
I think the above is correct. Router is just a set of addresses that call to the proper POST request for retrieving data.
Finally, I've changed the request to GET, got rid of both headers and parameters, and I get a status ok and the resulting JSON. I figured I'll just try a command on a browser and it worked,
The API does say every request must be a post method.

Coinspot API is down, with no ETA: Coinbase API down
I continue to receive {'status': 'invalid'} back from any request I make.
Their publicapi is still up, albeit only for BTC, LTC, and DOGE.
def get_latest_price():
r = requests.get("https://www.coinspot.com.au/pubapi/latest").json()
print(r)

Related

Marvel API returns Invalid Credentials

I'm using Marvel API.
I set up timestamp, apikey and hash(ts+privateKey+apiKey).md5
// ts
let ts = String(Date().timeIntervalSince1970)
// apiKey
"cfd8392fa89be1a574b402a9c54f9c52"
// privateKey
"d3ea745cf73f7a7278e1368ffe3b50f4bdb2746"
I tested using Postman.But A Response is always 401 Invalid Credentials.
/// URL
https://gateway.marvel.com:443/v1/public/characters?ts=1649942740.2140222&apikey=cfd8392fa89be1a574b402a9c54f9c52&hash=d9a73e6105271137942b5ea743ae2ad2
{
"code": "InvalidCredentials",
"message": "That hash, timestamp and key combination is invalid."
}
And I already set up an authorized referee with *(asterisk) at My Developer Account - Marvel
Please help me. I don't know what I missed.
Convert the timestamp, private and public key to md5 hash. Then use that for the query string.
import CryptoKit
let ts = String(Date().timeIntervalSince1970)
let hash = MD5(string:"\(ts)\("PRIVATE_KEY")\("PUBLIC_KEY")")
func MD5(string: String) -> String {
let digest = Insecure.MD5.hash(data: string.data(using: .utf8) ?? Data())
return digest.map {
String(format: "%02hhx", $0)
}.joined()
}

Alamofire, HTTPheaders for post request [string:any]

I need to send a post request using alamofire to my server, one of the header to be sent is not a string value but is an Int
Reading the documentation of Alamofire look like the HTTPHeaders is only type [String: String]
Is there any way to customise the HTTPHeaders to [String:Any]?
I can't find to much understandable for me online.
thanks
Alamofire doesn't have such methods, but you can easily do it
["hey": 1].mapValues { String(describing: $0) } returns [String: String]
If you have many places where you're using it, you can:
Create extension for Dictionary
extension Dictionary where Key == String, Value == Any {
func toHTTPHeaders() -> HTTPHeaders {
HTTPHeaders(mapValues { String(describing: $0) })
}
}
// Usage
AF.request(URL(fileURLWithPath: ""), headers: ["": 1].toHTTPHeaders())
Create extension for HTTPHeaders
extension HTTPHeaders: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, Any)...) {
self.init()
elements.forEach { update(name: $0.0, value: String(describing: $0.1)) }
}
}
// usage
AF.request(URL(fileURLWithPath: ""), headers: HTTPHeaders(["": 1]))
Create extension for Session
extension Session {
open func request(_ convertible: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: [String: Any],
interceptor: RequestInterceptor? = nil,
requestModifier: RequestModifier? = nil) -> DataRequest {
return request(convertible, method: method, parameters: parameters, encoding: encoding, headers: headers.mapValues { String(describing: $0) }, interceptor: interceptor, requestModifier: requestModifier)
}
}
// Usage
AF.request(URL(fileURLWithPath: ""), headers: ["": 1])
The reason there's no such option in Alamofire is type safety. When you use Any you can literary pass any value there and so probability of a mistake is much more. By requiring string library makes sure you're converting all values you need by yourself.
I'd go for the first variant, because it's more clear when you read the code that there's something going on there

Alamofire request with authorization bearer token and additional headers Swift

My API includes authorization bearer token and three additional headers. My problem is I'm not sending the bearer token right (Postman return the correct data not my simulator). I see a lot of examples for using the request adapter but can I not use that? Thanks!
The auth is actually in the authorization tab not in the header.
**Updated:
Solved the problem by following the documentation.
HTTP Headers
Here is the Alamofire function with working codes:
func getBetsData(completion: ((Bool) -> ())? = nil) {
guard let token = defaults.string(forKey: "token") else {
return
}
let headers: HTTPHeaders = [
.authorization(bearerToken: token),
.init(name: "bet_type", value: type),
.init(name: "bet_status", value: status),
.init(name: "page", value: String(page))
]
AF.request("https://example.com", headers: headers).responseDecodable(of: Bets.self) { response in
switch response.result {
case .success:
if let data = response.data {
do {
let bets = try JSONDecoder().decode(Bets.self, from: data)
print("message: \(bets.message)")
self.setupTableData()
completion?(true)
} catch {
print("Error: \(error)")
completion?(false)
}
}
case.failure(let error):
print(error)
completion?(false)
}
}
}
You can add the headers directly:
let headers: HTTPHeaders = [.authorization(bearerToken: token)]
Additionally, if you're decoding a Decodable value from the response, you should not use responseJSON, as that decodes the Data using JSONSerialization and then you just parse it again. Instead, you should use responseDecodable.
AF.request(...).responseDecodable(of: Bets.self) { response in
// Use response.
}
That will be much more efficient and will capture errors for you automatically.
As mention by Dilan only token is not enought you will need to Bearer in the same Header parameter.
Here is one of the best method to handle Token Request and Retrier in all the request you send to server in the application
https://www.avanderlee.com/swift/authentication-alamofire-request-adapter/
By this you don't need to handle token manually in all the webservice.

Kraken EAPI: Invalid key

I am trying to do a client with swift however I cannot communicate with the private api {"error": ["EAPI: Invalid key"]}
I use CCHmac function and tried to have a look at different api implementation.
I just can't get whats wrong...
Here is my code:
func getTradeBalance(basedAsset: String, completionBlock: #escaping BlockResult) {
guard let url = URL(string: "\(krakenUtils.rootUrl)/\(krakenUtils.version)/private/Balance") else {
return
}
let nonce: String = String(Int(Date().timeIntervalSince1970.rounded()))
let path = "/\(krakenUtils.version)/private/Balance"
let pubKey = krakenUtils.publicKey
let params = ["nonce": nonce]
//Sign = HMAC-SHA512 of URI + SHA256-POSTDATAS + base64decodedSecret
let sign = getMessageSignature(path: path,
nonce: nonce)
Alamofire.request(url, method: .post,
parameters: params, encoding: JSONEncoding.default,
headers: ["API-Key": pubKey,
"API-Sign": sign])
.responseJSON { resp in
let result = self.handleResponse(result: resp)
guard let json = result.0 else {
completionBlock(nil, result.1)
return
}
print(json)
}
}
private func getMessageSignature(path: String, nonce: String) -> String {
let secretDecoded = Data(base64Encoded: krakenUtils.privateKey, options: Data.Base64DecodingOptions.init(rawValue: 0))!
let np = (nonce + "nonce=" + nonce).sha256().data(using: .utf8, allowLossyConversion: false)!
var pathNP = path.data(using: .utf8, allowLossyConversion: false)!
pathNP.append(contentsOf: np)
let lRet = HMAC.sign(data: pathNP, algorithm: .sha512, key: secretDecoded).base64EncodedString()
return lRet
}
public static func sign(data: Data, algorithm: Algorithm, key: Data) -> Data {
let signature = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: algorithm.digestLength)
data.withUnsafeBytes { dataBytes in
key.withUnsafeBytes { keyBytes in
CCHmac(algorithm.algorithm, keyBytes, key.count, dataBytes, data.count, signature)
}
}
return Data(bytes: signature, count: algorithm.digestLength)
}
This is guide for authenticated call HTTPS Header:
API-Key = API key
API-Sign = Message signature using HMAC-SHA512 of (URI path + SHA256(nonce + POST data)) and base64 decoded secret API key
This is guide for authenticated call POST Data:
nonce = always increasing unsigned 64 bit integer
otp = two-factor password (if two-factor enabled, otherwise not required)
For the API-Key you use krakenUtils.publicKey. The name suggests you use some public key (do not know where you got it)
However this should be your personal API-Key. You can get it at the kraken site (login with your account) and create an API key. You also get your API-code here. They go together as a pair
Although this is an old question, many users (including myself) who have attempted to use Kraken’s API have encountered and reported similar problems (INVALID KEY). Recently I reported the issue I was having to a Kraken rep…after an exchange with several reps it was discovered that there was a flaw in the posted Kraken example on their website. Here is some of the exchange:
...”In the method QueryPrivateEndpoint there is a line of code (should be line 256 from the file downloaded) that looks like this:
String apiEndpointFullURL = baseDomain + privatePath + endPointName + "?" + inputParameters;
It needs to be modified to look like this:
String apiEndpointFullURL = baseDomain + privatePath + endPointName;
After you make that code change, the Invalid Key error should go away.
…….
Thank you for your gratitude and pointing out the mistake to our attention.
We are pleased to know the issue has been resolved.
“……
They will post the update to their example code on their website.
Also, it’s worth noting that an improper/inconsistent ORDER of the parameters in a call that has parameters associated with it can cause an INVALID KEY error.

Saving Alamofire request data into a variable

I'm having troubles with getting password salt from my VM server via Alamofire. I'm making a request to server and it should return me salt, so I can salt my password, hash it and send it back to server.
The problem is that I do not understand how to save the salt, that Alamofire receives, into a variable, so I can just add it to password and hash it like that:
let salted_password = user_password + salt
let hash = salted_password.sha1()
Where user_password is what the user entered into password field and salt is what I got from Alamofire salt request.
Here is my code:
func getSalt(completionHandler: #escaping (DataResponse<String>, Error?) -> Void) {
Alamofire.request("http://192.168.0.201/salt", method: .post, parameters: salt_parameters).responseString { response in
switch response.result {
case .success(let value):
completionHandler(response as DataResponse<String>, nil)
case .failure(let error):
completionHandler("Failure", error)
}
}
}
let salt = getSalt { response, responseError in
return response.result.value!
}
It gives me the following error:
Binary operator '+' cannot be applied to operands of type 'String' and '()'.
So is it possible to save a request value into a variable? What should I do?
Thank you for your attention.
The problem here is because of how you implemented your completion block
For example:
func someAsynchronousCall(a: Int, b: Int, #escaping block: (_ result: Int) -> Void) {
... some code here
... {
// let's just say some async call was done and this code is called after the call was done
block(a + b)
}
}
To use this code it would look like this:
var answer: Int = 0
someAsynchronousCall(100, b: 200) { result in // the `result` is like what you're returning to the user since API calls need to be called asynchronously you do it like this rather than creating a function that has a default return type.
answer = result
print(answer)
}
print(answer)
The print would look like this
0
300
Since we declared answer as 0 it printed that first since the async call wasn't done yet, after the async call was done (usually a few milliseconds after) it then printed 300
So all in all your code should look something like this
var salt: String?
getSalt { response, responseError in
salt = response.result.value
}