Amazon Products API Request Signing


Anyone using the Amazon Product Advertising API (previously known as the Amazon Associates Web Service) should be aware that on August 15th Amazon will start requiring all incoming requests to be signed in accordance with their API documentation. Subscribers to the service should have received an email from Amazon, but if not let this serve as a PSA as well as a tutorial. So far we have implemented solutions for a handful of clients using a variety of languages, and I wanted to share our results with the hope that it will save some headaches.

In this tutorial, we will cover signing requests using Python, PHP, and Visual Basic. If you look at the Product Advertising API documentation (or any of Amazon’s APIs really), you will see the details for signing REST requests. The process is straightforward, and more or less easy to implement.

  • Add an ISO 8601 timestamp (in GMT) as the Timestamp parameters
  • Sort all of the URL params
  • Construct a string to sign
  • Compute an HMAC signature using the SHA256 hash algorithm
  • Base64 encode the resulting signature and set it as the Signature parameter

General Disclaimer

The following code snippets are not meant to be ingredients in copy-pasta, they are meant to illustrate the procedure for signing Amazon API requests and show users the appropriate functions/libraries/resources for doing so. None of the following code will run as-is.

Since the Python implementation is by far the easiest and most readable, it will be useful to demonstrate what is going on.

Python using built-in libraries: hmac, hashlib, and base64

import base64,hashlib,hmac,time
from urllib import urlencode
 
AWS_ACCESS_KEY_ID = "Your Access Key ID"
AWS_SECRET_ACCESS_KEY = "Your Secret Key"
 
base_url = "http://ecs.amazonaws.com/onca/xml"
url_params = {'Operation':"ItemSearch",'Service':"AWSECommerceService",
 'AWSAccessKeyId':AWS_ACCESS_KEY_ID,'AssociateTag':"yourtag-10",
 'Version':"2006-09-11",'Availability':"Available",'Condition':"All",
 'ItemPage':"1",'ResponseGroup':"Images,ItemAttributes,EditorialReview",
 'Keywords':"Amazon"}
 
