I am having a problem with the hash encoding for signing version 2 of the ec2 API.
Pay attention to my version 1. Signing hashing works fine, but it depreciates, and I will need to upgrade to version 2. So, firstly, here is the code that works ...
Options
- this is just a dictionary, I just need to sort the parameters by key and add each pair of values ββwithout separators, and then the hash of this line against my key. (again, note that this works great)
private string GetVersion1Sig() { string sig = string.Join(string.Empty, parameters.OrderBy(vp => vp.Key).Select(p => string.Format("{0}{1}", p.Key, p.Value)).ToArray()); UTF8Encoding encoding = new UTF8Encoding(); HMACSHA256 signature = new HMACSHA256(encoding.GetBytes(_secretAccessKey)); byte[] hash = signature.ComputeHash(encoding.GetBytes(sig)); string result = Convert.ToBase64String(hash); return result; }
Now, with version 2 there are some changes, here is the doco from the API Developer's Guide ...
- Create the canonical query string that you will need later in this procedure:
but. Sort UTF-8 query string components by parameter name with natural byte order. Parameters can be obtained from a GET URI or from a POST body (when the Content-Type is application / x-www-form-urlencoded).
b. The URL encodes the name and parameter values ββin accordance with the following rules:
β’ Do not encode the URLs of any of the unreserved characters defined in RFC 3986. These undeserved characters are: AZ, az, 0-9, hyphen (-), underscore (_), period (.), And tilde (~).
β’ Percentage encodes all other characters with% XY, where X and Y are hexadecimal characters 0-9 and upper case AF.
β’ Percentage encodes extended UTF-8 characters in the form% XY% ZA ....
β’ Percentage encodes a whitespace as% 20 (and not +, as general coding schemes do).
Note
Currently, all AWS parameter names use unreserved characters, so you do not need to encode them. However, you can include code to handle parameter names that use reserved characters for possible future use.
from. Separate the names of the encoded parameters from their encoded values ββwith an equal sign (=) (ASCII character 61), even if the parameter value is empty.
e. Separate name-value pairs with ampersand (s) (ASCII code 38).
- Create a signature line according to the following pseudogram ("\ n" is an ASCII new line). StringToSign = HTTPVerb + "\ n" + ValueOfHostHeaderInLowercase + "\ n" + HTTPRequestURI + "\ n" +
CanonicalizedQueryString The HTTPRequestURI component is a component of the absolute HTTP URI path to, but not including, the query string. If HTTPRequestURI is empty, use a forward slash (/). - Compute the HMAC compliant with RFC 2104, the string you just created, the secret access key as the key, and SHA256 or SHA1 as the hash algorithm. For more information, go to http://www.rfc.net/rfc2104.html .
- Convert the resulting value to base64.
- Use the resulting value as the value of the signature request parameter.
So, I have a....
private string GetSignature() { StringBuilder sb = new StringBuilder(); sb.Append("GET\n"); sb.Append("ec2.amazonaws.com\n"); sb.Append("/\n"); sb.Append(string.Join("&", parameters.OrderBy(vp => vp.Key, new CanonicalizedDictCompare()).Select(p => string.Format("{0}={1}", HttpUtility.UrlEncode(p.Key), HttpUtility.UrlEncode(p.Value))).ToArray())); UTF8Encoding encoding = new UTF8Encoding(); HMACSHA256 signature = new HMACSHA256(encoding.GetBytes(_secretAccessKey)); byte[] hash = signature.ComputeHash(encoding.GetBytes(sb.ToString())); string result = Convert.ToBase64String(hash); return result; }
for completeness, here is the implementation of IComparer ....
internal class CanonicalizedDictCompare : IComparer<string> { #region IComparer<string> Members public int Compare(string x, string y) { return string.CompareOrdinal(x, y); } #endregion }
As far as I can tell, I have done everything I need to do for this hash, but I keep getting an error message from the server, telling me that my signature is incorrect. Help...