Signing Amazon Web Service Requests in ActionScript

Amazon.com announced a change to its Product Advertising API requiring all requests after August 15, 2009 to be signed. I’d been meaning to update one of the Scannerfly example applications and the Shelfari Scanner to sign Amazon requests, but was hoping someone else would figure it out before me. As near as I can tell, no one has provided an implementation in ActionScript, so I cobbled one together.

Generally, the process is fairly straighforward: developers need to transform their REST request’s parameters into a canonical form, add a timestamp, and then append a signed version of the resulting string. However, in reality there a lots of finicky details that make the process frustrating…luckily for you, I’ve created an ActionScript implementation that you should be able to modify to suit your purposes.

The signature process relies on the as3crypto library to provide HMAC and SHA-256 implementations. In addition, you’ll need an instance of mx.rpc.http.HttpService that you’ve likely instantiated in your MXML, along with your Amazon Web Services developer ID and secret key:

// A HTTPService configured to perform the required Amazon Web Services request.
<mx:HTTPService id="AmazonSearch" url="http://webservices.amazon.com/onca/xml" showBusyCursor="true">
	<mx:request>
		<awsaccesskeyid>{amazonDeveloperId}</awsaccesskeyid>
		<idtype>EAN</idtype>
		<itemid>9781592400874</itemid>
		<operation>ItemLookup</operation>
		<responsegroup>ItemAttributes,Images,Tracks,EditorialReview</responsegroup>
		<searchindex>Books</searchindex>
		<service>AWSECommerceService</service>
		<signature>{signature}</signature>
		<timestamp>{timestamp}</timestamp>
	&lt;/mx:request&gt;
&lt;mx:HTTPService&gt;
 
// The Amazon host providing the Product API web service.
private const AWS_HOST:String = "webservices.amazon.com";
 
// The HTTP method used to send the request.
private const AWS_METHOD:String = "GET";
 
// The path to the Product API web service on the Amazon host.
private const AWS_PATH:String = "/onca/xml";
 
// The AWS Access Key ID to use when querying Amazon.com.
[Bindable]
private var amazonDeveloperId:String = "####################";
 
// The AWS Secret Key to use when querying Amazon.com.
[Bindable]
private var amazonSecretAccessKey:String = "####################";
 
// The request signature string.
[Bindable]
private var signature:String;
 
// The request timestamp string, in UTC format (YYYY-MM-DDThh:mm:ssZ).
[Bindable]
private var timestamp:String;
 
private function generateSignature():void
{
	var parameterArray:Array = new Array();
	var parameterCollection:ArrayCollection = new ArrayCollection();
	var parameterString:String = "";
	var sort:Sort = new Sort();
	var hmac:HMAC = new HMAC(new SHA256());
	var requestBytes:ByteArray = new ByteArray();
	var keyBytes:ByteArray = new ByteArray();
	var hmacBytes:ByteArray;
	var encoder:Base64Encoder = new Base64Encoder();
	var formatter:DateFormatter = new DateFormatter();
	var now:Date = new Date();
 
 
	// Set the request timestamp using the format: YYYY-MM-DDThh:mm:ss.000Z
	// Note that we must convert to GMT.
	formatter.formatString = "YYYY-MM-DDTHH:NN:SS.000Z";
	now.setTime(now.getTime() + (now.getTimezoneOffset() * 60 * 1000));
	timestamp = formatter.format(now);
 
	// Process the parameters.
	for (var key:String in AmazonSearch.request )
	{
		// Ignore the "Signature" request parameter.
		if (key != "Signature")
		{
			var urlEncodedKey:String = encodeURIComponent(decodeURIComponent(key));
			var parameterBytes:ByteArray = new ByteArray();
			var valueBytes:ByteArray = new ByteArray();
			var value:String = AmazonSearch.request[key];
			var urlEncodedValue:String = encodeURIComponent(decodeURIComponent(value.replace(/\+/g, "%20")));
 
 
			// Use the byte values, not the string values.
			parameterBytes.writeUTFBytes(urlEncodedKey);
			valueBytes.writeUTFBytes(urlEncodedValue);
			parameterCollection.addItem( { parameter : parameterBytes , value : valueBytes } );
		}
	}
 
	// Sort the parameters and formulate the parameter string to be signed.
	parameterCollection.sort = sort;
	sort.fields = [ new SortField("parameter", true), new SortField("value", true) ];
	parameterCollection.refresh();
	parameterString = AWS_METHOD + "\n" + AWS_HOST + "\n" + AWS_PATH + "\n";
	for (var i:Number = 0; i < parameterCollection.length; i++)
	{
		var pair:Object = parameterCollection.getItemAt(i);
 
 
		parameterString += pair.parameter + "=" + pair.value;
 
		if (i < parameterCollection.length - 1)
			parameterString += "&";
	}
 
	// Sign the parameter string to generate the request signature.
	requestBytes.writeUTFBytes(parameterString);
	keyBytes.writeUTFBytes(amazonSecretAccessKey);
	hmacBytes = hmac.compute(keyBytes, requestBytes);
	encoder.encodeBytes(hmacBytes);
	signature = encodeURIComponent(encoder.toString());
}
 
