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¶m2=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
- hmac library documentation http://docs.python.org/library/hmac.html
- base64 library documentation http://docs.python.org/library/base64.html
- hashlib library documentation http://docs.python.org/library/hashlib.html
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
- hash_hmac documentation http://us3.php.net/manual/en/function.hash-hmac.php
- base64_encode documentation http://us3.php.net/base64_encode
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
- AWS Community Forums Thread (where most of this code comes from): http://developer.amazonwebservices.com/connect/thread.jspa?threadID=33204&start=0&tstart=0
- Base64 Encoder/Decoder http://www.source-code.biz/snippets/vbasic/12.htm
- CSHA256 Class http://www.freevbcode.com/ShowCode.asp?ID=2565
- HexString2Bin Function http://www.experts-exchange.com/Programming/Programming_Languages/Visual_Basic/Q_21330788.html
- Zip Archive of everything you need Amazon_request_signing.zip
Other Amazon Resources
- Amazon Product Advertising API FAQ https://affiliate-program.amazon.com/gp/advertising/api/detail/faq.html
- Request signing sandbox http://associates-amazon.s3.amazonaws.com/signed-requests/helper/index.html
- Product Advertising API Documentation http://docs.amazonwebservices.com/AWSECommerceService/2009-07-01/DG/
There you have it. Amazon Product Advertising API request signing in three languages: Python, PHP, and Visual Basic 6. Enjoy!
-David