This example shows how to upload an object to Object Storage Service (OSS) using the PutObject API from C# with a manually computed HMAC-SHA1 Authorization header, without using the OSS SDK.
For production use, use the OSS SDK, which handles signing automatically. This example is for cases where you need direct REST API access or want to understand the signing mechanism.
Prerequisites
Before you begin, ensure that you have:
An Alibaba Cloud account with an AccessKey ID and AccessKeySecret
An OSS bucket
Visual Studio 2017 with .NET Framework 4.0
How it works
The signing flow has three stages:
Build the StringToSign — Construct a canonical string from the HTTP method, headers, and resource path.
Compute the signature — Sign the StringToSign with your AccessKeySecret using HMAC-SHA1, then Base64-encode the result.
Send the request — Include the signature in the
Authorizationheader and send the PUT request to OSS.
Stage 1: Build the StringToSign
The StringToSign for a PutObject request follows this format:
PUT\n
\n
{Content-Type}\n
{Date (GMT)}\n
/{BucketName}/{ObjectName}| Field | Required | Description | Example |
|---|---|---|---|
| HTTP verb | Yes | The HTTP method in uppercase | PUT |
| Content-MD5 | No | MD5 hash of the request body. Leave blank if not provided — the field must still appear as an empty line. | *(empty)* |
| Content-Type | Yes | MIME type of the object | application/json |
| Date | Yes | Current time in GMT (RFC 1123 format). OSS rejects requests where the timestamp differs from server time by more than 15 minutes. | Thu, 19 Mar 2026 14:00:00 GMT |
| Canonicalized resource | Yes | /BucketName/ObjectName | /mybucket/mytest/1.txt |
The StringToSign for this example is:
string str = varb + "\n\n" + content_type + "\n" + timeGmt + "\n/" + BucketName + "/" + objectName;The \n\n represents the HTTP verb line ending followed by an empty Content-MD5 field. Both newlines are required — omitting either one produces a wrong signature and a 403 error.Stage 2: Compute the signature
Sign the StringToSign using HMAC-SHA1 with your AccessKeySecret, then Base64-encode the result:
public static string HmacSha1Sign(string secret, string strOrgData)
{
var hmacsha1 = new HMACSHA1(Encoding.UTF8.GetBytes(secret));
var dataBuffer = Encoding.UTF8.GetBytes(strOrgData);
var hashBytes = hmacsha1.ComputeHash(dataBuffer);
return Convert.ToBase64String(hashBytes);
}The resulting Authorization header has this format:
Authorization: OSS {AccessKey}:{Signature}For example:
Authorization: OSS <your-access-key>:<base64-encoded-hmac-sha1-signature>Stage 3: Send the request
The following example sends a PutObject request with a computed Authorization header. It was tested with Visual Studio 2017 and .NET Framework 4.0.
Replace the placeholder values before running:
| Placeholder | Description | Example |
|---|---|---|
AccessKey | Your Alibaba Cloud AccessKey ID | LTAI5tXxx |
AccessKeySecret | Your Alibaba Cloud AccessKeySecret | *(your secret)* |
Endpoint | OSS endpoint for the bucket's region | oss-cn-hangzhou.aliyuncs.com |
BucketName | Name of your OSS bucket | my-bucket |
objectName | Path and name of the object to create | mytest/1.txt |
using System;
using System.Security.Cryptography;
using System.Text;
using System.Net;
using System.IO;
using System.Reflection;
namespace Aliyun.OSS.Samples
{
class Program
{
//Your AccessKey
public static string AccessKey = "xx";
//Your AccessKeySecret
public static string AccessKeySecret = "xx";
//bucket endpoint
public static string Endpoint = "oss-cn-hangzhou.aliyuncs.com";
public static string BucketName = "xx";
public static string objectName = "mytest/1.txt";
public static string HmacSha1Sign(string secret, string strOrgData)
{
var hmacsha1 = new HMACSHA1(Encoding.UTF8.GetBytes(secret));
var dataBuffer = Encoding.UTF8.GetBytes(strOrgData);
var hashBytes = hmacsha1.ComputeHash(dataBuffer);
return Convert.ToBase64String(hashBytes);
}
public static string HttpRequest(string url, string data, string sign, string contentType, string time1)
{
byte[] datas = System.Text.Encoding.GetEncoding("UTF-8").GetBytes(data);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "PUT";
request.Timeout = 150000;
request.Headers.Add("Authorization", "OSS " + AccessKey + ":" + sign);
CallPrivateMethod(request, "SetSpecialHeaders", "Date", time1);
request.ContentType = contentType;
Stream requestStream = null;
string responseStr = null;
try
{
if (datas != null)
{
request.ContentLength = datas.Length;
requestStream = request.GetRequestStream();
requestStream.Write(datas, 0, datas.Length);
requestStream.Close();
}
else
{
request.ContentLength = 0;
}
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
responseStr = response.Headers.GetValues("x-oss-request-id")[0];
//responseStr = sr.ReadToEnd();
}
catch (Exception)
{
Console.WriteLine("error");
}
finally
{
request = null;
requestStream = null;
}
return responseStr;
}
public static string ToGMTString()
{
return DateTime.Now.ToUniversalTime().ToString("r");
}
public static void CallPrivateMethod(object instance, string name, params object[] param)
{
BindingFlags flag = BindingFlags.Instance | BindingFlags.NonPublic;
Type type = instance.GetType();
MethodInfo method = type.GetMethod(name, flag);
method.Invoke(instance, param);
}
static void Main(string[] args)
{
string varb = "PUT";
string content_type = "application/json";
string timeGmt = ToGMTString();
string str = varb + "\n\n" + content_type + "\n" + timeGmt + "\n/" + BucketName + "/" + objectName;
string signature = HmacSha1Sign(AccessKeySecret, str);
string url = "http://" + BucketName + "." + Endpoint + "/" + objectName;
string data = "{ \"key\":\"this is a oss's test\"}";
string result = HttpRequest(url, data, signature, content_type, timeGmt);
Console.WriteLine("requestId: " + result);
}
}
}A successful upload prints the x-oss-request-id value from the response. If the call fails, the console prints error — check the Authorization header construction and confirm the Date header is within 15 minutes of OSS server time.
TheDateheader is set via reflection usingCallPrivateMethodbecause .NET'sHttpWebRequestdoes not expose theDateheader through its public API.
What's next
Include signatures in the Authorization header — Full reference for the OSS V1 signature specification, including parameter details and worked examples.