1. Advertising
    y u no do it?

    Advertising (learn more)

    Advertise virtually anything here, with CPM banner ads, CPM email ads and CPC contextual links. You can target relevant areas of the site and show ads based on geographical location of the user if you wish.

    Starts at just $1 per CPM or $0.10 per CPC.

Amazon Product Advertising API Signature - PHP

Discussion in 'Amazon' started by myhart, Jun 3, 2009.

  1. #1
    After August 15, 2009 in order to use Amazon's Product Advertising API a timestamp and signature must be included with each request made to Amazon.com. Information concerning this requirement can be accessed at Amazon's Product Advertising API Version 2009-03-31. While searching for additional information about generating the signature string. I found two helpful websites which feature PHP code for this purpose!

    Amazon® AWS HMAC signed request using PHP

    REST Authentication for PHP4

    In order to get a better understanding of how Amazon's signature authentication process worked. I decided to hack together some basic PHP code which adds both the timestamp and signature to the Amazon API request. Unlike the code available from the above websites. I decided to see if I could generate the signed request string without using arrays. The following code appears to works ok when using PHP5. Please note that you will need an "AWSAccessKeyId" and a "Secret Access Key" to create the signed Amazon request URL. The "XML" link included in the code allows you to view Amazon's raw XML data. If you don't want the link to show simply comment it out. Same with the print_r ($XML) funtion which allows for viewing the data after it has been parsed by PHP5's simplexml_load_file () function.

    The following code pretty much follows Amazon's example under "Example REST Requests". The code generates a Timestamp using the gmdate() function. The colons in the Timestamp are urlencoded using the str_replace() function. Comas in the ResponseGroup are also urlencoded using str_replace(). I manually sorted the order of the "parameter/value pairs" in the $String as shown in the Amazon example. Which states that they should be arranged "by byte value (not alphabetically, lowercase parameters will be listed after uppercase ones)". Line breaks in the $String are included to make arraigning the parameter/value pairs easier. The line breaks in the $String are then removed using str_replace(). Line breaks (/n) are added to the $Prepend string. $Prepend and $String are combined into one string and use base64_encode(hash_hmac( )) and the Secret Access Key to create the Amazon request signature. The $Signature string is then urlencoded for plus (+) and equal (=) using str_replace(). The $SignedRequest is created simply by combining the different pieces of the request URL and adding the Signature.

    <?
    $AWSAccessKeyId = "";
    $SecretAccessKey = "";
    
    $ItemId = "0679722769"; // ASIN
    $Timestamp = gmdate("Y-m-d\TH:i:s\Z"); 
    $Timestamp = str_replace(":", "%3A", $Timestamp);
    $ResponseGroup = "ItemAttributes,Offers,Images,Reviews";
    $ResponseGroup = str_replace(",", "%2C", $ResponseGroup);
    
    $String = "AWSAccessKeyId=$AWSAccessKeyId&
    ItemId=$ItemId&
    Operation=ItemLookup&
    ResponseGroup=$ResponseGroup&
    Service=AWSECommerceService&
    Timestamp=$Timestamp&
    Version=2009-01-06";
    
    $String = str_replace("\n", "", $String); 
    
    $Prepend = "GET\nwebservices.amazon.com\n/onca/xml\n";
    $PrependString = $Prepend . $String;
    
    $Signature = base64_encode(hash_hmac("sha256", $PrependString, $SecretAccessKey, True));  
    $Signature = str_replace("+", "%2B", $Signature);
    $Signature = str_replace("=", "%3D", $Signature);
    
    $BaseUrl = "http://webservices.amazon.com/onca/xml?";
    $SignedRequest = $BaseUrl . $String . "&Signature=" . $Signature;
    
    $XML = simplexml_load_file($SignedRequest);
    
    echo '<a href="'.$SignedRequest.'">XML</a><p>';
    print_r ($XML);
    ?>
    PHP:
     
    myhart, Jun 3, 2009 IP
  2. ciscopower

    ciscopower Active Member

    Messages:
    125
    Likes Received:
    2
    Best Answers:
    0
    Trophy Points:
    68
    #2
    Thank you, informative.
     
    ciscopower, Jun 5, 2009 IP
  3. mxyzplk

    mxyzplk Well-Known Member

    Messages:
    792
    Likes Received:
    3
    Best Answers:
    0
    Trophy Points:
    140
    #3
    interesting, I think I could combine it with other affiliate script...

    and tophotdeals awesome. wanna guide me to build that with wp?
     
    mxyzplk, Jun 16, 2009 IP
  4. markowe

    markowe Well-Known Member

    Messages:
    1,136
    Likes Received:
    26
    Best Answers:
    0
    Trophy Points:
    165
    #4
    Thanks, this is nice. I used the code you linked to above which really simplified things, though yours is even shorter. I think I am right in saying that yours would only work with PHP5 because earlier versions of PHP don't include support for sha256, which is needed for this.
     
    markowe, Jun 18, 2009 IP
  5. stokescomp

    stokescomp Peon

    Messages:
    2
    Likes Received:
    0
    Best Answers:
    0
    Trophy Points:
    0
    #5
    actually Amazon® AWS HMAC signed request using PHP uses the sha256 also

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

    the second link uses:
    The code requires a library called sha256.inc.php

    it should be easy to upgrade to php5 and use the included sha256 function.
     
    stokescomp, Jun 18, 2009 IP
  6. myhart

    myhart Peon

    Messages:
    228
    Likes Received:
    11
    Best Answers:
    0
    Trophy Points:
    0
    #6
    It appears that PHP4 with the mhash function enabled will support sha256. See Dan Casey's post at:

    http://php.filearena.net/manual/kr/function.sha1.php

    I don't have access to testing wth PHP4 however substituting

    $Signature= base64_encode(mhash(MHASH_SHA256, $PrependString, $SecretAccessKey));
    PHP:
    for

    $Signature = base64_encode(hash_hmac("sha256", $PrependString, $SecretAccessKey, True));
    PHP:
    works ok for me using PHP 5.2.9! If someone would test the above using PHP4 with mhash enabled the results might be interesting?

    For reference on the mhash function see:

    http://us3.php.net/manual/en/function.mhash.php

    "If specified, the function will return the resulting HMAC instead. HMAC is keyed hashing for message authentication, or simply a message digest that depends on the specified key."
     
    myhart, Jun 20, 2009 IP
  7. myhart

    myhart Peon

    Messages:
    228
    Likes Received:
    11
    Best Answers:
    0
    Trophy Points:
    0
    #7
    Forgot to mention in my last post that since simplexml_load_file() is a PHP5 funtion that you may need to delete or comment out that line. To check if the signature is correct simply click the XML link and view the "Signature" Argument Name. This assumes that the raw XML feed is returned with your call to Amazon. Should look like the following:

    <Argument Name="Signature" Value="WnWjr28mfoCu1FWOLqDAEDl9NYcN13v/oEqXwUQIQEw=" />
    Code (markup):
    Also please be aware that print_r ($XML); no longer displays the parsed XML!
     
    myhart, Jun 20, 2009 IP
  8. TechEvangelist

    TechEvangelist Guest

    Messages:
    919
    Likes Received:
    140
    Best Answers:
    0
    Trophy Points:
    133
    #8
    I get the following error when I run the URL generated by this script:

    <?xml version="1.0" ?> 
    - <ItemLookupErrorResponse xmlns="http://ecs.amazonaws.com/doc/2009-01-06/">
    - <Error>
      <Code>SignatureDoesNotMatch</Code> 
      <Message>The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.</Message> 
      </Error>
      <RequestID>4c5c9d3d-6f65-4acd-bd45-a0155faaf3d4</RequestID> 
      </ItemLookupErrorResponse>
    Code (markup):
    I am 100% positive that my Access Key and Secret Key are correct. My server is running PHP5.

    Any ideas?
     
    TechEvangelist, Jun 20, 2009 IP
  9. makeingbank

    makeingbank Banned

    Messages:
    105
    Likes Received:
    0
    Best Answers:
    0
    Trophy Points:
    0
    #9
    I am getting the same error to :/
     
    makeingbank, Jun 20, 2009 IP
  10. TechEvangelist

    TechEvangelist Guest

    Messages:
    919
    Likes Received:
    140
    Best Answers:
    0
    Trophy Points:
    133
    #10
    My problem is with the signature. if I remove the signature, the request works properly.

    Both base64_encode and hash_hmac appear to be working properly.
     
    TechEvangelist, Jun 21, 2009 IP
  11. myhart

    myhart Peon

    Messages:
    228
    Likes Received:
    11
    Best Answers:
    0
    Trophy Points:
    0
    #11
    Try using the following (same code) which should copy and paste properly.

    <?
    $AWSAccessKeyId = "";
    $SecretAccessKey = "";
    
    $ItemId = "0679722769"; // ASIN
    $Timestamp = gmdate("Y-m-d\TH:i:s\Z"); 
    $Timestamp = str_replace(":", "%3A", $Timestamp);
    $ResponseGroup = "ItemAttributes,Offers,Images,Reviews";
    $ResponseGroup = str_replace(",", "%2C", $ResponseGroup);
    
    $String = "AWSAccessKeyId=$AWSAccessKeyId&
    ItemId=$ItemId&
    Operation=ItemLookup&
    ResponseGroup=$ResponseGroup&
    Service=AWSECommerceService&
    Timestamp=$Timestamp&
    Version=2009-01-06";
    
    $String = str_replace("\n", "", $String); 
    
    $Prepend = "GET\nwebservices.amazon.com\n/onca/xml\n";
    $PrependString = $Prepend . $String;
    
    $Signature = base64_encode(hash_hmac("sha256", $PrependString, $SecretAccessKey, True));  
    $Signature = str_replace("+", "%2B", $Signature);
    $Signature = str_replace("=", "%3D", $Signature);
    
    $BaseUrl = "http://webservices.amazon.com/onca/xml?";
    $SignedRequest = $BaseUrl . $String . "&Signature=" . $Signature;
    
    $XML = simplexml_load_file($SignedRequest);
    
    echo '<a href="'.$SignedRequest.'">XML</a><p>';
    print_r ($XML);
    ?>
    
    Code (markup):
    Also make sure that the following is on 1 line.

    $Signature = base64_encode(hash_hmac("sha256", $PrependString, $SecretAccessKey, True));
    Code (markup):
    If that doesn't help echo the $SignedRequest URL and check to see if it looks like the following with your "AWSAccessKeyId" and "Signature" included. As soon as time allows I will create a webpage showing each step of the process.

    http://webservices.amazon.com/onca/xml?AWSAccessKeyId=YOURACCESSKEY&ItemId=0679722769&Operation=ItemLookup&ResponseGroup=ItemAttributes%2COffers%2CImages%2CReviews&Service=AWSECommerceService&Timestamp=2009-06-21T17%3A15%3A28Z&Version=2009-01-06&Signature=Ifp9yz/9El%2B3jNPYy8lqkOJdj%2Bl/OyIVwrIXxP/XOAM%3D
    Code (markup):
    I also found that without an attached signature the Amazon API request would be processed.
     
    myhart, Jun 21, 2009 IP
    TechEvangelist likes this.
  12. TechEvangelist

    TechEvangelist Guest

    Messages:
    919
    Likes Received:
    140
    Best Answers:
    0
    Trophy Points:
    133
    #12
    Hi myhart

    I tested it. Same results.

    The URL looks correct. Everything looks like it should work, but it doesn't. I tested both base64_encode and hash_hmac. Both appear to be working, so I don't think it is a problem with encoding on my server.

    I am also getting a strange error from SimpleXML.

     
    TechEvangelist, Jun 21, 2009 IP
  13. myhart

    myhart Peon

    Messages:
    228
    Likes Received:
    11
    Best Answers:
    0
    Trophy Points:
    0
    #13
    Hi TechEvangelist,
    I was able to duplicate the message that SimpleXML returns to you by adding a space at the end of
    ResponseGroup=$ResponseGroup&
    Code (markup):
    By commenting out
    $String = str_replace("\n", "", $String);
    Code (markup):
    I also created the same message.

    You might try the script with the $String all on one line as I suspect an extra space in the string somewhere?

    $String = "AWSAccessKeyId=$AWSAccessKeyId&ItemId=$ItemId&Operation=ItemLookup&ResponseGroup=$ResponseGroup&Service=AWSECommerceService&Timestamp=$Timestamp&Version=2009-01-06";
    Code (markup):
     
    myhart, Jun 21, 2009 IP
  14. myhart

    myhart Peon

    Messages:
    228
    Likes Received:
    11
    Best Answers:
    0
    Trophy Points:
    0
    #14
    After doing some reading about removing line breaks. I wonder if we also need to remove "Carriage Returns" from the $String? I suppose this may vary from server to server???

    $String = str_replace("\r", "", $String);
    Code (markup):
     
    myhart, Jun 21, 2009 IP
  15. myhart

    myhart Peon

    Messages:
    228
    Likes Received:
    11
    Best Answers:
    0
    Trophy Points:
    0
    #15
    If we use preg_replace instead of str_replace we can strip all "carriage returns", "line breaks" and "spaces" from the $String.

    $String = preg_replace('/[\r\n\" "]/', '', $String);
    Code (markup):
     
    myhart, Jun 21, 2009 IP
  16. TechEvangelist

    TechEvangelist Guest

    Messages:
    919
    Likes Received:
    140
    Best Answers:
    0
    Trophy Points:
    133
    #16
    I think you have pegged the problem.

    The returns in the final request URL appear to be causing the problem. I was just looking at the URL in the address bar, which looked correct. I should have looked at the code being generated. The strange error messages were adding to the confusion.

    $String = preg_replace('/[\r\n\" "]/', '', $String);
    Code (markup):
    That did it. You need to strip out both the returns and the newlines. You da man!

    Every Amazon affiliate who is following this thread needs to give this guy some green.
     
    TechEvangelist, Jun 22, 2009 IP
  17. chipimbiri

    chipimbiri Peon

    Messages:
    18
    Likes Received:
    1
    Best Answers:
    0
    Trophy Points:
    0
    #17
    I personally use ASP.NET
     
    chipimbiri, Jun 22, 2009 IP
  18. webopius

    webopius Peon

    Messages:
    18
    Likes Received:
    0
    Best Answers:
    0
    Trophy Points:
    0
    #18
    Just a quick note to say thanks for this.

    Works perfectly for me across both Amazon US and Amazon UK sites [for the UK, I changed the $Preprend to point to amazon.co.uk]. Personally, I created the request string without the line breaks and because the site I'm using creates fairly complicated Amazon requests dynamically and I can't always create the string in the new Amazon sort order, I added a split(), asort() and implode() sequence to the $String to sort the Amazon operations into the correct order before I sign it.
     
    webopius, Jun 28, 2009 IP
  19. myhart

    myhart Peon

    Messages:
    228
    Likes Received:
    11
    Best Answers:
    0
    Trophy Points:
    0
    #19
    Glad you found the above helpful! Once we have a basic working example of adding the API signature to our Amazon product request. It really doesn't seem all that hard to do. I have been working to overcome the issues you mentioned "both Amazon US and Amazon UK sites" and "site I'm using creates fairly complicated Amazon requests dynamically". For the second issue I am also using asort() and implode(). Though sounds like I may have taken a somewhat different approach as I explode() the request URL twice. I have tested the following code with Freekrai's ASM2 . Which seems to be working fine. I will post how to add the Amazon Product signature to ASM2 as soon as I get time. As to the first issue the code shown below will automatically create the signed request URL for both Amazon US and UK sites.

    $Request = old request URL
    $SignedRequest = new signed request URL
    Be sure to add your $SecretAccessKey!


    $String = preg_replace('/[\r\n\" "]/', '', str_replace(",", "%2C", $Request));
    $SecretAccessKey = "";
    $Timestamp = str_replace(":", "%3A",  gmdate("Y-m-d\TH:i:s\Z"));
    $StringArray = explode( '?', $String);
    $ValueStringArray = explode("&", $StringArray[1]);
    $ValueStringArray[] = "Timestamp=".$Timestamp;       
    asort($ValueStringArray);
    $NewString = implode("&", $ValueStringArray);
    $url = parse_url($StringArray[0]);
    $Prepend = "GET\n" .$url['host']. "\n" .$url['path']. "\n";
    $PrependString = $Prepend . $NewString;
    $Signature = base64_encode(hash_hmac("sha256", $PrependString, $SecretAccessKey, True));
    $Signature = str_replace("+", "%2B", str_replace("=", "%3D", $Signature));
    $SignedRequest = $StringArray[0]. "?" . $NewString . "&Signature=" . $Signature;
    Code (markup):
     
    myhart, Jun 28, 2009 IP
  20. Wachiraj

    Wachiraj Greenhorn

    Messages:
    63
    Likes Received:
    0
    Best Answers:
    0
    Trophy Points:
    16
    #20
    Now unpractised But for a store of knowledge beforehand thanks
     
    Wachiraj, Jun 28, 2009 IP