Skip to content

Instantly share code, notes, and snippets.

@yvanin
Last active March 5, 2024 20:37
Show Gist options
  • Star 29 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save yvanin/0bdf68c1139ad698519e to your computer and use it in GitHub Desktop.
Save yvanin/0bdf68c1139ad698519e to your computer and use it in GitHub Desktop.
This C# code calculates a request signature using Version 4 signing process. It was developed for and tested on Amazon SQS requests, so it does not cover every scenario for the other services, e.g. multipart uploads are not supported. Nevertheless, it's simple and independent single class that can be easily embedded into other projects. .NET Fra…
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
namespace AwsV4SignatureCalculator
{
public class AwsV4SignatureCalculator
{
public const string Iso8601DateTimeFormat = "yyyyMMddTHHmmssZ";
public const string Iso8601DateFormat = "yyyyMMdd";
private readonly string _awsSecretKey;
private readonly string _service;
private readonly string _region;
/// <param name="service">AWS service name, e.g. "sqs" or "ec2"</param>
/// <param name="region">AWS region to send requests to</param>
public AwsV4SignatureCalculator(string awsSecretKey, string service, string region = null)
{
_awsSecretKey = awsSecretKey;
_service = service;
_region = region ?? "us-east-1";
}
/// <summary>
/// Calculates request signature string using Signature Version 4.
/// http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
/// </summary>
/// <param name="request">Request</param>
/// <param name="signedHeaders">Canonical headers that are a part of a signing process</param>
/// <param name="requestDate">Date and time when request takes place</param>
/// <returns>Signature</returns>
public string CalculateSignature(HttpRequestMessage request, string[] signedHeaders, DateTime requestDate)
{
signedHeaders = signedHeaders.Select(x => x.Trim().ToLowerInvariant()).OrderBy(x => x).ToArray();
var canonicalRequest = GetCanonicalRequest(request, signedHeaders);
var stringToSign = GetStringToSign(requestDate, canonicalRequest);
return GetSignature(requestDate, stringToSign);
}
// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
private static string GetCanonicalRequest(HttpRequestMessage request, string[] signedHeaders)
{
var canonicalRequest = new StringBuilder();
canonicalRequest.AppendFormat("{0}\n", request.Method.Method);
canonicalRequest.AppendFormat("{0}\n", request.RequestUri.AbsolutePath);
canonicalRequest.AppendFormat("{0}\n", GetCanonicalQueryParameters(request.RequestUri.ParseQueryString()));
canonicalRequest.AppendFormat("{0}\n", GetCanonicalHeaders(request, signedHeaders));
canonicalRequest.AppendFormat("{0}\n", String.Join(";", signedHeaders));
canonicalRequest.Append(GetPayloadHash(request));
return canonicalRequest.ToString();
}
private static string GetCanonicalQueryParameters(NameValueCollection queryParameters)
{
StringBuilder canonicalQueryParameters = new StringBuilder();
foreach (string key in queryParameters)
{
canonicalQueryParameters.AppendFormat("{0}={1}&", Utils.UrlEncode(key),
Utils.UrlEncode(queryParameters[key]));
}
// remove trailing '&'
if (canonicalQueryParameters.Length > 0)
canonicalQueryParameters.Remove(canonicalQueryParameters.Length - 1, 1);
return canonicalQueryParameters.ToString();
}
private static string GetCanonicalHeaders(HttpRequestMessage request, IEnumerable<string> signedHeaders)
{
var headers = request.Headers.ToDictionary(x => x.Key.Trim().ToLowerInvariant(),
x => String.Join(" ", x.Value).Trim());
if (request.Content != null)
{
var contentHeaders = request.Content.Headers.ToDictionary(x => x.Key.Trim().ToLowerInvariant(),
x => String.Join(" ", x.Value).Trim());
foreach (var contentHeader in contentHeaders)
{
headers.Add(contentHeader.Key, contentHeader.Value);
}
}
var sortedHeaders = new SortedDictionary<string, string>(headers);
StringBuilder canonicalHeaders = new StringBuilder();
foreach (var header in sortedHeaders.Where(header => signedHeaders.Contains(header.Key)))
{
canonicalHeaders.AppendFormat("{0}:{1}\n", header.Key, header.Value);
}
return canonicalHeaders.ToString();
}
private static string GetPayloadHash(HttpRequestMessage request)
{
var payload = request.Content != null ? request.Content.ReadAsStringAsync().Result : "";
return Utils.ToHex(Utils.Hash(payload));
}
// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
private string GetStringToSign(DateTime requestDate, string canonicalRequest)
{
var dateStamp = requestDate.ToString(Iso8601DateFormat, CultureInfo.InvariantCulture);
var scope = string.Format("{0}/{1}/{2}/{3}", dateStamp, _region, _service, "aws4_request");
var stringToSign = new StringBuilder();
stringToSign.AppendFormat("AWS4-HMAC-SHA256\n{0}\n{1}\n",
requestDate.ToString(Iso8601DateTimeFormat, CultureInfo.InvariantCulture),
scope);
stringToSign.Append(Utils.ToHex(Utils.Hash(canonicalRequest)));
return stringToSign.ToString();
}
// http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
private string GetSignature(DateTime requestDate, string stringToSign)
{
var kSigning = GetSigningKey(requestDate);
return Utils.ToHex(Utils.GetKeyedHash(kSigning, stringToSign));
}
private byte[] GetSigningKey(DateTime requestDate)
{
var dateStamp = requestDate.ToString(Iso8601DateFormat, CultureInfo.InvariantCulture);
var kDate = Utils.GetKeyedHash("AWS4" + _awsSecretKey, dateStamp);
var kRegion = Utils.GetKeyedHash(kDate, _region);
var kService = Utils.GetKeyedHash(kRegion, _service);
return Utils.GetKeyedHash(kService, "aws4_request");
}
private static class Utils
{
private const string ValidUrlCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
public static string UrlEncode(string data)
{
StringBuilder encoded = new StringBuilder();
foreach (char symbol in Encoding.UTF8.GetBytes(data))
{
if (ValidUrlCharacters.IndexOf(symbol) != -1)
{
encoded.Append(symbol);
}
else
{
encoded.Append("%").Append(string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int) symbol));
}
}
return encoded.ToString();
}
public static byte[] Hash(string value)
{
return new SHA256CryptoServiceProvider().ComputeHash(Encoding.UTF8.GetBytes(value));
}
public static byte[] GetKeyedHash(string key, string value)
{
return GetKeyedHash(Encoding.UTF8.GetBytes(key), value);
}
public static byte[] GetKeyedHash(byte[] key, string value)
{
KeyedHashAlgorithm mac = new HMACSHA256(key);
mac.Initialize();
return mac.ComputeHash(Encoding.UTF8.GetBytes(value));
}
public static string ToHex(byte[] data)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.Length; i++)
{
sb.Append(data[i].ToString("x2", CultureInfo.InvariantCulture));
}
return sb.ToString();
}
}
}
}
@vashni
Copy link

