I am working with webhooks from Bold Commerce, which are validated using a hash of the timestamp and the body of the webhook, with a secret key as the signing key. The headers from the webhook looks like this :
X-Bold-Signature: 06cc9aab9fd856bdc326f21d54a23e62441adb5966182e784db47ab4f2568231
timestamp: 1556410547
Content-Type: application/json
charset: utf-8
According to their documentation, the hash is built like so (in PHP):
$now = time(); // current unix timestamp
$json = json_encode($payload, JSON_FORCE_OBJECT);
$signature = hash_hmac('sha256', $now.'.'.$json, $signingKey);
I am trying to recreate the same hash using python, and I am always getting the wrong value for the hash. I've tried several combinations, with and without base64 encoding. In python3, a bytes object is expected for the hmac, so I need to encode everything before I can compare it. At this point my code looks like so :
json_loaded = json.loads(request.body)
json_dumped = json.dumps(json_loaded)
# if I dont load and then dump the json, the message has escaped \n characters in it
message = timestamp + '.' + json_dumped
# => 1556410547.{"event_type" : "order.created", "date": "2020-06-08".....}
hash = hmac.new(secret.encode(), message.encode(), hashlib.sha256)
hex_digest = hash.hexdigest()
# => 3e4520c869552a282ed29b6523eecbd591fc19d1a3f9e4933e03ae8ce3f77bd4
# hmac_to_verify = 06cc9aab9fd856bdc326f21d54a23e62441adb5966182e784db47ab4f2568231
return hmac.compare_digest(hex_digest, hmac_to_verify)
Im not sure what I am doing wrong. For the other webhooks I am validating, I used base64 encoding, but it seems like here, that hasnt been used on the PHP side. I am not so familiar with PHP so maybe there is something I've missed in how they built the orginal hash. There could be complications coming from the fact that I have to convert back and forth between byte arrays and strings, maybe I am using the wrong encoding for that ? Please someone help, I feel like I've tried every combination and am at a loss.
EDIT : Tried this solution by leaving the body without encoding it in json and it still fails :
print(type(timestamp)
# => <class 'str'>
print(type(body))
# => <class 'bytes'>
# cant concatenate bytes to string...
message = timestamp.encode('utf-8') + b'.' + body
# => b'1556410547.{\n "event_type": "order.created",\n "event_time": "2020-06-08 11:16:04",\n ...
hash = hmac.new(secret.encode(), message, hashlib.sha256)
hex_digest = hash.hexdigest()
# ...etc
EDIT EDIT :
Actually it is working in production ! Thanks to the solution described above (concatenating everything as bytes). My Postman request with the faked webhook was still failing, but that's probably because of how I copied and pasted the webhook data from my heroku logs :) .
Related
I'm trying to send one image to my Lambda Function using Python just to test for one project, but Postman is giving me one error and I don't know how to solve it.
My code is simply to detect if I have some data in the key "image" and return some message. I'm using Postman to send the POST request, I clicked in the Body tab, selected the form-data option and I wrote image for the key and selected the image file from my computer (the image size is 27 kb). This is the code in my Lambda Function:
def lambda_handler(event, context):
if event['image']:
return {
"Message": 'Everything went ok'
}
And this is the error message that I'm receiving from Postman:
{ "message": "Could not parse request body into json: Unexpected
character ('-' (code 45)) in numeric value: expected digit (0-9) to
follow minus sign, for valid numeric value\n at [Source:
(byte[])"----------------------------137965576541301454606184\r\nContent-Disposition: form-data; name="image"; filename="TestImage.png"\r\nContent-Type:
image/png\r\n\r\n�PNG\r\n\n ... }
To solve that problem, I needed to set my Camera to convert the image to base64 and then upload it to the server.
In the server, I convert it again and then work with it as I want. Base64 is a group of binary-to-text encoding schemes that represent binary data in an ASCII string format by translating it into a radix-64 representation.
So, you will convert your image to string and then sending it, it was the best way that I found to upload my images.
I was struggling with this. I was using Postman, getting UnidentifiedImageError. The below worked.
Posting the Image:
data = open('x.jpg','rb').read()
data = base64.b64encode(data).decode("utf8")
r = requests.post('url',data=data)
Processing on the function side
def lambda_handler(event, context):
image_bytes = event['body'].encode('utf-8')
img_b64dec = base64.b64decode(image_bytes)
img_byteIO = BytesIO(img_b64dec)
image = Image.open(img_byteIO)
Ok, I know there are too many questions on this topic already; reading every one of those hasn't helped me solve my problem.
I have " hello'© " on my webpage. My objective is to get this content as json, strip the "hello" and write back the remaining contents ,i.e, "'©" back on the page.
I am using a CURL POST request to write back to the webpage. My code for getting the json is as follows:
request = urllib2.Request("http://XXXXXXXX.json")
user = 'xxx'
base64string = base64.encodestring('%s:%s' % (xxx, xxx))
request.add_header("Authorization", "Basic %s" % base64string)
result = urllib2.urlopen(request) #send URL request
newjson = json.loads(result.read().decode('utf-8'))
At this point, my newres is unicode string. I discovered that my curl post request works only with percentage-encoding (like "%A3" for £).
What is the best way to do this? The code I wrote is as follows:
encode_dict = {'!':'%21',
'"':'%22',
'#':'%24',
'$':'%25',
'&':'%26',
'*':'%2A',
'+':'%2B',
'#':'%40',
'^':'%5E',
'`':'%60',
'©':'\xa9',
'®':'%AE',
'™':'%99',
'£':'%A3'
}
for letter in text1:
print (letter)
for keyz, valz in encode_dict.iteritems():
if letter == keyz:
print(text1.replace(letter, valz))
path = "xxxx"
subprocess.Popen(['curl','-u', 'xxx:xxx', 'Content-Type: text/html','-X','POST','--data',"text="+text1, ""+path])
This code gives me an error saying " UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal
if letter == keyz:"
Is there a better way to do this?
The problem was with the encoding. json.loads() returns a stream of bytes and needs to be decoded to unicode, using the decode() fucntion. Then, I replaced all non-ascii characters by encoding the unicode with ascii encoding using encode('ascii','xmlcharrefreplace').
newjson = json.loads(result.read().decode('utf-8').encode("ascii","xmlcharrefreplace"))
Also, learning unicode basics helped me a great deal! This is an excellent tutorial.
I am using redis with Lua to fetch sessiondata of any admin from Django project .In Django project sessiondata is encoded into base64 form.
sessiondata value is :
session_data = "NzlmZjZmNWQxMGIzNTQzMDZhNDZjNzJiZGQ4OWZiY2NjNDg0NDVlZTqAAn1xAShVEl9hdXRoX3VzZXJfYmFja2VuZHECVSlkamFuZ28uY29udHJpYi5hdXRoLmJhY2tlbmRzLk1vZGVsQmFja2VuZHEDVQ1fYXV0aF91c2VyX2lkcQSKAgEKdS4="
My Lua code to decode session data is
-- decode base64 code fetch from django session data
local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
local function dec(data)
data = string.gsub(data, '[^'..b..'=]', '')
return (data:gsub('.', function(x)
if (x == '=') then return '' end
local r,f='',(b:find(x)-1)
for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
return r;
end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
if (#x ~= 8) then return '' end
local c=0
for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
return string.char(c)
end))
end
when I am running print(dec(session_data)) I am getting
output = 79ff6f5d10b354306a46c72bdd89fbccc48445ee:�}q(U_auth_user_backendqU)django.contrib.auth.backends.ModelBackendqU _auth_user_idq� u.
while output suppose to be
{'_auth_user_id': 2561L, '_auth_user_backend': 'django.contrib.auth.backends.ModelBackend'}
like Django we had.
Please let me know what I am doing wrong .
A quick google search yields this blog post.
This is the (abridged, nonverifying) python code he has to do the decoding of sessiondata:
def decode(session_data, secret_key, class_name='SessionStore'):
encoded_data = base64.b64decode(session_data)
utoken, pickled = encoded_data.split(b':', 1)
return pickle.loads(pickled)
In other words, your expectations are wrong. The un-base64-ed data contains a checksum hash (79ff6...445ee) followed by a : followed by serialized (via pickle) python data (�}q(U_auth..._user_idq� u.).
If you really want to understand how to decode python picked data, see PEP 307.
If you are using django 1.5.3+ you can use json serializer so that you don't have to try to decode python pickles in lua ;) Json serializer is default in django 1.6+.
I yield - I've tried for hours to crack this nut but can't figure it out. I'm too new to Ruby (and have no Python background!) to translate this and then post my JSON data to a site that requires user/pass, and then get the response data.
This is the Python code:
r = requests.post('https://keychain.oneid.com/validate/', json.dumps(data), auth=('username', 'password'))
r.json()
where data is:
{"some" => "data", "fun" => "times"}
I'm trying to replicate the functionality of this code in Ruby for use with a Rails application, but between figuring out how the Python requests.post() function operates and then writing the Ruby code for POST and GET, I've become totally lost.
I tried Net::HTTP but I'm not clear if I should be putting the username/pass in the body or use the basic_auth method -- basic_auth seems to only work inside Net::HTTP.get ... and Net::HTTP doesn't seem to easily handle JSON, but again, I could be totally out to lunch at this point.
Any suggestions or help would be greatly appreciated!
Use the rest-client gem or just use Net::HTTP.
Ruby code(version 1.9.3):
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://keychain.oneid.com/validate/')
req = Net::HTTP::Post.new uri.path
# ruby 2.0: req = Net::HTTP::Post.new uri
req.basic_auth 'username', 'password'
req.body = {:some => 'data', :fun => 'times'}.to_json
res = Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.ssl_version = :SSLv3
http.request req
end
puts res.body
# => {"errorcode": -99, "error": "Invalid API credentials. Please verify and try again"}
json = JSON.parse res.body
puts json['errorcode']
# => -99
I'd recommend taking a look at the RestClient gem. It makes it easy to deal with GET/POST, plus all the rest of the REST calls. It also has an IRB-based shell called restclient available from the command-line, making it easier to experiment with your connection settings.
From the documentation:
RestClient.post "http://example.com/resource", { 'x' => 1 }.to_json, :content_type => :json, :accept => :json
Looking at it you can see similarities with the Python code.
You can add the authentication info to the hash:
require 'restclient'
require 'json'
require 'base64'
RestClient.post(
'https://keychain.oneid.com/validate/',
{
:authentication => 'Basic ' + Base64.encode64(name + ':' + password),
'some' => 'data',
'fun' => 'times'
}.to_json,
:content_type => :json,
:accept => :json
)
Alternately, you could use the Curb gem. Curb used libcurl, which is an industry standard tool for web connectivity. The documentation shows several ways to send POST requests.
In my post handler, the body of the message is a json.
In self.request.body, I'm getting a HTML encoded message:
%7B+%22name%22%3A+%22John+Dao%22%2C+%22Age%22%3A+42+%7D=
Taking a look at the network traffic, the payload is actually:
{ "name": "John Dao", "Age": 42 }
So, I'm pretty sure the encoding happens on the server. How do I decode this string, or somehow tell json.loads to accept encoded message, or better yet - tell WSGI (It is Google App Engine) not encoding the body to beging with?
>>> import urllib
>>> urllib.unquote_plus("%7B+%22name%22%3A+%22John+Dao%22%2C+%22Age%22%3A+42+%7D=")
'{ "name": "John Dao", "Age": 42 }='
It looks as though the GAE implementation of WebOb is trying to parse and rewrite the POST body as though its content type is "application/x-www-urlencoded" - even the url-decoded string has a "=" appended to it.
If this is the case, and you can change the client behavior, try setting it to something like "application/json" instead.
If you want to get the values corresponding to the keys name and Age, you can simple call self.request.get('name') and self.request.get('Age'). If the key is not found it will return an empty string by default.