6

I am trying to use AWS API to create a stack in AWS CloudFormation, but they return error saying "signature we calculated does not match the signature you provided"

Fllowing is the code that I am using to generate the siganture

$private_key = "xxxxxxxxxxxxx";
$params = array();
$method = "POST";
$host = "cloudformation.eu-west-1.amazonaws.com";
$uri = "/onca/xml";

// additional parameters
$params["Service"] = "AWSCloudFormation";
$params["Operation"] = "DeleteStack";
$params["AWSAccessKeyId"] = "xxxxxxxxxxxxxx";
// GMT timestamp
$params["Timestamp"] = gmdate("Y-m-d\TH:i:s\Z");
// API version
$params["Version"] = "2010-05-15";

// sort the parameters
// create the canonicalized query
$canonicalized_query = array();
foreach ($params as $param => $value) {
    $param = str_replace("%7E", "~", rawurlencode($param));
    $value = str_replace("%7E", "~", rawurlencode($value));
    $canonicalized_query[] = $param . "=" . $value;
}
$canonicalized_query = implode("&", $canonicalized_query);

// create the string to sign
$string_to_sign = $method . "\n" . $host . "\n" . $uri . "\n" . $canonicalized_query;

// calculate HMAC with SHA256 and base64-encoding
$signature = base64_encode(hash_hmac("sha256", $string_to_sign, $private_key, True));

// encode the signature for the request
$signature = str_replace("%7E", "~", rawurlencode($signature));

the url I am using is 

'https://cloudformation.us-east-1.amazonaws.com/
?Action=DeleteStack
&StackName=MyStack
&Version=2010-05-15
&SignatureVersion=2
&Timestamp=2012-09-05T06:32:19Z
&AWSAccessKeyId=[AccessKeyId]
&Signature=[Signature]
&SignatureMethod=HmacSHA256'
Reneshankar
  • 157
  • 2
  • 2
  • 8

4 Answers4

7

Here is your code, a bit tidied up, with an example function showing how to extend this approach to other AWS APIs.

function aws_query($extraparams) {
    $private_key = ACCESS_SECRET_KEY;

    $method = "GET";
    $host = "webservices.amazon.com";
    $uri = "/onca/xml";

    $params = array(
        "AssociateTag" => ASSOCIATE_TAG,
        "Service" => "AWSECommerceService",
        "AWSAccessKeyId" => ACCESS_KEY_ID,
        "Timestamp" => gmdate("Y-m-d\TH:i:s\Z"),
        "SignatureMethod" => "HmacSHA256",
        "SignatureVersion" => "2",
        "Version" => "2013-08-01"
    );

    foreach ($extraparams as $param => $value) {
        $params[$param] = $value;
    }

    ksort($params);

    // sort the parameters
    // create the canonicalized query
    $canonicalized_query = array();
    foreach ($params as $param => $value) {
        $param = str_replace("%7E", "~", rawurlencode($param));
        $value = str_replace("%7E", "~", rawurlencode($value));
        $canonicalized_query[] = $param . "=" . $value;
    }
    $canonicalized_query = implode("&", $canonicalized_query);

    // create the string to sign
    $string_to_sign =
        $method . "\n" .
        $host . "\n" .
        $uri . "\n" .
        $canonicalized_query;

    // calculate HMAC with SHA256 and base64-encoding
    $signature = base64_encode(
        hash_hmac("sha256", $string_to_sign, $private_key, True));

    // encode the signature for the equest
    $signature = str_replace("%7E", "~", rawurlencode($signature));

    // Put the signature into the parameters
    $params["Signature"] = $signature;
    uksort($params, "strnatcasecmp");

    // TODO: the timestamp colons get urlencoded by http_build_query
    //       and then need to be urldecoded to keep AWS happy. Spaces
    //       get reencoded as %20, as the + encoding doesn't work with 
    //       AWS
    $query = urldecode(http_build_query($params));
    $query = str_replace(' ', '%20', $query);

    $string_to_send = "https://" . $host . $uri . "?" . $query;

    return $string_to_send;
}

function aws_itemlookup($itemId) {
    return aws_query(array (
        "Operation" => "ItemLookup",
        "IdType" => "ASIN",
        "ItemId" => $itemId
    ));
}
Buddy
  • 160
  • 1
  • 8