vashni commented Oct 25, 2016

Hi,

It's a neat code. Thank you.

But I had a problem with "GetSigningKey" method in the end and I solved it by using another encryption method given by AWS and encoding the secret key did the trick.

private byte[] GetSigningKey(DateTime requestDate)
{
byte[] kSecret = Encoding.UTF8.GetBytes(("AWS4" + ssAWSSecretKey).ToCharArray());
byte[] kDate = HmacSHA256(dateStamp, kSecret);
byte[] kRegion = HmacSHA256(ssRegion, kDate);
byte[] kService = HmacSHA256(ssServiceName, kRegion);
byte[] kSigning = HmacSHA256("aws4_request", kService);
return kSigning ;
}

static byte[] HmacSHA256(String data, byte[] key)
{
String algorithm = "HmacSHA256";
KeyedHashAlgorithm kha = KeyedHashAlgorithm.Create(algorithm);
kha.Key = key;
return kha.ComputeHash(Encoding.UTF8.GetBytes(data));
}

@netespar
Copy link

netespar commented Mar 13, 2018

Hi I have spent days trying to figure out how to start this API, but I still dont get, their documentation is not clear give wrong samples and have bunch of links that dont take you to anywhere, please if you could help me, how can I get the service and the region to use your class?

public AwsV4SignatureCalculator(string awsSecretKey, string service, string region = null)