...
 
// Somewhere in your code you'll call the following to generate request signature and perform the search.
generateSignature();
AmazonSearch.send();

And for those who need a complete working example, you can download the MXML for an example application from here. The example simply performs an ItemLookup; however, you will still need to add your Amazon developer ID and secret key for the example to work.

A note to other developers struggling with implementing the proper request signature, see the Signed Request Helper. This Javascript application breaks down each step in the formulating the normalized parameter string and signature.

Borders Keep Out Innovation Too

Since returning from living in Silicon Valley, I’ve been in the grips of something my father calls “expatriate blues” – it involves looking around at my new environment and critiquing what’s different, and not always in a positive manner. This is a natural response whenever you move countries – I’ve lived in five countries over the past dozen years, so this par for the course. So, without further ado, allow me to expound on my latest “expatriate blues”-derived pet peeve: the border.

It’s not that I’m against the border per se. Borders exist for a couple good reasons – ensuring the government’s ability to enforce laws, collect taxes, and keep out undesirables (although that seems to be increasingly interpreted as “primarily poor, unskilled immigrants”). That stuff is all good. Unfortunately, as technology becomes increasingly content-rich and network-dependent, we’re seeing a new generation of technologies that get tripped up at the border in the intricacies of international copyright law and licensing schemes.

I’m not the first one to lament the technologies not available in Canada from our neighbor to the southHulu, Mint, Netflix, an Amazon.com that doesn’t suck, hellooo? But this is nothing new for Canada – remember the iTunes store? Introduced in the US in April of 2003 and shortly thereafter in Canada in…December 2004. The streak continued with the iPhone (introduced in June 2007 in the US, July 2008 in Canada), and continues to this day with the continued unavailability of Amazon.com’s Kindle device. And, in case you didn’t realize, that last one also includes the Kindle iPhone App – it’s not available in Canada, which seems silly for a software application that can download books over wireless Internet.

In a world of technology that’s supposed enhance our capabilities and remove barriers to markets, it appears the last true barrier to market is still firmly in place. But why should we care? After all, all the innovations I’ve listed could be easily dismissed as trivial entertainment products. It’s not like we’re not getting access to lifesaving medical devices, right?

Wrong – that’s an incomplete and flawed argument. While it’s true these devices may appear to be frivolous, the future often arrives in unexpected forms. The real reason to be concerned about the unavailability of these services in Canada is that they impede Canada’s ability to innovate and to exploit new markets. In some cases, it is possible for engineers and others to work around the border – a US credit card here, a Washington state drop box there, a US IP address proxy service somewhere else – but these are all hacks. These hacks introduce friction, friction which slow down the system and places the country at a disadvantage to gain momentum.

If you’re a fan of Malcolm Gladwell, some of this argument might sound familiar. In his latest book, Outliers, Gladwell notes that many of the hockey players in the NHL are born between January 2nd, and the end of April. The reason? The cutoff dates for age brackets in the minor leagues is January 1st – meaning that anyone born on January 2nd ends up in a lower age bracket. These players’ age advantage translates into superior motor control and size – characteristics that predispose the players to appear more capable than others in their bracket. This means these players get picked for special coaching, which kicks off a virtuous feedback cycle. The players get better, and because they appear to be better players, they get more coaching, which makes them even better players. And so on.

The same is true for innovation. Without access to these products, Canada is placed at a disadvantage, just like a player born on January 1st. Smallest of the litter, slowest of the pack. Not only do our entrepreneurs not get the opportunity to build on top of these platforms, but our own companies are not forced to compete in the global market. This not only means that Canadians don’t get access to better products, but also that Canadians don’t learn how to build better products themselves for export to the world.

Canada needs to move quickly to remove this barrier, or we may end up finding ourselves out of much more than the latest shiny toy.