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.

14 Responses to “Signing Amazon Web Service Requests in ActionScript”


  • Is this for Flex? I’m searching for some help on a as3 flash version without much luck.

  • Of course, it would be a terrible idea to distribute your secret key in a publicly visible swf. As such, any signing process should remain secret. This means that the signing should really happen server-side.

  • @de-lin: Yes, this is for Flex. However, you should be able to use the function as-is to generate the signature for your request. I believe you have access to the HttpService class from Flash as well.

    @spender: Yes and no. Yes, I agree that distributing the key in a pubicly visible SWF would be a bad idea. However, there are still cases where you might use client-side signing – in particular, I could see it as being reasonable for a Flex app deployed on a stand-alone kiosk. As with all technology, it’s the developer’s responsibility to decide what is or isn’t appropriate for their application.

  • I don’t believe your MXML example works on Flex 3. Have you tried it?

  • Yes, it works. However, there were some constants and variables that I omitted from the code in the name of brevity. However, given that those variables and constants are necessary to make it work, I have re-added them to the example code above.

  • Thanks very much for doing this! It worked well for me.

  • Hi Brendon,
    thanks a lot for your effort! It saved me many hours of coding and headaches.

    Just mentioning that for me the behaviour was rather erratic: it would work sometimes, and other times Amazon would say it doesn’t like the signature. By the way, I am using FLex SDK 4. So I investigated the issue a bit and I found that whenever Amazon said it didn’t like the signature, it contained special characters like + or =. So, for example, the signature I was sending it was 4+ay2iDrJqhmMqK58uKdidh7pLMNYFpAd0GuC4NWB+I= , but it was expecting 4%2Bay2iDrJqhmMqK58uKdidh7pLMNYFpAd0GuC4NWB%2BI%3D. What I did was to change the line “signature = encodeURI(encoder.toString());” into “signature = encodeURIComponent(encoder.toString());”, because encodeURIComponent encodes many more characters than encodeURI – and Amazon mention here http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/Query_QueryAuth.html, at the bottom of the page, that the signature itself needs to be encoded as per RFC3986. It workes perfectly now. And by the way, I looked at the SDK 3 documentation, and nothing changed in the functionality of the encodeURIComponent function, so I am surprised all of you guys didn’t have any problems.
    And just touching on that one, the ActionScript implementation of encodeURIComponent actually encodes fewer characters than Amazon would like. But it does seem to work fine, so it may be that those particular characters don’t appear in the signature at all. But you know better ;)

    Again, fantastic work! Thanks.
    Mihai

  • Mihal – thanks for the update to the code! I had used encodeURIComponent earlier in the code, but had failed to use it when generating the final signature. Whoops! I’ve updated the code above to reflect your suggestion.

  • Thanks you very much from France!

    I tried using the corelib:

    trace (HMAC.hash(key,data,SHA256));

    But it doesnt work ;-(

  • Tom – The problem probably isn’t your use of corelib. Amazon is pretty particular about the encoding of the data (see the use of encodeURIComponent in the code above), so I would guess that was the source of your problem.

  • Yep right, I cannot understand why Amazon decided to add this fu*** security feature! Now the web service to get albums art is (alsmot) more secure than a paypall service ;-)

  • Thanks a bunch for posting this code. It still works :) .

  • Are there licensing issues around use of your example code? We plan to use it in another example (reference) application. Please advise… and thanks for posting the code, it works!

  • Kirsten – the code is here for you to use as you see fit. Go crazy.

    I’ll post another version with a license bundle (probably MIT or BSD license) when I get the chance.

Leave a Reply