AWS Signature creation using PHP

10,783

Solution 1

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
    ));
}

Solution 2

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.

Share:
10,783
Reneshankar
Author by

Reneshankar

Updated on June 21, 2022

Comments

  • Reneshankar
    Reneshankar almost 2 years

    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'
    
  • Thomas Smart
    Thomas Smart over 10 years
    aws sdk is on average 2x slower than doing direct rest requests (if done properly)
  • orrd
    orrd over 7 years
    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.
  • Ryan Parman
    Ryan Parman over 7 years
    @ThomasSmart: I would be fascinated to see benchmarks doing comparable tasks. Honestly.
  • Ryan Parman
    Ryan Parman over 7 years
    @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.
  • orrd
    orrd over 7 years
    @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.
  • Thomas Smart
    Thomas Smart over 7 years
    @RyanParman i presented these at the AWS user group in singapore 2 years ago, but you can see the summary here: 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.
  • ljs.dev
    ljs.dev over 5 years
    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.
  • De'Yonte W.
    De'Yonte W. over 4 years
    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