Tuesday, May 12, 2009

Amazon Product Advertising API, HMAC-SHA256 signature,asp.net, C# Working Code Sample

Amazon Product Advertising API, HMAC-SHA256 signature,asp.net 2.0, C# Working Code Sample
 
using System:
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Xml;
using System.Xml.XPath;
using System.Collections.Generic;
using System.Web;
using System.Security.Cryptography;

public partial class _Default : System.Web.UI.Page
{
    private const string MY_AWS_ACCESS_KEY_ID = "xxxxxxxxxxx";
    private const string MY_AWS_SECRET_KEY = "asasasasasasasasasasasasasasasasas";
    private const string DESTINATION = "ecs.amazonaws.com";

    private const string NAMESPACE = "http://webservices.amazon.com/AWSECommerceService/2009-03-31";
    private const string ITEM_ID = "B000ERVMI8";


    private string endPoint;
    private string akid;
    private byte[] secret;
    private HMAC signer;

    private const string REQUEST_URI = "/onca/xml";
    private const string REQUEST_METHOD = "GET";


    protected void Page_Load(object sender, EventArgs e)
    {
       // SignedRequestHelper helper = new SignedRequestHelper(MY_AWS_ACCESS_KEY_ID, MY_AWS_SECRET_KEY, DESTINATION);

        SignedRequestHelper(MY_AWS_ACCESS_KEY_ID, MY_AWS_SECRET_KEY, DESTINATION);

        string requestString = "Service=AWSECommerceService&Version=2009-03-31&Operation=ItemLookup&ResponseGroup=Small&ItemId=" + ITEM_ID;
        string requestUrl = Sign(requestString);
        string title = FetchTitle(requestUrl);
        Response.Write("Method 2: Query String form.");
        Response.Write("Title is \"" + title + "\"");
        //System.Console.WriteLine();

        //System.Console.WriteLine("Hit Enter to end");
        //System.Console.ReadLine();
    }
    private static string FetchTitle(string url)
    {
        try
        {
            WebRequest request = HttpWebRequest.Create(url);
            WebResponse response = request.GetResponse();
            XmlDocument doc = new XmlDocument();
            doc.Load(response.GetResponseStream());
            XmlNode titleNode = doc.GetElementsByTagName("Title", NAMESPACE).Item(0);
            string title = titleNode.InnerText;
            return title;
        }
        catch (Exception e)
        {
            System.Console.WriteLine("Caught Exception: " + e.Message);
            System.Console.WriteLine("Stack Trace: " + e.StackTrace);
           
        }

        return null;
    }

     public void SignedRequestHelper(string awsAccessKeyId, string awsSecretKey, string destination)
        {
            this.endPoint = destination.ToLower();
            this.akid = awsAccessKeyId;
            this.secret = Encoding.UTF8.GetBytes(awsSecretKey);
            this.signer = new HMACSHA256(this.secret);
        }

        public string Sign(IDictionary<string, string> request)
        {
            ParamComparer pc = new ParamComparer();
            SortedDictionary<string, string> sortedMap = new SortedDictionary<string, string>(request, pc);

            sortedMap["AWSAccessKeyId"] = this.akid;
            sortedMap["Timestamp"] = this.GetTimestamp();

            // Get the canonical query string
            string canonicalQS = this.ConstructCanonicalQueryString(sortedMap);

            // Derive the bytes needs to be signed.
            StringBuilder builder = new StringBuilder();
            builder.Append(REQUEST_METHOD)
                .Append("\n")
                .Append(this.endPoint)
                .Append("\n")
                .Append(REQUEST_URI)
                .Append("\n")
                .Append(canonicalQS);

            string stringToSign = builder.ToString();
            byte[] toSign = Encoding.UTF8.GetBytes(stringToSign);
            // Compute the signature and convert to Base64.
            byte[] sigBytes = signer.ComputeHash(toSign);
            string signature = Convert.ToBase64String(sigBytes);
            // now construct the complete URL and return to caller.
            StringBuilder qsBuilder = new StringBuilder();
            qsBuilder.Append("http://")
                .Append(this.endPoint)
                .Append(REQUEST_URI)
                .Append("?")
                .Append(canonicalQS)
                .Append("&Signature=")
                .Append(this.PercentEncodeRfc3986(signature));
            return qsBuilder.ToString();
        }
        public string Sign(string queryString)
        {
            IDictionary<string, string> request = this.CreateDictionary(queryString);
            return this.Sign(request);
        }

