Click here to Skip to main content
Click here to Skip to main content

Create URL for an Authenticated Amazon EC2 API Request

, 30 Mar 2011
Rate this:
Please Sign up or sign in to vote.
This article describes how to create a URL for an authenticated Amazon EC2 API request.

Introduction

As described in the Amazon Web Services documentation topic, Making Query Requests, the URL for an EC2 query is composed of the host address and various request parameters, one of which is a signature that verifies the identity of the sender. The signature is an encrypted hash of the request parameters and their values based on the sender’s private key and either the HMACSha1 or HMACSha256 algorithm. Most of the code required to encrypt the signature simply orders and formats the request parameters in the same way that AWS orders and formats them when AWS recreates the signature to authenticate the request. The request includes the public key that AWS uses to identify the private key used to encrypt the signature. AWS gets the sender’s private key and then encrypts its own version of the signature using the same algorithm as the sender. The signature of a legitimate request matches that which AWS creates. The critical segment of the process for the sender follows the same protocol AWS uses to create the text input for the encryption algorithm.

Create the Encryption Input - the String to Sign

The topic Making Query Requests specifies the explicit protocol for creating the string to sign. This topic implements the procedure in C#.

The first step is to sort the query elements by parameter name in natural byte ordering. Most code languages include a list class that orders members by a comparator suited to the type of the list members. This example uses the .NET generic class SortedDictionary<string, string>. The type identifiers indicate the string type of member parameters and values. The .NET comparator CompareOrdinal, shown in the following method, compares two string objects by evaluating the numeric values of the corresponding character objects in each string. The SortedDictionary class in this example uses the ParamComparator class. Natural byte ordering is case-insensitive as is the CompareOrdinal method of the .NET String class.

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

The following code segment creates a SortedDictionary object and adds request parameters for the EC2 action DescribeInstances. Using the ParamComparer, the SortedDictionary automatically orders the members in UTF-8 natural byte ordering.

ParamComparer pc = new ParamComparer(); 
SortedDictionary<string, string> sortedRequestPars = 
                 new SortedDictionary<string, string>(pc); 
string action = "DescribeInstances"; 

sortedRequestPars.Add("Action", action); 
sortedRequestPars.Add("SignatureMethod", "HmacSHA1"); 
sortedRequestPars.Add("Version", "2010-11-15"); 
sortedRequestPars.Add("SignatureVersion", "2"); 
string date = GetEC2Date(); 
sortedRequestPars.Add("Timestamp", date); 
sortedRequestPars.Add("AWSAccessKeyId", accessKeyId);

The Timestamp parameter that the previous code initializes must be formatted in Coordinated Universal Time (abbreviated UTC). The static method of the .NET DateTime class gets the request time and formats it according to the EC2 specification. You can use the Timestamp parameter or the Expires parameter in an EC2 request. A request that uses the Timestamp parameter is valid until fifteen minutes after the specified time. A request that uses the Expires parameter is valid until the time specified.

static public string GetEC2Date() 
{ 
    //string httpDate = DateTime.UtcNow.ToString("s") + "Z"; 
    string httpDate = DateTime.UtcNow.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'", 
                      DateTimeFormatInfo.InvariantInfo); 
    return httpDate; 
}

The parameters and their values must be URL encoded. This is easier to do in some programming languages than in others. This example uses C# and shows explicit URL encoding. The code replaces characters such as *, !, and ) with their corresponding ASCII control encoding. This is often called percent encoding.

