I need to translate some existing PHP code to Python. This job connects to gravity forms and queries for certain data. In order to make the query, a signature must be calculated in order to verify the connection.
The Gravity Forms web api gives good PHP directions here.
The PHP method is as follows:
function calculate_signature( $string, $private_key ) {
$hash = hash_hmac( 'sha1', $string, $private_key, true );
$sig = rawurlencode( base64_encode( $hash ) );
return $sig;
}
Based on my understanding of Python and the information about hash-hmac and rawurlencoded from php2python.com, I wrote the following:
import hmac, hashlib, urllib, base64
def calculate_signature(string, private_key):
hash_var = hmac.new(private_key, string, hashlib.sha1).digest()
sig = urllib.quote(base64.b64encode(hash_var))
return sig
However, the two signatures are not equivalent, and thus Gravity Forms returns a HTTP 403: Bad Request response.
Am I missing something within my translation?
Update (11/04/15)
I have now matched my php and python urls. However, I still receive a 403 error.
The reason the php and python signatures did not match had nothing to do with their calculate_signature() methods.
The issue was caused by differing expires variables. Php used strtotime("+60 mins") which resulted in a UTC time 60 minutes from now. Whereas Python used datetime.date.now() + timedelta(minutes=60). This is also 60 minutes from now, but in your current timezone.
I always want to calculate the expire variable in UTC so I replaced my Python calculation with datetime.datetime.utcnow() + timedelta(minutes=60).
You're almost there. urllib.quote does not encode slashes, for example, as PHP's rawurlencode does. You can use urllib.quote_plus to achieve the desired effect:
import hmac, hashlib, urllib, base64
def calculate_signature(string, private_key):
hash_var = hmac.new(private_key, string, hashlib.sha1).digest()
sig = urllib.quote_plus(base64.b64encode(hash_var))
return sig
Related
Hi shopify guru's I know this question has come around a thousand times, I know this because I have read every single thread I could find.
My app verificaton was working fine, but now that I switched to embedded app, I can't seem to verify the hmac.
//Remove hmac from hash comparison
$hmac = $data['hmac'];
unset($data['hmac']);
//sort the values alphabetically
ksort($data);
$data = urldecode(http_build_query($data));
$hash = hash_hmac('sha256', $data, $this->ci->get('settings')['shopify']['api_secret']);
This code will keep returning a different hash from the hmac shopify sends me, I'm guessing there is a problem with encoding or escaping, I have tried every thing I could think of (htmlspecialchars, urldecode, strreplace, doublecheck secret etc..)
the string looks like this after the urldecode line:
locale=en&protocol=https://&shop=mystorehandle.myshopify.com×tamp=1539901099
Any help would be appreciated, I wanted to get some work done on my app, but the past 3 hours have been filled with trying to get my hmac in sync with shopify's :(
I'm quiet certain I'm doing it right, but have no clue as to why it won't work then.
I'm buying a pie for whoever knows the answer first
.
Ok so... don't ask me how or why this works, but it does. After 2-3 hours on this, I took a complete stab in the dark, and it worked. I'm pretty sure it only works because there is a bug on the Shopify side.
My embedded app was being fed GET params hmac/shop/timestamp/protocol/locale in that order.
For some reason, building a query string of shop=[myshop]×tamp=[timestamp] worked.
In other words, I removed hmac, but ALSO protocol and locale.
Using this code though, BROKE the TEST version of my app, which was actually working fine with protocol and locale included.
My only conclusion here is that the hmac is derived from the GET parameters following hmac, which are in alphabetical order, stopping when the following parameter is not in that order.
So if GET params are hmac/shop/timestamp/protocol/locale - generate your hash string using shop & timestamp.
If GET params are hmac/locale/protocol/shop/timestamp - generate your hash string using locale, protocol, shop & timestamp.
So strange. Would be really interested to know if this works for you also!
Here's my code:
parse_str($_SERVER['QUERY_STRING'], $queryStringArray);
$providedHmac = $_GET['hmac'];
unset($queryStringArray['hmac']);
$amp = '';
$i = 0;
foreach($queryStringArray as $key => $value)
{
$keyFirstLetter = substr($key, 0, 1);
if($i == 0 || $keyFirstLetter > $lastKeyFirstLetter)
{
$newQueryString .= $amp . $key . '=' . $value;
$amp = '&';
}
$lastKeyFirstLetter = $keyFirstLetter;
$i++;
}
$calculatedHmac = hash_hmac('sha256', $newQueryString, SHOPIFY_APP_SHARED_SECRET);
$hmacValid = false;
if($calculatedHmac == $providedHmac)
{
$hmacValid = true;
}
So I am working on a PHP script that queries an API which uses HMAC authentication headers. However, I have been banging my head trying to encode the HMAC signature correctly. I have a preexisting nodejs script to work from as a template.
In the nodejs script, the HMAC signature is calculated using the following:
var crypto = require('crypto');
var hmac = [];
hmac.secret = 'ODc0YTM3YzUxODFlMWQ1YTdhMGQwY2NiZmE1N2Y1ODdjYzM5NTgyMDJhZjVkYTE4MmQxYzQ5ODk0M2QzNWQxYw==';
hmac.timestamp = 1457326475000;
hmac.path = '/account/';
hmac.message = hmac.path +'\n' + hmac.timestamp;
var sig = crypto.createHmac('sha512', new Buffer(hmac.secret, 'base64'));
hmac.signature = sig.update(hmac.message).digest('base64');
console.log(hmac);
This correctly calculates the HMAC signature as:
bWjIFFtFmWnj0+xHLW2uWVa6M6DpbIV81uyUWwRFCJUg+0Xyt40QWZWQjGvfPUB/JbjGZHUoso0Qv5JHMYEv3A==.
Meanwhile, in PHP, I am using:
<?php
$hmac['secret'] = 'ODc0YTM3YzUxODFlMWQ1YTdhMGQwY2NiZmE1N2Y1ODdjYzM5NTgyMDJhZjVkYTE4MmQxYzQ5ODk0M2QzNWQxYw==';
$hmac['nonce'] = '1457326475000';
$hmac['path'] = '/account/';
$hmac['message'] = $hmac['path']."\n".$hmac['nonce'] ;
$hmac['signature'] = base64_encode(hash_hmac('sha512',$hmac['message'],
$hmac['secret'], true));
print_r($hmac);
The above code, will calculate the HMAC signature as:
vqP49m/bk9nA4S3nMqW2r+kc2+yBfwhY/jWGUfz6dlKJUMkC2ktiPnuCcymdSWl4XezZT5VKCATYfus86Hz/Gg==
Working from the principle that "one million monkeys hacking away at a million keyboards" might one day be able to encode a valid HMAC signature, I have even tested a loop that iterates through all the permutations of the above PHP code (with/without base64 encoding the message, secret; with/without binary encoding of the HMAC, etc.)... to no avail.
Any suggestions for this here, one exhausted simian?
The problem is that you're not decoding your $hmac['secret'] first before passing it to hash_hmac().
Try:
$hmac['secret'] = base64_decode($hmac['secret']);
$hmac['signature'] = base64_encode(
hash_hmac('sha512', $hmac['message'], $hmac['secret'], true)
);
I am playing around with signatures for request on the iPhone.
I have a simple PHP script running that will verify incoming JSON data against the signature in the header (all in all it is very similar to two-legged OAuth).
The following Obj-C code generates the signature:
const char *cKey = [kConsumerSecret cStringUsingEncoding:NSUTF8StringEncoding];
const char *cData = [payload cStringUsingEncoding:NSUTF8StringEncoding];
NSLog(#"Payload: %s", cData);
unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
NSData *hmac = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
NSString *signature = [hmac base64EncodedString];
And the verifying PHP code looks like this:
$originalPayload = '{"name":"Ben’s iPhone"}'; // The message received from iOS
$hash = hash_hmac("sha256", $originalPayload, "Secret Key", TRUE);
$signature = strtr(base64_encode($hash), array('=' => '', '+' => '-', '/' => '_'));
The functions work perfectly fine when using ASCII characters. But when they contain UTF-8 characteres iOS calculates the signature based on:
Ben’s iPhone
While PHP calculates it based on:
Ben\u2019s iPhone
Making the signatures invalid. I have tried multiple approaches and none of them have yielded any results. Does anyone have an idea for how to solve this issue?
Remark: Yes, I could simply use OAuth, but I'm just curious on how to implement it myself.
Update: I have tracked down the problem to a part in the code, that modifies the parsed JSON array in the PHP code. In order to hash, but not include it in the body, I added the temporary index 'time' that contained a UTC timestamp. That modification of the original array made PHP change the character representation (escaping UTF-8 characters). Just appending the timestamp work fine.
Nonetheless the functions should yield the same result, shouldn't they?
I have a torrent hash from the magnet link. For example: fda164e7af470f83ea699a529845a9353cc26576
When I try to get information about leechers and peers I should request: http://tracker.publicbt.com/scrape?info_hash=???
How should I convert info hash for this request? Is it url encoding or becoding? how? In PHP.
It's a raw hexadecimal representation. Use pack() with H to convert it. Then URL encode it.
Got this python snippet from a colleague,
r = ''
s = 'fda164e7af470f83ea699a529845a9353cc26576'
for n in range(0, len(s), 2):
r += '%%%s' % s[n:n+2].upper()
print r
Output: %FD%A1%64%E7%AF%47%0F%83%EA%69%9A%52%98%45%A9%35%3C%C2%65%76
Works like a charm.
Edit: Works like a charm for getting the tracker to give back status 200 (ok) but still doesn't work for retrieving the torrent details...
In case someone is having trouble and comes across this thread in the future: the trick to this whole issue is to use the bool $raw_output argument of the PHP: sha1 function, setting it to "true".
The BDecode/DEncode classes can be found HERE. This project, called Trackon, also includes many other helpful classes to interact with torrent trackers and files.
So, in PHP, something like this will work to obtain the correct info hash for scraping the tracker for details:
include('./path/to/BDecode.php');
include('./path/to/BEncode.php');
function getHash($torFile){
$tfile = BDecode(file_get_contents($torFile));
$infohash = sha1(BEncode($tfile["info"]), TRUE);
return urlencode($infohash);
}
Then merely call it like so:
$hash = getHash('./path/to/.torrent');
Hope this helps someone out there. I was still scratching my head after reading many posts about how to obtain the correct info hash. I understand why this wasn't mentioned anywhere now though, this argument was added in PHP 5. If you're not running PHP 5, you will have to convert the sha1 hash to raw binary after you calculate it.
I'm trying to setup some Google Maps Premier API action, and to do so, I need to sign my URLs to authenticate. If you go down to Signature examples, there is some Python, C# and Java code to show you how to do the signature via HMAC-SHA1. There is also an example so that I can to test my PHP implementation. However, I just can't seem to get it to work.
Here's my code:
$key = "vNIXE0xscrmjlyV-12Nj_BvUPaw=";
$data = "/maps/api/geocode/json?address=New+York&sensor=false&client=clientID";
$my_sign = hash_hmac("sha1", $data, base64_decode($key));
$my_sign = base64_encode($my_sign);
$valid_sign = "KrU1TzVQM7Ur0i8i7K3huiw3MsA=";
When, I run this, I get a signature of:
ZDRlNGMwZjIyMTA1MWM1Zjk0Nzc4M2NkYjlmNDQzNDBkYzk4NDI4Zg==
Which totally doesn't match.
Things I have thought about:
The key is in Modified URL encoded format, so changing - and _ to + and / also doesn't work
The Python example code does indeed work, so this is a valid example.
Completely rewriting our code-base in python instead of PHP (I inherited it).
You have 2 problems at least,
The Google uses special URL-safe Base64. Normal base64_decode doesn't work.
You need to generate the SHA1 in binary.
Try this,
$key = "vNIXE0xscrmjlyV-12Nj_BvUPaw=";
$data = "/maps/api/geocode/json?address=New+York&sensor=false&client=clientID";
$my_sign = hash_hmac("sha1", $data, base64_decode(strtr($key, '-_', '+/')), true);
$my_sign = strtr(base64_encode($my_sign), '+/', '-_');
A php example is available at http://gmaps-samples.googlecode.com/svn/trunk/urlsigning/UrlSigner.php-source
I assume your trying to sign the url for OAuth?
Try out this library: http://code.google.com/p/oauth/