Authenticating with Coinbase's Exchange's API (HMAC) using requests in Python - python

I am implementing Coinbase's exchange API using custom auth in requests-python. The following code works with all the (authenticated) GET-based calls, but fails for all the authenticated POST-based calls (I haven't tried with DELETE or UPDATE verbs). I don't understand why the signature wouldn't work for both, because the payload is timestamp + method + path for GETs and timestamp + method + path + body for PUTs, so custom auth seems correct. Something is going wrong with adding the body and changing GET to POST. Thanks!
You can get your API keys for trying it out here: https://gdax.com/settings
import json, hmac, hashlib, time, requests, base64
from requests.auth import AuthBase
class CoinbaseAuth(AuthBase):
SIGNATURE_HTTP_HEADER = 'CB-ACCESS-SIGN'
TIMESTAMP_HTTP_HEADER = 'CB-ACCESS-TIMESTAMP'
KEY_HTTP_HEADER = 'CB-ACCESS-KEY'
PASSPHRASE_HTTP_HEADER = 'CB-ACCESS-PASSPHRASE'
def __init__(self, api_key, secret_key, passphrase):
self.api_key = api_key
self.secret_key = secret_key
self.passphrase = passphrase
def __call__(self, request):
#Add headers
request.headers[CoinbaseAuth.KEY_HTTP_HEADER] = self.api_key
request.headers[CoinbaseAuth.PASSPHRASE_HTTP_HEADER] = self.passphrase
timestamp = str(time.time())
request.headers[CoinbaseAuth.TIMESTAMP_HTTP_HEADER] = timestamp
#add signature
method = request.method
path = request.path_url
content = request.body
message = timestamp + method + path
if content:
message += content
hmac_key = base64.b64decode(self.secret_key)
sig = hmac.new(hmac_key, message, hashlib.sha256)
sig_b64 = sig.digest().encode("base64").rstrip("\n")
#Add signature header
request.headers[CoinbaseAuth.SIGNATURE_HTTP_HEADER] = sig_b64
return request
#Get your keys here: https://gdax.com/settings
key = 'KEY GOES HERE'
secret = 'SECRET GOES HERE'
passphrase = 'PASSPHRASE GOES HERE'
api_url = 'https://api.gdax.com:443/'
auth = CoinbaseAuth(API_KEY, API_SECRET, API_PASS)
#GETs work, shows account balances
r = requests.get(api_url + 'accounts', auth=auth)
print r.json()
#POSTs fail: {message: 'invalid signature'}
order = {}
order['size'] = 0.01
order['price'] = 100
order['side'] = 'buy'
order['product_id'] = 'BTC-USD'
r = requests.post(api_url + 'orders', data=json.dumps(order), auth=auth)
print r.json()
And the output:
GET call: 200: [{u'available': .......}]
POST call: 400: {u'message': u'invalid signature'}
EDIT: POSTing 'a' instead of valid JSON-encoded data results in the same signature error (rather than a JSON decoding error from the server), so I don't think it is the way I'm forming the data. Notably, if I omit the body -- request.post(..., data='',...) --- the server responds appropriately with {u'message': u'Missing product_id'}.

I don't know why, but if I change the data keyword argument to requests.post() to json it works:
r = requests.post(api_url + 'orders', json=order, auth=auth)
EDIT: The only thing that changes, AFAICT, is the content-type in the header is changed from text to JSON. So it is likely that or a unicode vs ASCII encoding issue. Here's the issue for the library that added this feature recently: https://github.com/kennethreitz/requests/issues/2025#issuecomment-46337236

I believe the content needs to be a json string with no spaces (this is what the node example does anyway). Maybe try this:
message += json.dumps(content).replace(' ', '')

I had the same exact problem until I looked at the public gdax API for nodeJS and found that they are using some additional headers that were not mentioned in the GDAX API docs. I added them and then it started working. See my answer to the following: GDAX API Always Returns Http 400 "Invalid Signature" Even though I do it exactly like in the API Doc

Related

Issue with FTX US API

I have looked through the FTX API documentation found here: https://docs.ftx.us/#overview
And I've looked at the example code found in this repo: https://github.com/ftexchange/ftx/tree/master/rest
I can't get or post anything that requires Authentication. I am using the API key on my account that has 'full trade permissions', and when I look at print(request.headers) the headers look like they are in the right format.
Let me know if you need any more information, my code returns
Error Code: 400 Invalid API Key
import time
import hmac
from requests import Request
ts = int(time.time() * 1000)
request = Request('GET', 'https://ftx/api/wallet/balances')
prepared = request.prepare()
signature_payload = f'{ts}{prepared.method}{prepared.path_url}'.encode()
signature = hmac.new('API SECRET'.encode(), signature_payload, 'sha256').hexdigest()
request.headers[f'FTXUS-KEY'] = API KEY'
request.headers[f'FTXUS-SIGN'] = signature
request.headers[f'FTXUS-TS'] = str(ts)
res = requests.get('https://ftx/api/wallet/balances', headers=prepared.headers)
r = res.json()
I also got this error and It was a silly mistake.
I think you have FTX.US API Key and you are hitting on 'https://ftx/api/wallet/balances'. U should hit on 'https://ftx.us/api/wallet/balances' to get this working