        private string GetTimestamp()
        {
            DateTime currentTime = DateTime.UtcNow;
            string timestamp = currentTime.ToString("yyyy-MM-ddTHH:mm:ssZ");
            return timestamp;
        }
        private string PercentEncodeRfc3986(string str)
        {
            str = HttpUtility.UrlEncode(str, System.Text.Encoding.UTF8);
            str.Replace("'", "%27").Replace("(", "%28").Replace(")", "%29").Replace("*", "%2A").Replace("!", "%21").Replace("%7e", "~");

            StringBuilder sbuilder = new StringBuilder(str);
            for (int i = 0; i < sbuilder.Length; i++)
            {
                if (sbuilder[i] == '%')
                {
                    if (Char.IsDigit(sbuilder[i + 1]) && Char.IsLetter(sbuilder[i + 2]))
                    {
                        sbuilder[i + 2] = Char.ToUpper(sbuilder[i + 2]);
                    }
                }
            }
            return sbuilder.ToString();
        }

        private IDictionary<string, string> CreateDictionary(string queryString)
        {
            Dictionary<string, string> map = new Dictionary<string, string>();

            string[] requestParams = queryString.Split('&');

            for (int i = 0; i < requestParams.Length; i++)
            {
                if (requestParams[i].Length < 1)
                {
                    continue;
                }

                char[] sep = { '=' };
                string[] param = requestParams[i].Split(sep, 2);
                for (int j = 0; j < param.Length; j++)
                {
                    param[j] = HttpUtility.UrlDecode(param[j], System.Text.Encoding.UTF8);
                }
                switch (param.Length)
                {
                    case 1:
                        {
                            if (requestParams[i].Length >= 1)
                            {
                                if (requestParams[i].ToCharArray()[0] == '=')
                                {
                                    map[""] = param[0];
                                }
                                else
                                {
                                    map[param[0]] = "";
                                }
                            }
                            break;
                        }
                    case 2:
                        {
                            if (!string.IsNullOrEmpty(param[0]))
                            {
                                map[param[0]] = param[1];
                            }
                        }
                        break;
                }
            }

            return map;
        }

        private string ConstructCanonicalQueryString(SortedDictionary<string, string> sortedParamMap)
        {
            StringBuilder builder = new StringBuilder();

            if (sortedParamMap.Count == 0)
            {
                builder.Append("");
                return builder.ToString();
            }

            foreach (KeyValuePair<string, string> kvp in sortedParamMap)
            {

                builder.Append(this.PercentEncodeRfc3986(kvp.Key));
                builder.Append("=");
                builder.Append(this.PercentEncodeRfc3986(kvp.Value));
                builder.Append("&");
            }
            string canonicalString = builder.ToString();
            canonicalString = canonicalString.Substring(0, canonicalString.Length - 1);
            return canonicalString;
        }
    }

class ParamComparer : IComparer<string>
{
    public int Compare(string p1, string p2)
    {
        return string.CompareOrdinal(p1, p2);
    }
}

8 comments:

scorpion said...

Superb, thanks a lot!! You saved me lot of time.

vandal said...

I use the same algorithm for signing the request, but I can't seem to generate the same signature as Amazon does.
I even tried out their Signed Requests Helper and still kept on getting a different signature.

Help will be appreciated.

thanks

penneyworth said...
This comment has been removed by the author.
penneyworth said...

Where can I get the library that has the SignedRequestHelper class?

penneyworth said...
This comment has been removed by the author.
Unknown said...
This comment has been removed by the author.
Sumit Rawat said...

Really you are big madarchod. The code you written saves a lot of time and away my frustration from Signature. Really awesome....

serkan said...

thank you bro it helped me god bless you