# Add a ISO 8601 compliant timestamp (in GMT)
url_params['Timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())
 
# Sort the URL parameters by key
keys = url_params.keys()
keys.sort()
# Get the values in the same order of the sorted keys
values = map(url_params.get, keys)
 
# Reconstruct the URL paramters and encode them
url_string = urlencode( zip(keys,values) )
url_string = url_string.replace('+'," ") 
url_string = url_string.replace(':',":") 
 
#Construct the string to sign
string_to_sign = """GET
ecs.amazonaws.com
/onca/xml
%s""" % url_string
 
# Sign the request
signature = hmac.new(
    key=AWS_SECRET_ACCESS_KEY,
    msg=string_to_sign,
    digestmod=hashlib.sha256).digest()
 
# Base64 encode the signature
signature = base64.encodestring( signature )
 
# Make the signature URL safe
signature = signature.replace('+','+')
signature = signature.replace('=','=')
url_string += "&Signature;=%s" % signature
print "%s?%s" % (base_url,url_string)

A few quick notes before moving on to the other examples. The timestamp here follows the ISO 8601 standard. This standard does not require milliseconds or the terminating Z, but I have seen examples with and without each of these. I believe all of the following timestamps would be valid:

  • 2009-07-25T07:31Z
  • 2009-07-25T07:31:00Z
  • 2009-07-25T07:31:00.00000Z
  • 2009-07-25T07:31
  • 2009-07-25T07:31:00
  • 2009-07-25T07:31:00.00000

That said, the time format in the above example is known to work (we use it in production code, and I believe it’s the same time format used by Boto). The other issue I’d like to address is constructing the “string to sign”. This is a string which describes the request in a standardized way. The general format is:

VERB
sub.domain.com
/api/path
param1=value1&param2=value2&...

The VERB is one of GET, PUT, DELETE, or POST (the HTTP REST verbs). Since the Products API is read-only, we will only be working with the GET verb. The base of the “string to sign” in the above example will be consistent for all Product Advertising API calls.
GET
ecs.amazonaws.com
/onca/xml


Python Resources


PHP using built-in functions: hash_hmac, base64_encode

The PHP implementation is also pretty straightforward. I have seen some implementations that rely on additional packages install with Pear/Pecl, but I do not see the reasoning behind going outside of the standard library of functions.

$AWS_ACCESS_KEY_ID = "Your Access Key ID";
$AWS_SECRET_ACCESS_KEY = "Your Secret Key";
 
$base_url = "http://ecs.amazonaws.com/onca/xml";
$url_params = array('Operation'=>"ItemSearch",'Service'=>"AWSECommerceService",
 'AWSAccessKeyId'=>$AWS_ACCESS_KEY_ID,'AssociateTag'=>"yourtag-10",
 'Version'=>"2006-09-11",'Availability'=>"Available",'Condition'=>"All",
 'ItemPage'=>"1",'ResponseGroup'=>"Images,ItemAttributes,EditorialReview",
 'Keywords'=>"Amazon");
 
// Add the Timestamp
$url_params['Timestamp'] = gmdate("Y-m-d\TH:i:s.\\0\\0\\0\\Z", time());
 
// Sort the URL parameters
$url_parts = array();
foreach(array_keys($url_params) as $key)
    $url_parts[] = $key."=".$url_params[$key];
sort($url_parts);
 
// Construct the string to sign
$string_to_sign = "GET\necs.amazonaws.com\n/onca/xml\n".implode("&",$url_parts);
$string_to_sign = str_replace('+','%20',$string_to_sign);
$string_to_sign = str_replace(':','%3A',$string_to_sign);
$string_to_sign = str_replace(';',urlencode(';'),$string_to_sign);
 
// Sign the request
$signature = hash_hmac("sha256",$string_to_sign,$AWS_SECRET_ACCESS_KEY,TRUE);
 
// Base64 encode the signature and make it URL safe
$signature = base64_encode(signature);
$signature = str_replace('+','%2B',$signature);
$signature = str_replace('=','%3D',$signature);
 
$url_string = implode("&",$url_parts);
$url = $base_url.$url_string."&Signature=".$signature;
print $url;

PHP Resources


Visual Basic 6 *sigh*

This was one a beast. I’m not a VB expert, and never hope to be, but I can make my way around well enough. Like most VB developers, I spent hours scouring forums, reading expert sex change, and banging my head on my desk. The resulting code is far to verbose to post here, but I will provide an archive of the files I came up with along with a brief description.

Starting with the simplest, I made a Class Module called TimeStamp (here is the CLS file). This was the fastest/easiest way I could figure out how to get an ISO 8601 timestamp. I imagine it could be done more elegantly, but that wasn’t my goal.

Next are two files I found and imported with minimal modification: Base64 and CSHA256 (BAS file and CLS file, respectively). The former is a Base64 encode/decoder and the latter is a pure VB6 implementation of the SHA-256 hashing algorithm. Without these two files, none of this is remotely possible - mad props (and condolences) to the person who wrote CSHA256.

The CSHA256 file does require one modification. In the ConvertToWordArray function change AscB to Asc

lByte = AscB(Mid(sMessage, lByteCount + 1, 1))
lByte = Asc(Mid(sMessage, lByteCount + 1, 1))

The final file glues it all together: HMAC_SHA256 (dunno why I named it that). Here is the BAS file. The only function you need to worry about in here is signEncode. Here is how it is used:

Dim URL As String
Dim AWSAccessKeyId As String
Dim AWSSecretAccessKey As String
Dim ts As TimeStamp
Set AWSAccessKeyId = "Your AWS Access Key Id"
Set AWSSecretAccessKey = "Your AWS Secret Access Key"
Set ts = New TimeStamp
URL = "http://ecs.amazonaws.com/onca/xml?Service=AWSEcommerceService" &_ 
    "&Timestamp=" & ts.getIsoTimestamp() & "&Operation=ItemSearch&" & _
    "&Service=AWSECommerceService&AWSAccessKeyId=" & AWSAccessKeyId & _
    "&AssociateTag=yourtag-10&Version=2006-09-11&Availability=Available" & _
    "&Condition=All&ItemPage=1&ResponseGroup=Images,ItemAttributes,EditorialReview" & _
    "&Keywords=Amazon"
URL = signEncode(URL)
MsgBox URL

The signEncode function does pretty much everything. It first separates the base URL from the request parameters, then sorts the parameters and calculates the signature. I had to fiddle around with it for a while before I found an issue with the Base64 encoding. I can’t take any credit for any of this really since I just gathered all the pieces and put them together. However, I hope this compilation of files and explanation will be useful.


Visual Basic Resources


Other Amazon Resources


There you have it. Amazon Product Advertising API request signing in three languages: Python, PHP, and Visual Basic 6. Enjoy!

-David

david on 07/25/2009
Copyright ©2009. Loot whatever you like as long you're not a Rogue.