Trying to digitally sign via HMAC-SHA1 with PHP - php

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/

Related

shopify embedded app hmac verification failed php

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&timestamp=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]&timestamp=[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;
}

PHP -- calculating correct HMAC signature as nodejs script

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)
);

Gravity Forms Signature - From PHP to Python

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

Something missing in iPhone/PHP encryption/decryption

I have been trying to implement some encryption between an iPhone app and a PHP web service. It's not working however. It seems like the first half of the text is NOT decrypted while the second half is decrypted just fine. What should I do?
The PHP encryption method is as follows:
function decrypt($str, $iv) {
$iv .= "00000000";
$str = base64_decode($str);
return self::decrypt_data($str, $iv, self::secret_key);
}
The iPhone stuff that encrypts the text uses a CryptoHelper class like this:
NSString *encrypted = [[CryptoHelper sharedInstance] encryptString:dataString];
The CryptoHelper class can be seen at http://pastie.org/1267796.
Try a simple example where you send a known Base64 encoded string from the iPhone app to PHP.
Compare the known valid string to what PHP is getting. I know recently, when trying to do an Ajax post from a script to PHP, we were having trouble with some characters (specifically +) being converted to spaces by PHP because it was doing a URL decode automatically. We had to switch all + to their % (URL-encoded %2B) equivalent. This fixed the problem for us.

How to convert torrent info hash for scrape?

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.

Categories