@VatsalBhesaniya
Copy link

Hi,
I am understanding every single method in you code and it is perfect according to amazon.
But i can not figure out how to construct first two parameters in CalculateSignature method.
public string CalculateSignature(HttpRequestMessage request, string[] signedHeaders, DateTime requestDate)
{
}
Please help me with code or any documentation.

@yvanin
Copy link
Author

yvanin commented Sep 3, 2019

you get HttpRequestMessage request in your ASP.NET Web API message handler (if you are not using Web API you should change the code - you basically need HTTP request URI, headers and content).

as for signed (canonical) headers documentation says the following:

The canonical headers consist of a list of all the HTTP headers that you are including with the signed request.   
For HTTP/1.1 requests, you must include the host header at a minimum.  
For HTTP/2 requests, you must include the :authority header instead of the host header.  
Different services might require other headers. 

@mufaddal-shafqat
Copy link

mufaddal-shafqat commented Jan 1, 2020

A caller to this class would have completed the post. Can you please provide the caller function.

@AndrewMaisey
Copy link

AndrewMaisey commented Jan 15, 2020

Hello, please see the following for a fully worked example which calls a custom AWS4 authenticated web service. This shows the caller method and signing functions (I have simplified it somewhat into a linear program for this example).

using System;
using System.Web;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;

namespace TestAPMAWSV4Sign
{
    /// <summary>
    /// Full process as described here. https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
    /// Supports query strings on request Uri.
    /// </summary>
    public class Program
    {
        #region Variables
		
        //public static string url = "https://aws-url-host.com/v1/employees";
        // With query string - as example
		
        public static string url = "https://aws-url-host.com/v1/reports/MyService?EndDateTime=2020-01-13&Siteid=63&StartDateTime=2019-09-20";
        public static string accessKey = "YOURAWS-ACCESSKEY-INHERE";
        public static string secretkey = "YOURAWS-SECRETKEY-INHERE";
        public static string awsRegion = "eu-west-2";
        public static string awsServiceName = "execute-api";
        public static string xApiKey = "APIKEY-TOCALLAWSFUNCTION-IFNEEDED";
        #endregion

        #region Properties
        #endregion

        #region Constructors
        #endregion

