4
votes

I'm creating Windows Phone 8.1 Store App with ability to upload some blobs into Azure Storage. I cannot use WindowsAzure.Storage lib (very weird), so I'm trying to use REST. I cannot figure out, what's wrong.

try
{
    string time = DateTime.Now.ToString("R", System.Globalization.CultureInfo.InvariantCulture);
    string tosign = "GET\n" +
                        "\n" +  //Content-Encoding
                        "\n" +  //Content-Language
                        "0\n" +  //Content-Length
                        "\n" +  //Content-MD5
                        "\n" +  //Content-Type
                        "\n" +  //Date
                        "\n" +  //If-modified-since
                        "\n" +  //If-match
                        "\n" +  //If-none-match
                        "\n" +  //If-unmodified-since
                        "\n" +  //Range
                        "x-ms-date:" + time + "\nx-ms-version:2015-02-21\n" +   //CanonicalizedHeaders
                        "/storage_name/\ncomp:list";    //CanonicalizedResource

    string hashKey = "DHpNuYG5MXhamfbKmFPClUlNi38QiM2uqIqz07pgvpv2gmXJRwxaMlcV05pFCYsrelGYKPed9QphyJ/YnUrh5w=="; //Primary access key

    MacAlgorithmProvider macAlgorithmProvider = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256);
    var messageBuffer = CryptographicBuffer.ConvertStringToBinary(tosign, BinaryStringEncoding.Utf8);
    IBuffer keyBuffer = CryptographicBuffer.ConvertStringToBinary(hashKey, BinaryStringEncoding.Utf8);
    CryptographicKey hmacKey = macAlgorithmProvider.CreateKey(keyBuffer);
    IBuffer signedMessage = CryptographicEngine.Sign(hmacKey, messageBuffer);
    string hashedString = CryptographicBuffer.EncodeToBase64String(signedMessage);

    var client = new HttpClient();
    Uri uri = new Uri("https://storage_name.blob.core.windows.net/?comp=list");
    client.DefaultRequestHeaders.Add("x-ms-date", time);
    client.DefaultRequestHeaders.Add("x-ms-version", "2015-02-21");
    client.DefaultRequestHeaders.Add("Authorization", "SharedKey storage_name:" + hashedString);
    var response = await client.GetAsync(uri);
}
catch(Exception ex)
{
    Debug.WriteLine(ex.ToString());
}

Error: Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.

How I can make it work?

2
I guess, I have an error in my crypto method.Vlad

2 Answers

3
votes

Your cryptography code is awry. :-)

Code Samples

What follows are working code samples that "call the HMAC-SHA256 algorithm on the UTF-8-encoded signature string and encode the result as Base64." In the below samples, the hashKey parameter is the "Primary Access Key" from Azure and the toSign parameter is the "Signature String" from the docs.

Encoding the Signature on Windows Phone 8.1

On Windows Phone 8.1 we use the Windows.Security.Cryptography namespace like this:

public string GetEncodedSignature(string toSign, string hashKey)
{
    // UTF-8-encoded signature string
    var utf8 = BinaryStringEncoding.Utf8;
    var msgBuffer = CryptographicBuffer.ConvertStringToBinary(toSign, utf8);

    // primary access key
    // note the use of DecodeFromBase64String
    var keyBuffer = CryptographicBuffer.DecodeFromBase64String(hashKey);

    // make the HMAC-SHA256 algorithm
    var alg = MacAlgorithmNames.HmacSha256;
    var objMacProv = MacAlgorithmProvider.OpenAlgorithm(alg);
    CryptographicHash hash = objMacProv.CreateHash(keyBuffer);

    // call the HMAC-SHA256 algorithm
    hash.Append(msgBuffer);
    IBuffer hashMsg = hash.GetValueAndReset();

    // retrieve the result!
    var result = CryptographicBuffer.EncodeToBase64String(hashMsg);

    return result;
}

Encoding the Signature in .NET

I also created an example for .NET that uses the System.Security.Cryptography namespace.

public static string GetEncodedSignature(string toSign, string hashKey)
{
    byte[] bytes;
    byte[] unicodeKey = Convert.FromBase64String(hashKey);
    var utf8encodedString = Encoding.UTF8.GetBytes(toSign);
    using (var hmac = new HMACSHA256(unicodeKey))
    {
        bytes = hmac.ComputeHash(utf8encodedString);
    }

    var signature = Convert.ToBase64String(bytes);
    return signature;
}

Both code samples produce the same encoded signature when given the same inputs. You can see one of them in more detail at this Fiddle that generates the HTTP Request.

See Also

MSDN Article Authentication for the Azure Storage Services

1
votes

Probably you've already worked this out but if not or for anyone else, I've just had the same issue and for me what fixed it is changing the Content-Length to "\n" (instead of "0\n"). From version 2015-02-21 and later you don't need to specify the length.

try
{
    string time = DateTime.Now.ToString("R", System.Globalization.CultureInfo.InvariantCulture);
    string tosign = "GET\n" +
                        "\n" +  //Content-Encoding
                        "\n" +  //Content-Language
                        "\n" +  //Content-Length
                        "\n" +  //Content-MD5
                        "\n" +  //Content-Type
                        "\n" +  //Date
                        "\n" +  //If-modified-since
                        "\n" +  //If-match
                        "\n" +  //If-none-match
                        "\n" +  //If-unmodified-since
                        "\n" +  //Range
                        "x-ms-date:" + time + "\nx-ms-version:2015-02-21\n" +   //CanonicalizedHeaders
                        "/storage_name/\ncomp:list";    //CanonicalizedResource

    string hashKey = "DHpNuYG5MXhamfbKmFPClUlNi38QiM2uqIqz07pgvpv2gmXJRwxaMlcV05pFCYsrelGYKPed9QphyJ/YnUrh5w=="; //Primary access key


   string hashedString = GetEncodedSignature(tosign, hashKey); //Shaun's answer method

    var client = new HttpClient();
    Uri uri = new Uri("https://storage_name.blob.core.windows.net/?comp=list");
    client.DefaultRequestHeaders.Add("x-ms-date", time);
    client.DefaultRequestHeaders.Add("x-ms-version", "2015-02-21");
    client.DefaultRequestHeaders.Add("Authorization", "SharedKey storage_name:" + hashedString);
    var response = await client.GetAsync(uri);
}
catch(Exception ex)
{
    Debug.WriteLine(ex.ToString());
}