(Python) Bittrex API v3 keeps returning invalid content hash

Writing a bot for a personal project, and the Bittrex api refuses to validate my content hash. I've tried everything I can think of and all the suggestions from similar questions, but nothing has worked so far. Tried hashing 'None', tried a blank string, tried the currency symbol, tried the whole uri, tried the command & balance, tried a few other things that also didn't work. Reformatted the request a few times (bytes/string/dict), still nothing.
Documentation says to hash the request body (which seems synonymous with payload in similar questions about making transactions through the api), but it's a simple get/chcek balance request with no payload.
Problem is, I get a 'BITTREX ERROR: INVALID CONTENT HASH' response when I run it.
Any help would be greatly appreciated, this feels like a simple problem but it's been frustrating the hell out of me. I am very new to python, but the rest of the bot went very well, which makes it extra frustrating that I can't hook it up to my account :/
import hashlib
import hmac
import json
import os
import time
import requests
import sys
# Base Variables
Base_Url = 'https://api.bittrex.com/v3'
APIkey = os.environ.get('B_Key')
secret = os.environ.get('S_B_Key')
timestamp = str(int(time.time() * 1000))
command = 'balances'
method = 'GET'
currency = 'USD'
uri = Base_Url + '/' + command + '/' + currency
payload = ''
print(payload) # Payload Check
# Hashes Payload
content = json.dumps(payload, separators=(',', ':'))
content_hash = hashlib.sha512(bytes(json.dumps(content), "utf-8")).hexdigest()
print(content_hash)
# Presign
presign = (timestamp + uri + method + str(content_hash) + '')
print(presign)
# Create Signature
message = f'{timestamp}{uri}{method}{content_hash}'
sign = hmac.new(secret.encode('utf-8'), message.encode('utf-8'),
hashlib.sha512).hexdigest()
print(sign)
headers = {
'Api-Key': APIkey,
'Api-Timestamp': timestamp,
'Api-Signature': sign,
'Api-Content-Hash': content_hash
}
print(headers)
req = requests.get(uri, json=payload, headers=headers)
tracker_1 = "Tracker 1: Response =" + str(req)
print(tracker_1)
res = req.json()
if req.ok is False:
print('bullshit error #1')
print("Bittex response: %s" % res['code'], file=sys.stderr)
I can see two main problems:
You are serialising/encoding the payload separately for the hash (with json.dumps and then bytes) and for the request (with the json=payload parameter to request.get). You don't have any way of knowing how the requests library will format your data, and if even one byte is different you will get a different hash. It is better to convert your data to bytes first, and then use the same bytes for the hash and for the request body.
GET requests do not normally have a body (see this answer for more details), so it might be that the API is ignoring the payload you are sending. You should check the API docs to see if you really need to send a request body with GET requests.

How to construct the hashed token signature for Azure Cosmos DB REST API to list users?