        #region Methods
        public static void Main(string[] args)
        {
            // 0. Prepare request message.
            HttpRequestMessage msg = new HttpRequestMessage(HttpMethod.Get, url);
            msg.Headers.Host = msg.RequestUri.Host;

            // Get and save dates ready for further use.
            DateTimeOffset utcNowSaved = DateTimeOffset.UtcNow;
            string amzLongDate = utcNowSaved.ToString("yyyyMMddTHHmmssZ");
            string amzShortDate = utcNowSaved.ToString("yyyyMMdd");

            // Add to headers. 
            msg.Headers.Add("x-amz-date", amzLongDate);
            msg.Headers.Add("x-api-key", xApiKey); // My API call needs an x-api-key passing also for function security.

            // **************************************************** SIGNING PORTION ****************************************************
            // 1. Create Canonical Request            
            var canonicalRequest = new StringBuilder();
            canonicalRequest.Append(msg.Method + "\n");
            canonicalRequest.Append(string.Join("/", msg.RequestUri.AbsolutePath.Split('/').Select(Uri.EscapeDataString)) + "\n");

            canonicalRequest.Append(GetCanonicalQueryParams(msg) + "\n"); // Query params to do.

            var headersToBeSigned = new List<string>();
            foreach (var header in msg.Headers.OrderBy(a => a.Key.ToLowerInvariant(), StringComparer.OrdinalIgnoreCase))
            {
                canonicalRequest.Append(header.Key.ToLowerInvariant());
                canonicalRequest.Append(":");
                canonicalRequest.Append(string.Join(",", header.Value.Select(s => s.Trim())));
                canonicalRequest.Append("\n");
                headersToBeSigned.Add(header.Key.ToLowerInvariant());
            }
            canonicalRequest.Append("\n");

            var signedHeaders = string.Join(";", headersToBeSigned);
            canonicalRequest.Append(signedHeaders + "\n");
            canonicalRequest.Append("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); // Signature for empty body.
            // Hash(msg.Content.ReadAsByteArrayAsync().Result);

            // 2. String to sign.            
            string stringToSign = "AWS4-HMAC-SHA256" + "\n" + amzLongDate + "\n" + amzShortDate + "/" + awsRegion + "/" + awsServiceName + "/aws4_request" + "\n" + Hash(Encoding.UTF8.GetBytes(canonicalRequest.ToString()));

            // 3. Signature with compounded elements.
            var dateKey = HmacSha256(Encoding.UTF8.GetBytes("AWS4" + secretkey), amzShortDate);
            var dateRegionKey = HmacSha256(dateKey, awsRegion);
            var dateRegionServiceKey = HmacSha256(dateRegionKey, awsServiceName);
            var signingKey = HmacSha256(dateRegionServiceKey, "aws4_request");

            var signature = ToHexString(HmacSha256(signingKey, stringToSign.ToString()));

            // **************************************************** END SIGNING PORTION ****************************************************
            
            // Add the Header to the request.
            var credentialScope = amzShortDate + "/" + awsRegion + "/" + awsServiceName + "/aws4_request";
            msg.Headers.TryAddWithoutValidation("Authorization", "AWS4-HMAC-SHA256 Credential=" + accessKey + "/" + credentialScope + ", SignedHeaders=" + signedHeaders + ", Signature=" + signature);
            
            // Invoke the request with HttpClient.
            HttpClient client = new HttpClient();
            HttpResponseMessage result = client.SendAsync(msg).Result;
            if (result.IsSuccessStatusCode)
            {
                // Inspect the result and payload returned.
                Console.WriteLine(result.Headers);
                Console.WriteLine(result.Content.ReadAsStringAsync().Result);
                // Wait on user.
                Console.ReadLine();
            }
        }

        /// <summary>
        /// Gets the query string parameters.
        /// </summary>
        /// <param name="request">(HttpRequestMessage) Request with Headers in.</param>
        /// <returns>(string) Params in order.</returns>
        private static string GetCanonicalQueryParams(HttpRequestMessage request)
        {
            var values = new SortedDictionary<string, string>();

            var querystring = HttpUtility.ParseQueryString(request.RequestUri.Query);
            foreach (var key in querystring.AllKeys)
            {
                if (key == null)//Handles keys without values
                {
                    values.Add(Uri.EscapeDataString(querystring[key]), $"{Uri.EscapeDataString(querystring[key])}=");
                }
                else
                {
                    // Escape to upper case. Required.
                    values.Add(Uri.EscapeDataString(key), $"{Uri.EscapeDataString(key)}={Uri.EscapeDataString(querystring[key])}");
                }
            }
            // Put in order - this is important.
            var queryParams = values.Select(a => a.Value);
            return string.Join("&", queryParams);
        }

        /// <summary>
        /// SHA 265.
        /// </summary>
        /// <param name="bytesToHash"></param>
        /// <returns>(string) Hashed result.</returns>
        public static string Hash(byte[] bytesToHash)
        {            
            return ToHexString(SHA256.Create().ComputeHash(bytesToHash));
        }

        /// <summary>
        /// To hex string.
        /// </summary>
        /// <param name="array"> Bytes to make hex.</param>
        /// <returns>(string) As Hex.</returns>
        private static string ToHexString(IReadOnlyCollection<byte> array)
        {
            var hex = new StringBuilder(array.Count * 2);
            foreach (var b in array)
            {
                hex.AppendFormat("{0:x2}", b);
            }
            return hex.ToString();
        }

        /// <summary>
        /// Encryption method for AWS.
        /// </summary>
        /// <param name="key">(Byte []) Key.</param>
        /// <param name="data">(string) Data.</param>
        /// <returns>(Byte []) Hmac result.</returns>
        private static byte[] HmacSha256(byte[] key, string data)
        {
            return new HMACSHA256(key).ComputeHash(Encoding.UTF8.GetBytes(data));
        }
    }
    #endregion
}

@tgyogesh
Copy link

tgyogesh commented Mar 4, 2020

Hi @AndrewMaisey , It works for me perfectly, Thanks for the code. But when i add msg.content and send put request, it throws the below error message. I almost tried all the ways but i couldn't make it work. Could you please help me out.
Connection: keep-alive
x-amzn-RequestId: ***************************************
Access-Control-Allow-Origin: *

{"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."}

@AndrewMaisey
Copy link

Hi @tgyogesh, glad you have been able to get the code running.

With respect to your payload, I don't know without looking at your code but have a check of step 6 in:

https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html

specifically lowercase;

HashedPayload = Lowercase(HexEncode(Hash(requestPayload)))

Hope you have success.

@MisterC1974
Copy link

Hi @AndrewMaisey
Thank you for the code, I used it and converted from C# to VB.net to create a signed request to Amazon Gateway without issue. As above however, when changing the httpmethod from .get to .put and adding msg.content to a json string, the code fails. Do you or does anyone have a working C# put request with the excellent signing functionality.
Many thanks

@AndrewMaisey
Copy link

AndrewMaisey commented May 29, 2020

@MisterC1974
Copy link

Hi @AndrewMaisey
I just wanted to come back to you and say I managed to get your code going, and in-case anyone is wondering. I amended the following lines:-
Changed method to 'Put' from 'Get' HttpRequestMessage msg = new HttpRequestMessage(HttpMethod.Put, url);

Added content type and json example on msg.content

msg.Content = new StringContent("{ "json name":json value,"json name":"json value"}",
Encoding.UTF8,
"application/json");//CONTENT-TYPE header

Calculate hash for canonical request

canonicalRequest.Append(Hash(msg.Content.ReadAsByteArrayAsync().Result));

Works flawlessly!

@dmontilla
Copy link

Thanks for this code, it really helpme a lot, it got me confuse the documentation from amazon, thanks!!!

@sachin-kharmale
Copy link

can anyone give me working example for aws post request content should be json

@philipogorman
Copy link

@myvanin thanks!

@priyankaj1910
Copy link

Thanks, above code helped me lot. I also need to add auth data to URL. Currently, in the above code its sent to Header. When I send that I am getting signature doesn't match error

@priyankaj1910
Copy link

priyankaj1910 commented Mar 26, 2021

UrlToSend = UrlToSend + "?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=" + ApiAuthInfoAccessKey + "%2F" + amzShortDate + "%2F" + ApiAuthInfoAWSRegion + "%2F" + ApiAuthInfoAWSServiceName + "%2Faws4_request&X-Amz-Date=" + amzLongDate + "&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=" + Signature + "";

This is the URL I am sending

@priyankaj1910
Copy link

priyankaj1910 commented Mar 26, 2021

Here is my code for AWS api with adding auth to URL

using ApiStrong.DAL.DataModel;
using ApiStrong.Library.HelperMethod;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ApiStrong.DAL.Repository;
using System.Net.Http;
using System.IO;
using System.Security.Cryptography;
using System.Web;
using System.Net;

namespace APIStrongExecuteQueueUtility
{
class Program
{