3

That Worked for me.

$str = "Service=AWSECommerceService&Operation=ItemSearch&AWSAccessKeyId={Access Key}&Keywords=Harry%20Potter&ResponseGroup=Images%2CItemAttributes%2COffers&SearchIndex=Books&Timestamp=2019-08-11T17%3A51%3A56.000Z";


$ar = explode("&", $str);

natsort($ar);

$str = "GET
webservices.amazon.com
/onca/xml
";

$str .= implode("&", $ar); 

$str = urlencode(base64_encode(hash_hmac("sha256",$str,'{Secret Key Here}',true)));


http://webservices.amazon.com/onca/xml?Service=AWSECommerceService&Operation=ItemSearch&AWSAccessKeyId={Access Key}&Keywords=Harry%20Potter&ResponseGroup=Images%2CItemAttributes%2COffers&SearchIndex=Books&Timestamp=2019-08-11T17%3A51%3A56.000Z&Signature=$str

Remember: If you get this error Your AccessKey Id is not registered for Product Advertising API. Please use the AccessKey Id obtained after registering at https://affiliate-program.amazon.com/assoc_credentials/home

Go to https://affiliate-program.amazon.com/assoc_credentials/home

You can also simulate your query at:

https://webservices.amazon.com/scratchpad/index.html and click on item search.

rescue1155
  • 365
  • 1
  • 14
  • 1
    Thank you for posting this. I googled-fu so many times to find a straightforward answer as to how to generate a AWS signature for the Products Advertising API since this service is not in the AWS SDK. This is the most-straightforward answer that I've seen instead of this – De'Yonte W. Sep 12 '19 at 23:42
1

I confirmed Frederick's answer. You must ksort the array before hashing it.

0

Is there a reason why you're not just using the AWS SDK for PHP? It handles all of this for you.

Ryan Parman
  • 6,855
  • 1
  • 29
  • 43
  • 4
    aws sdk is on average 2x slower than doing direct rest requests (if done properly) – Thomas Smart Oct 23 '13 at 01:32
  • 2
    The AWS SDK is a huge monstrosity. If you don't need some of it's more complicated features, it's better to avoid it for simple AWS use, especially if speed is important. – orrd Aug 16 '16 at 20:17
  • @ThomasSmart: I would be fascinated to see benchmarks doing comparable tasks. Honestly. – Ryan Parman Aug 16 '16 at 20:22
  • 1
    @orrd: What does the size really matter, though? It's one line in your composer.json file. This was something that I wrestled with after we shipped 1.0. But the more I thought about it, the more I realized that with Composer, it really didn't matter at all. Do Swift/Obj-C developers avoid Xcode and the Apple frameworks just because it's 8 GB? Of course not! Because the only code that _runs_ is the code that you _need_. – Ryan Parman Aug 16 '16 at 20:25
  • @RyanParman It's not the install size that concerns me, but it also actually runs a ton of PHP code even for simple requests. I experimented with it and it was slow and memory intensive so I decided it made more sense to write some simple request code from scratch. But that was years ago that I last tried it, so as Thomas Smart mentioned, benchmarks would be better to get a fair comparison, so this is all just hearsay. – orrd Aug 16 '16 at 20:59
  • 1
    @RyanParman i presented these at the AWS user group in singapore 2 years ago, but you can see the summary here: http://www.cloudcore.cloud/Benchmarks. The framework uses direct API calls with some processing on top of that as needed and it's a bit faster. These benchmarks were done 2 years ago though, not reviewed since. I also always point out that the goals of my framework and the SDK are a bit different. The SDK is great plug-and-play and it just works easily. Framework is for more extensive projects where query efficiency might be a higher priority. – Thomas Smart Aug 17 '16 at 07:28
  • In the case of a WordPress plugin, you want to be under 2MB ZIP'd size to allow those on limited hosting to install via upload. Besides the size and resource benefits with straight cURL'ing, composer libs need to be converted when you don't control the environment. When I just need S3 and CloudFront, I end up spending time stripping out all the other stuff and I still won't be aware of every line of code I'm shipping. – ljs.dev Jan 10 '19 at 09:05