Problem


Typically, we use the Azure SDK to upload files to Azure Blob Storage, as it is the easiest method supported by Microsoft. However, there are situations where the Azure SDK might not be available. So, how can we still upload files to Azure Blob Storage in such cases? In this post, I will guide you through the steps to upload files to your Azure Storage Account without using the Azure SDK.

Solution


Azure Blob Storage provides REST APIs that allow clients to perform all kinds of operations. This is the underlying mechanism behind the Azure SDK, there's no magic involved. This means that even without the Azure SDK, we can still use REST APIs to upload files. The basic steps are as follows:

  1. Generates an authorization header for HTTP PUT request.
    1. Constructs a string that includes HTTP verb, content length, content type, date, and other required headers.
    2. Uses HMAC-SHA256 to sign the string with the account key, then encodes it in base64.
    3. Please note, things may change when you read this post, please always refer to this Microsoft official document for latest updates.
  2. Constructs the necessary headers including the date, version, content-type, and blob type.
  3. Sends a PUT request to upload the file to Azure Blob Storage.
  4. Checks the response to determine if the upload was successful.

I've setup a storage account, named test996, with a container named goodstuff. Now I need to upload a file on my local disk named myfile.txt to the container.

I also need storage account key to generate authentication header in HTTP requests, which can be found in the "Access keys" blade of your Azure Storage Account instance.

C# Example

class Program
{
    static async Task Main(string[] args)
    {
        string accountName = "test996";
        string accountKey = "*******************";
        string filePath = "myfile.txt";
        string containerName = "goodstuff";
        string blobName = "myfile1.txt";
        string contentType = "text/plain";
        string date = DateTime.UtcNow.ToString("R");

        string urlPath = $"/{containerName}/{blobName}";
        string url = $"https://{accountName}.blob.core.windows.net{urlPath}";

        long contentLength = new FileInfo(filePath).Length;

        string authorizationHeader = GenerateAuthorizationHeader(accountName, accountKey, "PUT", contentLength, contentType, date, urlPath);

        Console.WriteLine($"Authorization: {authorizationHeader}");
        Console.WriteLine($"x-ms-date: {date}");

        using (HttpClient client = new HttpClient())
        {
            client.DefaultRequestHeaders.Add("x-ms-date", date);
            client.DefaultRequestHeaders.Add("x-ms-version", "2020-10-02");
            client.DefaultRequestHeaders.Add("Authorization", authorizationHeader);
            client.DefaultRequestHeaders.Add("x-ms-blob-type", "BlockBlob");

            using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                HttpContent content = new StreamContent(fileStream);
                content.Headers.Add("Content-Type", contentType);
                content.Headers.Add("Content-Length", contentLength.ToString());

                HttpResponseMessage response = await client.PutAsync(url, content);

                if (response.StatusCode == System.Net.HttpStatusCode.Created)
                {
                    Console.WriteLine("File uploaded successfully");
                }
                else
                {
                    Console.WriteLine($"File upload blow up sky high: {(int)response.StatusCode}");
                    Console.WriteLine(await response.Content.ReadAsStringAsync());
                }
            }
        }
    }

    static string GenerateAuthorizationHeader(string accountName, string accountKey, string verb, long contentLength, string contentType, string date, string urlPath)
    {
        string stringToSign = $"{verb}\n\n\n{contentLength}\n\n{contentType}\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:{date}\nx-ms-version:2020-10-02\n/{accountName}{urlPath}";
        byte[] key = Convert.FromBase64String(accountKey);
        using (HMACSHA256 hmac = new HMACSHA256(key))
        {
            byte[] signatureBytes = Encoding.UTF8.GetBytes(stringToSign);
            byte[] hash = hmac.ComputeHash(signatureBytes);
            string signedString = Convert.ToBase64String(hash);
            return $"SharedKey {accountName}:{signedString}";
        }
    }
}

Python Example

import hmac
import hashlib
import base64
from datetime import datetime
import os
import requests

def generate_authorization_header(account_name, account_key, verb, content_length, content_type, date, url_path):
    string_to_sign = f"{verb}\n\n\n{content_length}\n\n{content_type}\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:{date}\nx-ms-version:2020-10-02\n/{account_name}{url_path}"
    signed_string = base64.b64encode(hmac.new(base64.b64decode(account_key), string_to_sign.encode('utf-8'), hashlib.sha256).digest()).decode('utf-8')
    authorization_header = f"SharedKey {account_name}:{signed_string}"
    return authorization_header

account_name = 'test996'
account_key = '************'
file_path = 'myfile.txt'
container_name = 'goodstuff'
blob_name = 'myfile.txt'
content_length = str(os.path.getsize(file_path))
content_type = 'text/plain'
date = datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
url_path = f'/{container_name}/{blob_name}'
authorization_header = generate_authorization_header(account_name, account_key, 'PUT', content_length, content_type, date, url_path)

print(f"Authorization: {authorization_header}")
print(f"x-ms-date: {date}")

url = f"https://{account_name}.blob.core.windows.net{url_path}"
headers = {
    'x-ms-date': date,
    'x-ms-version': '2020-10-02',
    'Authorization': authorization_header,
    'Content-Type': content_type,
    'Content-Length': content_length,
    'x-ms-blob-type': 'BlockBlob'
}

with open(file_path, 'rb') as f:
    response = requests.put(url, headers=headers, data=f)

if response.status_code == 201:
    print("File uploaded successfully")
else:
    print(f"File upload blow up sky high: {response.status_code}")
    print(response.text)