    //public static string url = "https://aws-url-host.com/v1/employees";
    // With query string - as example

    public static string url = "YOUR URL";
    public static string accessKey = "YOURAWS-ACCESSKEY-INHERE";
    public static string secretkey = "YOURAWS-SECRETKEY-INHERE";
    public static string awsRegion = "us-east-1";
    public static string awsServiceName = "execute-api";
    public static string xApiKey = "APIKEY-TOCALLAWSFUNCTION-IFNEEDED";
    static void Main(string[] args)
    {

        ExcuteAWSAPI();
    }

 


    public static void ExcuteAWSAPI()
    {
        // 0. Prepare request message.

        // Get and save dates ready for further use.
        DateTimeOffset utcNowSaved = DateTimeOffset.UtcNow;
        string amzLongDate = utcNowSaved.ToString("yyyyMMddTHHmmssZ");
        string amzShortDate = utcNowSaved.ToString("yyyyMMdd");
        Uri uri = new Uri(url);


        // **************************************************** SIGNING PORTION ****************************************************
        // 1. Create Canonical Request            
        var canonicalRequest = new StringBuilder();
        canonicalRequest.Append("POST" + "\n");
        canonicalRequest.Append(string.Join("/", uri.AbsolutePath.Split('/').Select(Uri.EscapeDataString)) + "\n");

        //canonicalRequest.Append("\n"); // Query params to do.
        canonicalRequest.Append("X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=" + accessKey + "%2Fus-east-1%2F" + awsServiceName + "%2Faws4_request&X-Amz-Date=" + amzLongDate + "&X-Amz-Expires=86400&X-Amz-SignedHeaders=host");
        canonicalRequest.Append("\n");
        var headersToBeSigned = new List<string>();

        canonicalRequest.Append("host");
        canonicalRequest.Append(":");
        canonicalRequest.Append(string.Join(",", uri.Host.Trim()));
        canonicalRequest.Append("\n");
        headersToBeSigned.Add("host");

        canonicalRequest.Append("\n");

        var signedHeaders = string.Join(";", headersToBeSigned);
        canonicalRequest.Append(signedHeaders + "\n");
        canonicalRequest.Append("UNSIGNED-PAYLOAD");
        //canonicalRequest.Append("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); // Signature for empty body.
        // Hash(msg.Content.ReadAsByteArrayAsync().Result);


        // 2. String to sign.            
        string stringToSign = "AWS4-HMAC-SHA256" + "\n" + amzLongDate + "\n" + amzShortDate + "/" + awsRegion + "/" + awsServiceName + "/aws4_request" + "\n" + Hash(Encoding.UTF8.GetBytes(canonicalRequest.ToString()));

        // 3. Signature with compounded elements.
        var dateKey = HmacSha256(Encoding.UTF8.GetBytes("AWS4" + secretkey), amzShortDate);
        var dateRegionKey = HmacSha256(dateKey, awsRegion);
        var dateRegionServiceKey = HmacSha256(dateRegionKey, awsServiceName);
        var signingKey = HmacSha256(dateRegionServiceKey, "aws4_request");

        var signature = ToHexString(HmacSha256(signingKey, stringToSign.ToString()));

        // **************************************************** END SIGNING PORTION ****************************************************


        // Invoke the request with HttpClient.
        HttpClient client = new HttpClient();

        url = url + "?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=" + accessKey + "%2F" + amzShortDate + "%2F" + awsRegion + "%2F" + awsServiceName + "%2Faws4_request&X-Amz-Date=" + amzLongDate + "&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=" + signature + "";

        HttpRequestMessage msg = new HttpRequestMessage(HttpMethod.Post, url);
        HttpResponseMessage result = client.SendAsync(msg).Result;
        var dd = result.Content.ReadAsStringAsync().Result;
        if (result.IsSuccessStatusCode)
        {
            // Inspect the result and payload returned.
            Console.WriteLine(result.Headers);
            Console.WriteLine(result.Content.ReadAsStringAsync().Result);
            // Wait on user.
            Console.ReadLine();
        }

    }

