I write code to verify an HMAC Auth incoming POST request with JSON to our API. The HMAC I received is OD5ZxL4tdGgWr78e9vO3cYrjuOFT8WOrTbTIuuIH1PQ=
When I try to generate it by my self using Python, it is always different.
Here is the JSON request I received:
{
"shipper_id": 4841,
"status": "Cancelled",
"shipper_ref_no": "",
"tracking_ref_no": "",
"shipper_order_ref_no": "",
"timestamp": "2018-05-23T15:13:28+0800",
"id": "61185ecf-3484-4985-b625-ffe30ba36e28",
"previous_status": "Pending Pickup",
"tracking_id": "NVSGBHINK000000001"
}
And the client secret is 817a3723917f4c7fac24b1f1b324bbab.
The HMAC secret I received is OD5ZxL4tdGgWr78e9vO3cYrjuOFT8WOrTbTIuuIH1PQ=.
Here is the code when I write it in PHP:
<?php
define('CLIENT_SECRET', 'my_shared_secret');
function verify_webhook($data, $hmac_header){
$calculated_hmac = base64_encode(hash_hmac('sha256', $data, CLIENT_SECRET, true));
return ($hmac_header == $calculated_hmac);
}
$hmac_header = $_SERVER['X-NINJAVAN-HMAC-SHA256'];
$data = file_get_contents('php://input');
$verified = verify_webhook($data, $hmac_header);
error_log('Webhook verified: '.var_export($verified, true)); //check error.log to see result
?>
But I have no idea how to do it in Python 3.
In Python 3 you basically want something like the following, taken from how you handle GitHub webhook requests.
import hashlib
import hmac
secret = 'CLIENT_SECRET'
data = rsp.content # assumes you're using requests for data/sig
signature = rsp.headers['X-Something-Signature']
signature_computed = 'sha1=' + hmac.new(
key=secret.encode('utf-8'),
msg=data.encode('utf-8'),
digestmod=hashlib.sha1
).hexdigest()
if not hmac.compare_digest(signature, signature_computed):
log("Invalid payload")
If you want to recreate the hashing code from PHP to Python do it thusly:
def create_signature(key, data):
sig_hash = hmac.new(key.encode('utf8'), data.encode('utf8'), hashlib.sha256).digest()
base64_message = base64.b64encode(sig_hash).decode()
return base64_message
This will create the signature that should match what your PHP code is creating. Just compare the signature to what is sent in the header.
from collections import OrderedDict
params = orderedDict()
params["shipper_id"] = 4841
params["status"] = "Cancelled"
params["shipper_ref_no"] = ""
params["tracking_ref_no"] = ""
params["shipper_order_ref_no"] = ""
params["timestamp"] = "2018-05-23T15:13:28+0800"
params["id"] = "61185ecf-3484-4985-b625-ffe30ba36e28"
params["previous_status"] = "Pending Pickup"
params["tracking_id"] = "NVSGBHINK000000001"
mes = json(params, separator = (";",",")).highdigest()
sighnature = hmac.new(mes, sha256)
# separators = (";",",") - i'm not shure
params['sighnature'] = sighnature
r = response.post(url,params,sighnature)
print(r.text())
Related
I need to send an API request to SMS API.
My Python code is working but my X++/C# code is not working.
I tried with Postman, it is working as well.
Here's my Python code:
import requests
headers = {"Accept":"application/json"}
data = {"AppSid":"#############YxLtXtaN###",
"SenderID":"####23423#####",
"Body":"This is a test message.",
"Recipient":"###45645######",
"responseType":"JSON",
"CorrelationID":"",
"baseEncode":"true",
"statusCallback":"sent",
"async":"false"}
r = requests.post('http://myapi/rest/SMS/messages', auth=('user#domain.com', 'password'), headers=headers,
data=data)
Here's my X++/C# code:
class Class1
{
public static void main(Args _args)
{
str destinationUrl = 'myapi', requestXml, responseXml;
System.Net.HttpWebRequest request;
System.Net.HttpWebResponse response;
CLRObject clrObj;
System.Byte[] bytes;
System.Text.Encoding utf8;
System.IO.Stream requestStream, responseStream;
System.IO.StreamReader streamReader;
System.Exception ex;
System.Net.WebHeaderCollection httpHeader;
str byteStr;
System.Byte[] byteArray;
System.IO.Stream stream;
System.IO.Stream dataStream;
byteStr = strfmt('%1:%2', "user#domain.com", "password");
requestXml = " {\"AppSid\":\"###########\", \"SenderID\":\"########-AD\", \"Body\":\"This is a test message from ## from X++ Coding Language..\", \"Recipient\":\"######\", \"responseType\":\"JSON\", \"CorrelationID\":\"\", \"baseEncode\":\"true\", \"statusCallback\":\"sent\", \"async\":\"false\"}";
try
{
new InteropPermission(InteropKind::ClrInterop).assert();
httpHeader = new System.Net.WebHeaderCollection();
clrObj = System.Net.WebRequest::Create(destinationUrl);
request = clrObj;
utf8 = System.Text.Encoding::get_UTF8();
bytes = utf8.GetBytes(requestXml);
request.set_KeepAlive(true);
request.set_ContentType("application/xml");
request.AllowAutoRedirect=true;
utf8 = System.Text.Encoding::get_UTF8();
byteArray = utf8.GetBytes(byteStr);
byteStr = System.Convert::ToBase64String(byteArray);
httpHeader.Add("Authorization", 'Basic ' + byteStr);
request.set_ContentType("text/xml; encoding='utf-8'");
request.set_ContentLength(bytes.get_Length());
request.set_Method("POST");
request.set_Headers(httpHeader);
requestStream = request.GetRequestStream();
requestStream.Write(bytes, 0, bytes.get_Length());
response = request.GetResponse();
responseStream = response.GetResponseStream();
streamReader = new System.IO.StreamReader(responseStream);
responseXml = streamReader.ReadToEnd();
info(responseXml);
}
catch (Exception::CLRError)
{
//bp deviation documented
ex = CLRInterop::getLastException().GetBaseException();
error(ex.get_Message());
}
requestStream.Close();
streamReader.Close();
responseStream.Close();
response.Close();
}
}
I'm getting this error:
error code : The remote server returned an error: (308) Permanent
Redirect.
I made two changes.
removed the Authorization by commenting it out
//httpHeader.Add("Authorization", 'Basic ' + byteStr);
Made it HTTPS instead of HTTP. I could see the Location: HTTPS in the exception Response object in Visual Studio.
These 2 things resolved the issue for me.
I don't know about x++, but you could try explicitly allowing redirect on your webRequest object.
webRequest = System.Net.WebRequest::Create('http://myapi/rest/SMS/messages') as System.Net.HttpWebRequest;
webRequest.AllowAutoRedirect = true;
webRequest.MaximumAutomaticRedirections = 1; //Set value according to your requirements
Can any one help to do the same code in Python
<?php
$payload = '{"amount":205.16,"currency":"AED","language":"en","merchant_code":"O8Ryrfvy8aUb","merchant_order":"931","redirect_urls":{"cancel":"http://127.0.0.1:8000/api/pointspay-cancel","fail":"http://127.0.0.1:8000/api/pointspay-failed","success":"http://127.0.0.1:8000/api/pointspay-success"},"timestamp":1604722764987,"type":"direct"}';
echo x509_fingerprint($payload, '/home/tmt/Documents/Protect4less/LoyLogic/pointspay_certificate.p12', 'Protect4Less#123', 'sha256') . "\n\n";
function x509_fingerprint($payload, $cert_path, $cert_pass, $algorithm = 'sha256')
{
$algorithm = in_array($algorithm, array(
'sha1',
'md5',
'sha256'
)) ? $algorithm : 'sha1';
// 1. Capture the private key from certificate
if (!$cert_store = file_get_contents($cert_path)) {
echo "Error: Unable to read the cert file\n";
exit;
}
if (openssl_pkcs12_read($cert_store, $cert_info, $cert_pass)) {
} else {
echo "Error: Unable to read the cert store.\n";
exit;
}
$privateKey = $cert_info['pkey'];
print_r($privateKey);
// 2. create message digest
$messageDigest = openssl_digest($payload, $algorithm, true);
// 3. Sign the message digest using private key
openssl_private_encrypt($messageDigest, $encryptedData, $privateKey);
return base64_encode($encryptedData);
}
?>
This is PHP code and I want to do the same steps in PYTHON, can any 1 help me to do so.
I tried many things in python but was not able to do so.
Following is the Python code I tried, but the final signature doesn't match with the PHP signature
from OpenSSL import crypto
import hashlib
from base64 import b64encode
str1 = b'{"amount":205.16,"currency":"AED","language":"en","merchant_code":"O8Ryrfvy8aUb","merchant_order":"931","redirect_urls":{"cancel":"http://127.0.0.1:8000/api/pointspay-cancel","fail":"http://127.0.0.1:8000/api/pointspay-failed","success":"http://127.0.0.1:8000/api/pointspay-success"},"timestamp":1604722764987,"type":"direct"}'
psw = 'Protect4Less#123'
p12 = crypto.load_pkcs12(open("/home/tmt/Documents/Protect4less/LoyLogic/pointspay_certificate.p12", 'rb').read(), psw)
pkey = p12.get_privatekey()
my_hash2 = hashlib.sha256() #Choose SHA256 and update with same bytes
my_hash2.update(str1)
hash_digest = my_hash2.hexdigest()
print("Result after digesting: " + str(my_hash2.hexdigest()))
# data = str1
my_signature = crypto.sign(pkey, str1, 'sha256')
signature_b64 = b64encode(my_signature)
print(signature_b64)
I am trying to get a python script to say whether a twitch channel is live but haven't been able to do it, any and all help would be appreciated.
here are the docs I've been able to find
https://dev.twitch.tv/docs/api/guide
This is what I have atm but I keep on getting "'set' object has no attribute 'items'". This is modified code from "Is There Any Way To Check if a Twitch Stream Is Live Using Python?" however it is now outdated because of the new API.
import requests
def checkUser():
API_HEADERS = {
'Client-ID : [client id here from dev portal]',
'Accept : application/vnd.twitchtv.v5+json',
}
url = "https://api.twitch.tv/helix/streams/[streamer here]"
req = requests.Session().get(url, headers=API_HEADERS)
jsondata = req.json()
print(jsondata)
checkUser()
The answer to your problem of "'set' object has no attribute 'items'" is just a simple typo. It should be
API_HEADERS = {
'Client-ID' : '[client id here from dev portal]',
'Accept' : 'application/vnd.twitchtv.v5+json'
}
Notice how the Colon's aren't part of the text now
And to answer your overarching question of how to tell if a channel is online you can look at this sample code I made.
import requests
URL = 'https://api.twitch.tv/helix/streams?user_login=[Channel_Name_Here]'
authURL = 'https://id.twitch.tv/oauth2/token'
Client_ID = [Your_client_ID]
Secret = [Your Client_Secret]
AutParams = {'client_id': Client_ID,
'client_secret': Secret,
'grant_type': 'client_credentials'
}
def Check():
AutCall = requests.post(url=authURL, params=AutParams)
access_token = AutCall.json()['access_token']
head = {
'Client-ID' : Client_ID,
'Authorization' : "Bearer " + access_token
}
r = requests.get(URL, headers = head).json()['data']
if r:
r = r[0]
if r['type'] == 'live':
return True
else:
return False
else:
return False
print(Check())
I have been struggling to send a signed request to binance future using signature.
I found that example code on StackOverflow ("Binance API call with SHA56 and Python requests") and an answer has been given to it mentioning to use hmac
as below: but unfortunately i still don't see how to write this example. Could anyone show how the code of this example should look like? i am really uncomfortable with signed request. Thanks a lot for your understanding and your help advice given:
params = urlencode({
"signature" : hashedsig,
"timestamp" : servertimeint,
})
hashedsig = hmac.new(secret.encode('utf-8'), params.encode('utf-8'), hashlib.sha256).hexdigest()
Original example:
import requests, json, time, hashlib
apikey = "myactualapikey"
secret = "myrealsecret"
test = requests.get("https://api.binance.com/api/v1/ping")
servertime = requests.get("https://api.binance.com/api/v1/time")
servertimeobject = json.loads(servertime.text)
servertimeint = servertimeobject['serverTime']
hashedsig = hashlib.sha256(secret)
userdata = requests.get("https://api.binance.com/api/v3/account",
params = {
"signature" : hashedsig,
"timestamp" : servertimeint,
},
headers = {
"X-MBX-APIKEY" : apikey,
}
)
print(userdata)
The proper way would be:
apikey = "myKey"
secret = "mySecret"
servertime = requests.get("https://api.binance.com/api/v1/time")
servertimeobject = json.loads(servertime.text)
servertimeint = servertimeobject['serverTime']
params = urlencode({
"timestamp" : servertimeint,
})
hashedsig = hmac.new(secret.encode('utf-8'), params.encode('utf-8'),
hashlib.sha256).hexdigest()
userdata = requests.get("https://api.binance.com/api/v3/account",
params = {
"timestamp" : servertimeint,
"signature" : hashedsig,
},
headers = {
"X-MBX-APIKEY" : apikey,
}
)
print(userdata)
print(userdata.text)
Make sure to put the signature as the last parameter or the request will return [400]...
Incorrect:
params = {
"signature" : hashedsig,
"timestamp" : servertimeint,
}
Correct:
params = {
"timestamp" : servertimeint,
"signature" : hashedsig,
}
At the time of writing, Binance themselves are mainting a repo with some examples*, using the requests library. Here is a sample in case the link goes down or is moved:
import hmac
import time
import hashlib
import requests
from urllib.parse import urlencode
KEY = ''
SECRET = ''
# BASE_URL = 'https://fapi.binance.com' # production base url
BASE_URL = 'https://testnet.binancefuture.com' # testnet base url
''' ====== begin of functions, you don't need to touch ====== '''
def hashing(query_string):
return hmac.new(SECRET.encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256).hexdigest()
def get_timestamp():
return int(time.time() * 1000)
def dispatch_request(http_method):
session = requests.Session()
session.headers.update({
'Content-Type': 'application/json;charset=utf-8',
'X-MBX-APIKEY': KEY
})
return {
'GET': session.get,
'DELETE': session.delete,
'PUT': session.put,
'POST': session.post,
}.get(http_method, 'GET')
# used for sending request requires the signature
def send_signed_request(http_method, url_path, payload={}):
query_string = urlencode(payload)
# replace single quote to double quote
query_string = query_string.replace('%27', '%22')
if query_string:
query_string = "{}×tamp={}".format(query_string, get_timestamp())
else:
query_string = 'timestamp={}'.format(get_timestamp())
url = BASE_URL + url_path + '?' + query_string + '&signature=' + hashing(query_string)
print("{} {}".format(http_method, url))
params = {'url': url, 'params': {}}
response = dispatch_request(http_method)(**params)
return response.json()
# used for sending public data request
def send_public_request(url_path, payload={}):
query_string = urlencode(payload, True)
url = BASE_URL + url_path
if query_string:
url = url + '?' + query_string
print("{}".format(url))
response = dispatch_request('GET')(url=url)
return response.json()
response = send_signed_request('POST', '/fapi/v1/order', params)
print(response)
Some additional thoughts from myself:
You can also use a new library also from Binance called Binance connector. It is a bit new, it has some issues, but it can do the basic operations without you worrying about signed requests.
I wouldn't use serverTime because that means you need to make an additional request and networks can be slow, I'd follow this example and use the int(time.time() * 1000) you may not even need the function.
I purposedly used the POST example, because this is more complicated as you need to also encode and hash your custom parameters
At the time of writing, v3 is the latest version
Hope it helps.
* https://github.com/binance/binance-signature-examples/blob/master/python/futures.py
I am working with an API that doesn't have all the information I need in a single call, and I need to the project code it came from into the call that I am making. Right now it appends the project data to the list, but I really need it to be part of the original call. Here is my output now:
[{"committer_email": "justin.m.boucher#example.com", "short_id": "981147b9", "title": "Added .gitignore", "author_email": "justin.m.boucher#example.com", "authored_date": "2017-08-29T08:31:11.000-07:00", "created_at": "2017-08-29T08:31:11.000-07:00", "author_name": "Justin Boucher", "parent_ids": [], "committed_date": "2017-08-29T08:31:11.000-07:00", "message": "Added .gitignore\n", "committer_name": "Justin Boucher", "id": "981147b905913a60796283ce10f915c53679df49"}, {"project_id": "2"}]
Here is the output I want to achieve:
[{"project_id": "2", "committer_email": "justin.m.boucher#example.com", "short_id": "981147b9", "title": "Added .gitignore", "author_email": "justin.m.boucher#example.com", "authored_date": "2017-08-29T08:31:11.000-07:00", "created_at": "2017-08-29T08:31:11.000-07:00", "author_name": "Justin Boucher", "parent_ids": [], "committed_date": "2017-08-29T08:31:11.000-07:00", "message": "Added .gitignore\n", "committer_name": "Justin Boucher", "id": "981147b905913a60796283ce10f915c53679df49"}]
Here is my code so far:
get_commits.py:
import gitlab
import json
gitlab = gitlab.Gitlab()
projects = gitlab.getProjectID()
for i in projects:
api_str = '/projects/' + str(i) + '/repository/commits'
connect = gitlab.connectAPI(apiCall=api_str)
data = json.dumps(connect)
# Append project id to json, since it isn't created
# in the commits from Gitlab
commit = json.loads(data)
commit.append({'project_id': str(i)})
# make it pretty again for Splunk to read
commit = json.dumps(commit)
print commit
gitlab.py
import os
import ConfigParser
import requests
import json
# Setup Splunk Environment
APPNAME = 'splunk_gitlab'
CONFIG = 'appconfig.conf'
SPLUNK_HOME = os.environ['SPLUNK_HOME']
parser = ConfigParser.SafeConfigParser()
class Gitlab():
# # Load Settings
# parser.read(SPLUNK_HOME + '/etc/apps/' + APPNAME + '/local/' + CONFIG)
# if parser.has_section('Authentication'):
# pass
# else:
# parser.read(SPLUNK_HOME + '/etc/apps/' + APPNAME + '/default/' + CONFIG)
#
# GITLAB_URL = parser.get('Authentication', 'GITLAB_URL')
# API_KEY = parser.get('Authentication', 'API_KEY')
# Used for testing only
GITLAB_URL = 'http://<my_address>'
API_KEY = '<my_key>'
API_SERVER = GITLAB_URL + '/api/v4'
# Place api call to retrieve data
def connectAPI(self, apiCall='/projects'):
headers = {
'PRIVATE-TOKEN': self.API_KEY
}
final_url = self.API_SERVER + apiCall
resp = requests.get(final_url, headers=headers)
status_code = resp.status_code
resp = resp.json()
if status_code == 200:
return resp
else:
raise Exception("Something went wrong requesting (%s): %s" % (
resp['errors'][0]['errorType'], resp['errors'][0]['message']))
def getProjectID(self):
connect = self.connectAPI(apiCall='/projects')
data = json.dumps(connect)
projects = json.loads(data)
project_list = []
for i in projects:
project_list.append(i['id'])
return project_list
If you want to add a new element to the first dictionary in the list instead of appending a new dictionary to the list, try using assignment instead of append.
commit[0]['project_id'] = str(i)