According to the documentation for the Cosmos DB REST API, with every API call, the Authorization header must be set. The value for this is constructed as described here: https://learn.microsoft.com/en-us/rest/api/cosmos-db/access-control-on-cosmosdb-resources
I am implementing this in Python as follows:
def get_authorisation_token(verb, resource_type, resource_id, date, master_key):
key = base64.b64decode(master_key)
text = f"""{verb.lower()}\n{resource_type.lower()}\n{resource_id.lower()}\n{date.lower()}\n\n"""
text_encoded = text.encode('utf-8')
signature_hash = hmac.new(key, text_encoded, digestmod=hashlib.sha256).digest()
signature = base64.b64encode(signature_hash).decode()
key_type = 'master'
version = '1.0'
uri = f'type={key_type}&ver={version}&sig={signature}'
uri_encoded = urllib.parse.quote(uri)
return uri_encoded
Since this is sent with every call, the auth token needs to be re-created to match the request URL. So, for example to get a list of databases, one must provide the resource type to be dbs and the resource link/ID to be an empty string with the URL: https://{databaseaccount}.documents.azure.com/dbs/
The part I cannot figure out, is the correct combination of resource type and resource ID/link to get all users from a particular database. Documentation can be found here: https://learn.microsoft.com/en-us/rest/api/cosmos-db/list-users
I have tried some combinations but nothing returns the users, I just get a 401:
{
"code": "Unauthorized",
"message": "The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'get\nusers\ndbs/<db_name>\nmon, 09 nov 2020 23:37:24 gmt\n\n'\r\nActivityId: 697a4159-f160-4aab-ae90-6cb5eaadb710, Microsoft.Azure.Documents.Common/2.11.0"
}
Regarding the issue, please refer to the following code
from wsgiref.handlers import format_date_time
from datetime import datetime
from time import mktime
import base64
from urllib.parse import quote
import hmac
from hashlib import sha256
import requests
from azure.cosmos.auth import GetAuthorizationHeader
from azure.cosmos.cosmos_client import CosmosClientConnection
master_key = ''
database_name = ''
key = base64.b64decode(master_key)
verb = 'GET'
resource_type = 'users'
resource_id = f'dbs/{database_name}'
now = datetime.now()
stamp = mktime(now.timetuple())
date = format_date_time(stamp)
print(date)
text = "{verb}\n{resource_type}\n{resource_id}\n{date}\n{other}\n".format(
verb=(verb.lower() or ''),
resource_type=(resource_type.lower() or ""),
resource_id=(resource_id or ""),
date=date.lower(),
other="".lower())
body = text.encode("utf-8")
digest = hmac.new(key, body, sha256).digest()
signature = base64.encodebytes(digest).decode("utf-8")
key_type = 'master'
version = '1.0'
uri = f'type={key_type}&ver={version}&sig={signature[:-1]}'
uri_encoded = quote(uri)
url = "https://<>.documents.azure.com:443/dbs/<>/users"
payload = {}
headers = {
'Authorization': uri_encoded,
'x-ms-date': date,
'x-ms-version': '2018-12-31'
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)

Python requests token for REST API keeps returning 401 unable to determine proper token format

I have this working in Powershell, but i'm trying to figure out how to get it working in Python. In Powershell i just use
$webclient.Headers.Add("Authorization", "Basic $auth")
with passing to $auth my b64Val of my username and password combo, then getting my token string and again passing it as to the same format using Authorization and Basic with $auth containing my long token string.
However, i keep getting 401 returned over in python trying to fig out how to do the same.. here is my sample python code, the first request to get My_token works fine..
password = 'myPassword'
usrPass = "admin:" + password
b64Val = base64.b64encode(usrPass.encode("ascii")).decode("ascii")
My_token=requests.get("https://10.10.1.2/api/login",headers={"Authorization" : "Basic %s" % b64Val}, verify=False)
token_contents = My_token.content.decode("utf-8").replace('"', '')
#token_test = 'Token ' + token_contents
token_test = 'token ' + token_contents
#token_test = 'TOK:' + token_contents
#token_test = 'Basic ' + token_contents
#token_test = 'token=' + token_contents
header = {'Authorization' : token_test}
url = 'https://10.10.1.2/api/types/instances'
r = requests.get(url, headers=header, verify=False)
see my commented lines i tried all different types of token settings.
my thoughts were i had to convert the token from bytes to string and remove the double quotes from it , the My_token.content original value property looks like so.
b'"YWRtaW46MTU2OTE4NzkwMgg3NzpjZDdlZWEyNmI2NDJmOTY0ZmI5ZGQ3YzBiNnI2ZTNlZQ"'
Just trying to fig out what i'm misunderstanding with taking the My_token object which works fine and logs me in , but then return the token back on the next request i am trying to perform. thanks
[UPDATE and FIX]
thanks to stovfl's comments i was able to learn more and see my mistakes. i was over thinking this and making it more complex than needed to be ;). I just needed to KISS and just basic auth with my token contents lol.
r_test = requests.get('https://10.10.1.2/api/types/instances', auth=("admin", token_contents), verify=False)

Poloniex API Request Gives 404 Error

I am writing a custom Python class to encapsulate the Poloniex trading API. However, I am running in to a problem with the request returning a "404 Error". I have been over and over the documentation and am quite sure that I am utilizing the right endpoint... What else could I be doing wrong here:
...
self.trading_api = 'https://poloniex.com/tradingapi'
self.api_key = 'My API key'
self.secret_key = bytes('My Secret Key', 'latin-1')
...
req['nonce'] = int(time.time()*1000)
data = urllib.parse.urlencode(req).encode()
sign = hmac.new(self.secret_key, data, sha512)
signature=sign.hexdigest()
headers = dict(Key=self.api_key, Sign=signature)
conn = urllib.request.Request(self.trading_api, headers=headers)
self.rate_limit()
try:
requested = urllib.request.urlopen(conn, data=data)
return requested
The A in the url must be capitalized:
self.trading_api = 'https://poloniex.com/tradingApi'
While Poloniex's documentation does not make note of this (in fact the url used was copied directly from their page), remember to capitalize it!

Categories