    /// <summary>
    /// Gets the query string parameters.
    /// </summary>
    /// <param name="request">(HttpRequestMessage) Request with Headers in.</param>
    /// <returns>(string) Params in order.</returns>
    private static string GetCanonicalQueryParams(HttpRequestMessage request)
    {
        var values = new SortedDictionary<string, string>();

        var querystring = HttpUtility.ParseQueryString(request.RequestUri.Query);
        foreach (var key in querystring.AllKeys)
        {
            if (key == null)//Handles keys without values
            {
                values.Add(Uri.EscapeDataString(querystring[key]), $"{Uri.EscapeDataString(querystring[key])}=");
            }
            else
            {
                // Escape to upper case. Required.
                values.Add(Uri.EscapeDataString(key), $"{Uri.EscapeDataString(key)}={Uri.EscapeDataString(querystring[key])}");
            }
        }
        // Put in order - this is important.
        var queryParams = values.Select(a => a.Value);
        return string.Join("&", queryParams);
    }

    /// <summary>
    /// SHA 265.
    /// </summary>
    /// <param name="bytesToHash"></param>
    /// <returns>(string) Hashed result.</returns>
    public static string Hash(byte[] bytesToHash)
    {
        return ToHexString(SHA256.Create().ComputeHash(bytesToHash));
    }

