How to use the new Amazon AWS Signature in PHP

9 05 2009

I’ve just spent 3…no…4 hours messing about with this, until I finally got it to work.

The new Amazon Associates (or as it’s to be renamed, Amazon Product Advertising API) requires that by August all requests include a Signature.

Documentation here:

http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/rest-signature.html

Here’s how I implemented it in PHP (I had to make some changes to support multiple keywords, such as ‘dan aykroyd’. The space was causing huge problems after being urlencoded and decoded, parsed, hashed, etc).

define('AMAZON_ID','YOUR AWS DEVELOPER CODE');
define('SECRET_KEY','YOUR AWS SECRET KEY');
$keyword = "steve martin"; // The keywords you're searching for
$aws = array();
$keyword = urlencode($keyword);
$url = "http://webservices.amazon.com/onca/xml?Service=AWSECommerceService";
$url .= "&AWSAccessKeyId=".AMAZON_ID;
$url .= "&Operation=ItemSearch";
$url .= "&SearchIndex=DVD";
$url .= "&Keywords=".$keyword."";
$url .= "&ResponseGroup=Small,OfferFull,Images,Reviews,ItemAttributes,SalesRank";
$url .= "&Timestamp=".gmdate('Y-m-d\TH:i:s\Z');
$url = str_replace("+",urlencode("+"),$url);
$url_a = parse_url($url);
$url_a['query'] = str_replace(',',urlencode(','),$url_a['query']);
$url_a['query'] = str_replace(';',urlencode(':'),$url_a['query']);
parse_str($url_a['query'],$params);
uksort($params, 'strnatcmp');
$qstr = '';
foreach ($params as $key => $val) {
$qstr .= "=".rawurlencode($val);
}
$qstr = substr($qstr, 1);
$qstr = str_replace('%20',urlencode('+'),$qstr);
$qstr = str_replace(',',urlencode(','),$qstr);
$qstr = str_replace(';',urlencode(':'),$qstr);
$sig = "GET\n"
. "webservices.amazon.com\n"
. "/onca/xml\n"
. $qstr;
$sig = base64_encode(hash_hmac('sha256', $sig, SECRET_KEY, true));
$sig = str_replace('+','%2B',$sig);
$sig = str_replace('=','%3D',$sig);
$params['Signature'] = $sig;
$p = array();
foreach($params as $k=>$v) {
$p[]=$k."=".$v;
}
$qstr = implode("&",$p);
$rebuilt_url = $url_a['scheme']."://".$url_a['host'].$url_a['path']."?".$qstr;
$aws['url'] = $url."&Signature=".$sig;
$xml = file_get_contents($aws['url']);
echo '';
var_dump($xml);
echo '';

I know what you’re going to say, why didn’t I do it ‘this way’ or ‘that way’?

The problem is that you don’t just encrypt the query string and tack it onto the end of the URL as a Signature. You need to split the query string up, re-sort them, and then join them back together again first. And if the query doesn’t come out looking exactly the same as the original, then the Signature won’t validate. I tried dozens of other ways and in the end I had to go for the most longwinded and seemingly illogical method.

I *could* probably waste time finding a way to condense this and make it simpler, but I’ve already lost my entire night because of this. And at least I know it works.

Update: In the end I used a modified version of Ulrich Mierendorff’s code from here:
http://mierendo.com/software/aws_signed_query/
It works with all the queries I can throw at it, including searches with single quotes.
View it in action here:
http://www.bluesbrotherscentral.com/profiles/willie-big-eyes-smith/
(The Amazon box at the bottom of the page)
It looks like I was on the right track, if I had the time I would have fixed the bugs myself (but why reinvent the wheel?)


Actions

Information

8 responses

13 06 2009
Ulrich

Hi,
why are you using uksort($params, ’strnatcmp’);? Afaik ksort($params) would be better.

2 07 2009
bbcentral

That part of the code came from elsewhere, you could be right but I’m not changing my code now. It works fine for me.

23 07 2009
eddg67

What am I missing here ? When do u use $rebuilt_url ? Also why are u using an array to store $aws['url'] ?

23 07 2009
bbcentral

I probably didn’t need to include that line, I reuse it later on in my script for another reason. Same thing with the $aws array, it’s used later (I wrote my own custom class for all AWS requests).

I’m tweaking my original code right now, there’s a few types of queries that don’t work with it. I’ll try to update it sometime soon.

23 07 2009
eddg67

Thanks for the quick responsive. I am also trying to write a class to handle the new signature requirement where I was planning on including ur build signature method but have not got it too work yet. Thanks for posting this example to get me on the right path :) .
So if u can post the revisions I would really like to see how u handle rebuilding the query string for Operation= ItemLookup.

24 07 2009
eddg67

I noticed after trying to use your code sample that ur doing a str_replace after already generating the $sig which could cause the signature not to match.

$sig = base64_encode(hash_hmac(’sha256′, $sig, SECRET_KEY, true));
$sig = str_replace(‘+’,'%2B’,$sig);
$sig = str_replace(‘=’,'%3D’,$sig);

Thanks again for posting. It really helped me get my class working.

8 11 2009
mb

So what exactly is the output of this? Is it the same API output (XML format) from before August? Also, what if I wanted to use other parameters than you have listed? What is the order, or would I have to play with it?

9 11 2009
bbcentral

Everything is identical to the way it worked before August, you shouldn’t have to change anything else. The order shouldn’t matter, nor should adding other parameters. It’s basically lines 5-12 that you can change safely (don’t remove the timestamp parameter or else the code will break).

Hope that helps! :)

Leave a comment