apirequestsignatureamazon-product-api

Amazon product lookup signature mismatch


This is the response I'm getting: The request signature we calculated does not match the signature you provided.

Here is the code I'm using to generate the signature:

static byte[] HmacSHA256(String data, byte[] key)
{
    String algorithm = "HmacSHA256";
    KeyedHashAlgorithm kha = KeyedHashAlgorithm.Create(algorithm);
    kha.Key = key;

    return kha.ComputeHash(Encoding.UTF8.GetBytes(data));
}

static byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName)
{
    byte[] kSecret = Encoding.UTF8.GetBytes(("AWS4" + key).ToCharArray());
    byte[] kDate = HmacSHA256(dateStamp, kSecret);
    byte[] kRegion = HmacSHA256(regionName, kDate);
    byte[] kService = HmacSHA256(serviceName, kRegion);
    byte[] kSigning = HmacSHA256("aws4_request", kService);

    return kSigning;
}

When I use Amazon's test settings, I get the correct signature

key = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
dateStamp = '20120215'
regionName = 'us-east-1'
serviceName = 'iam'

However, when I put my live settings in, I get the not matching error.

Here is what I'm using to get my signature:

var reqSig = getSignatureKey("[my secret key]", dateStamp, "us-west-2","AWSECommerceService");

This is what I'm submitting (I'm just testing it in a browser, for now):

ecs.amazonaws.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=[my
access key that corresponds with the secret
key]&Operation=ItemLookup&IdType=UPC&ItemId=635753490879&Timestamp=2019-01-24T19:14:55.2644840Z&Signature=32BA07ECE67F3177BF2EA02923E624D612A45FAA144ED0E43BDDC0DF6574EAC3

I'm not sure if it has to do with the region -- us-west-2 in my case -- because there is no place on the request for a region. I'm not sure how Amazon can test my signature against my parameters if I can't specify what region it is and I've used the region to calculate the signature.

What am I missing?


Solution

  • Ok. After spending days going through this, here is what I had to do:

    /*
    
        DOCUMENTATION: https://docs.aws.amazon.com/AWSECommerceService/latest/DG/rest-signature.html#rest_detailedexample
    */
    
        var itemID = "0679722769";
        var accessKeyID = "AKIAIOSFODNN7EXAMPLE";
        var timeStamp = DateTime.UtcNow.ToString("o");
        var req = $"Service=AWSECommerceService&AWSAccessKeyId={accessKeyID}&Operation=ItemLookup&IdType=UPC&ItemId={itemID}&Version=2013-08-01&Timestamp={timeStamp}";
        req = req.Replace(":", "%3A").Replace(",", "%2C"); //UrlDecode certain characters
        var reqlist = req.Split('&').ToArray(); //we need to sort our key/value pairs
        Array.Sort(reqlist);
        req = String.Join("&", reqlist); //join everything back
        var reqToSign = $@"GET
    webservices.amazon.com
    /onca/xml
    {req}".Replace("\r", ""); //create the request for signing. We need to replace microsofts's crlf with just a lf; Make sure there are no leading spaces after the linefeeds.
    
        var signage = getSignatureKey("1234567890",reqToSign);
        req = $"http://webservices.amazon.com/onca/xml?{req}&Signature={signage}"; //create our request with the signature appended.
        return req;
    }
    
    private static byte[] HmacSHA256(String data, byte[] key)
    {
        String algorithm = "HmacSHA256";
        KeyedHashAlgorithm kha = KeyedHashAlgorithm.Create(algorithm);
        kha.Key = key;
    
        return kha.ComputeHash(Encoding.UTF8.GetBytes(data));
    }
    
    
    private static string getSignatureKey(string key, string stringToSign)
    {
        byte[] kSecret = Encoding.UTF8.GetBytes(key.ToCharArray());
        byte[] kSigning = HmacSHA256(stringToSign, kSecret);
        return WebUtility.UrlEncode(Convert.ToBase64String(kSigning));
    }
    

    Contrary to most of the answers found here and elsewhere, this is the only way that works. The entire request has to be hashed, not just particular parameters. I can't speak to other Amazon services, but the Commerce Service has to be done like this.

    Quite a few answers referenced this: https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html or this: Amazon API generating a request signature in C# .NET

    As I stated in my question, this is most certainly not correct. If you're not passing a region parameter, how can Amazon create the same signature since it doesn't have all the information.

    Anyway, this works now.