    /// <summary>
    /// To hex string.
    /// </summary>
    /// <param name="array"> Bytes to make hex.</param>
    /// <returns>(string) As Hex.</returns>
    private static string ToHexString(IReadOnlyCollection<byte> array)
    {
        var hex = new StringBuilder(array.Count * 2);
        foreach (var b in array)
        {
            hex.AppendFormat("{0:x2}", b);
        }
        return hex.ToString();
    }

    /// <summary>
    /// Encryption method for AWS.
    /// </summary>
    /// <param name="key">(Byte []) Key.</param>
    /// <param name="data">(string) Data.</param>
    /// <returns>(Byte []) Hmac result.</returns>
    private static byte[] HmacSha256(byte[] key, string data)
    {
        return new HMACSHA256(key).ComputeHash(Encoding.UTF8.GetBytes(data));
    }

    
}

}

@priyankaj1910
Copy link

@AndrewMaisey can u help?

@AndrewMaisey
Copy link

AndrewMaisey commented Mar 30, 2021

Suspect there is something amiss in the signature line @priyankaj1910
Check the values you are wrapping up into the signature. Other than that I don't know.
Perhaps the docs might guide: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html

@priyankaj1910
Copy link

Suspect there is something amiss in the signature line.
Check the values you are wrapping up into the signature. Other than that I don't know.
Perhaps the docs might guide: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html

Yes referred to this one https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html ... getting signature not matched . DId all steps properly not sure what is wrong

@OldWarrior3000
Copy link

Thanks @AndrewMaisey, worked great for me!

@rasputino
Copy link

Thanks @AndrewMaisey, after some hours wasting time with another libraries your code works really good!

@jchristn
Copy link

Thanks for posting this @yvanin - I put together a NuGet package which creates AWS V4 signatures and would love your feedback. Cheers, Joel

https://github.com/jchristn/AWSSignatureGenerator/

@priyankasrion
Copy link

Do we have any nuget packager for .net Framework 2.1 application to sign AWS API request?
https://github.com/aws-samples/sigv4a-signing-examples/blob/main/dotnet/ seems to be for dotnet core only.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment