Signing Amazon REST Requests with SHA-256 in Swift

Accessing an Amazon REST API requires an encrypted request call, encoded with your secret key.

We’ll walk through an example with all the code you need to make it work.

Take this ItemLookup request of Amazon’s Product Advertising API:

http://webservices.amazon.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE
&Operation=ItemLookup&ItemId=0679722769&ResponseGroup=
ItemAttributes,Offers,Images,Reviews&Version=2013-08-01

Breaking it down we have the following parameters:

  • Service = AWSECommerceService
  • AWSAccessKeyId = AKIAIOSFODNN7EXAMPLE
  • Operation = ItemLookup
  • ItemId = 0679722769
  • ResponseGroup = ItemAttributes,Offers,Images,Reviews
  • Version = 2013-08-01
  • Timestamp = 2014-08-18T12:00:00Z

Step 1

Re-ordering all parameters by their key in order of ASCII character weight. This is essentially alphabetically, however all capitalized letters come before any lowercase letters.

Placing your parameters into an Array of Strings, the following snippet will reorder them correctly:

func utf8StringCompare(s1: String, s2: String) -> Bool {
    let u1 = s1.utf8
    let u2 = s2.utf8
    for (x, y) in zip(u1, u2) {
        if x < y { return true }
        if x > y { return false }
    }
    return u1.count < u2.count
}

let sortedParameterArray = unsortedParameterArray.sorted(by: utf8StringCompare)

Step 2

Rejoin the array of parameters with ampersands into one canonical string:

canonicalString = sortedParameterArray.joined(separator: "&")

Prepend the following three lines (with line breaks) before the canonical string:

let stringToSign = [
    "GET",
    "webservices.amazon.com",
    "/onca/xml",
    canonicalString
    ].joined(separator: "\n")

Step 3

This string must now be encoded into an RFC 2104-compliant HMAC with the SHA256 hash algorithm, and then used to sign our request.

For the signing process, we’ll use CryptoSwift, a pure swift library containing many of the standard and secure cryptographic algorithms we’ll require when using APIs. Install CryptoSwift into your project using their documentation.

Step 4

Now that we have the CryptoSwift framework, we can create a class to use it. Our class’s main function will take the string from above, along with our Amazon Secret Key and return a signed 64Base value.

import Foundation
import CryptoSwift

class URLRequestSigner: NSObject {

  func signString(stringToSign: String, secretSigningKey: String) -> String? {
    guard let signature = try? HMAC(key: [UInt8](secretSigningKey.utf8),
    variant: .sha256).authenticate([UInt8](stringToSign.utf8))
        else { return .none }
    return signature.toBase64()
  }
}

//Call like this:
let signedString = URLRequestSigner().signString(stringToSign: stringToSign,
secretSigningKey: secretAccessKey)

Step 5

Add the signature to your request, and the result is a properly-formatted signed request:

http://webservices.amazon.com/onca/xml? AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE &AssociateTag=mytag-20 &ItemId=0679722769 &Operation=ItemLookup &ResponseGroup=Images%2CItemAttributes%2COffers%2CReviews &Service=AWSECommerceService &Timestamp=2014-08-18T12%3A00%3A00Z &Version=2013-08-01 &Signature=j7bZM0LXZ9eXeZruTqWm2DIvDYVUU3wxPPpp%2BiXxzQc%3D

Step 6

We can now perform a request as usual:

let url = completeURLWithSignature
var request = URLRequest(url: url)
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")

let task = session.dataTask(with: request, completionHandler: { (data: Data?, response:
URLResponse?, error: Error?) -> Void in
  print(data)
  print(response)
  print(error)
})
task.resume()
session.finishTasksAndInvalidate()

Congratulations you can now sign Amazon REST API requests!