private static string PercentEncodeRfc3986(string str) 
{ 
    str = HttpUtility.UrlEncode(str, System.Text.Encoding.UTF8); 
    str = str.Replace("'", "%27").Replace("(", "%28").Replace(")", 
                      "%29").Replace("*", "%2A").Replace("!", 
                      "%21").Replace("%7e", "~").Replace("+", "%20"); 
    StringBuilder sbuilder = new StringBuilder(str); 

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

After the parameters are sorted in natural byte order and URL encoded, the next step is to concatenate them into a single text string. The following method uses the PercentEncodeRfc3986 method, shown previously, to create the parameter string.

public static String GetSortedParamsAsString(
       SortedDictionary<String, String> paras, bool isCanonical) 
{ 
    String sParams = ""; 
    String sKey = null; 
    String sValue = null; 
    String separator = ""; 

    foreach (KeyValuePair<string, String> entry in paras) 
    { 
        sKey = entry.Key; 
        sValue = entry.Value; 

        if (isCanonical) 
        { 
            sKey = PercentEncodeRfc3986(sKey); 
            sValue = PercentEncodeRfc3986(sValue); 
        } 
        sParams += separator + sKey + "=" + sValue; 
        separator = "&"; 
    } 
    return sParams; 
}

The following ComposeStringToSign method creates the final string to sign. This method simply calls the method GetSortedParamsAsString, shown previously, adds the host URL, the request method that is usually GET or POST, and the resource path, if any. The resulting string is the input value for encryption.

static public string ComposeStringToSign(SortedDictionary<string, string> listPars, 
                     string method, string host, string resourcePath) 
{ 
    String stringToSign = null; 

    stringToSign = method + "\n"; 
    stringToSign += host + "\n"; 
    stringToSign += resourcePath + "\n"; 
    stringToSign += GetSortedParamsAsString(listPars, true); 

    return stringToSign; 
}

Following is the string to sign:

GET 

ec2.amazonaws.com 

/ 

AWSAccessKeyId=AKIAJDZUQ3CGZ73M2ZIQ&Action=DescribeAvailabilityZones&
  SignatureMethod=HmacSHA1&SignatureVersion=2&Timestamp=201103-
  10T16%3A47%3A22Z&Version=2010-11-15

Encrypt the Input String – Create the Signature

Along with the sender’s private key, the string to sign is the input value for the encryption algorithm. Encryption can use either the HMACSha1 or the HMACSha256 algorithm. The .NET System.Security.Cryptography namespace supports both algorithms. In either case, the procedure uses the sender’s private key to initialize a signer object that computes a hash of the input string. The final step base64 encodes the hash and returns it as the signature value.

The following method shows the HMACSha1 encryption:

public static string GetAWS3_SHA1AuthorizationValue(string AWSAccessKeyId, 
              string AWSSecretAccessKey, string stringToSign) 
{ 
    System.Security.Cryptography.HMACSHA1 MySigner = 
           new System.Security.Cryptography.HMACSHA1(
           System.Text.Encoding.UTF8.GetBytes(AWSSecretAccessKey)); 

    string SignatureValue = Convert.ToBase64String(
           MySigner.ComputeHash(System.Text.Encoding.UTF8.GetBytes(stringToSign))); 
 
    return SignatureValue; 
}

The following method shows the HMACSha256 encryption.

public static string GetAWS3_SHA256AuthorizationValue(string AWSAccessKeyId, 
              string AWSSecretAccessKey, string AmzDate) 
{ 
    System.Security.Cryptography.HMACSHA256 MySigner = 
      new System.Security.Cryptography.HMACSHA256(
      System.Text.Encoding.UTF8.GetBytes(AWSSecretAccessKey)); 

    string SignatureValue = Convert.ToBase64String(
           MySigner.ComputeHash(System.Text.Encoding.UTF8.GetBytes(AmzDate))); 
 
    return SignatureValue; 
}

All that remains to complete the request URL is to prepend the host URL to the ordered and encoded parameters and append the signature value. The following line of code calls several methods described previously to order and URL encode the request parameters and then adds the Signature parameter name and value.

string queryString = "https://ec2.amazonaws.com/?" + 
       GetSortedParamsAsString(sortedRequestPars, false) + "&Signature=" + signature;

The result is a request URL with authentication signature: https://ec2.amazonaws.com/?AWSAccessKeyId=AKIAJDZUQ3CGZ73M2ZIQ&Action= DescribeAvailabilityZones&SignatureMethod=HmacSHA1&SignatureVersion=2&Timestamp= 2011-03-10T16:55:46Z&Version=2010-11-15&Signature=4IhhwNE9JBJULbae97vbxRmkV0I=.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Michael Dodaro
Technical Writer Steyer Associates at Microsoft Research
United States United States
http://www.iis.net/community/default.aspx?tabid=320&g=6&CreatedBy=mike%20dodaro
 
http://mikedodaro.net
Follow on   Twitter

Comments and Discussions

 
QuestionImportant [modified] PinmemberNuno Correia24-Apr-14 0:52 
BugPlease provide a sample secret key (even one that doesn't work) PinmemberBrandon D'Imperio4-Oct-13 9:11 
QuestionSignature also needs URL Encoding PinmemberApache_Solutions29-Jul-11 1:14 
AnswerRe: Signature also needs URL Encoding PinmemberMichael Dodaro20-Feb-12 15:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 30 Mar 2011
Article Copyright 2011 by Michael Dodaro
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid