Call Create File on Azure File Service REST Api - azure

I am trying to create a file in Azure File Service:
https://learn.microsoft.com/en-us/rest/api/storageservices/create-file
I've seen and read multiple threads about this issue, but I can't figure out what might be the problem in my case... Most likely I am missing some small thing in the request which I cannot find.
The error I am getting: The MAC signature found in the HTTP request (...) is not the same as any computed signature.
The signature string I've used:
PUT
\n
\n
\n
\n
text/plain // Content-Type
\n
\n
\n
\n
\n
\n
x-ms-content-length:1000
x-ms-date:Tue, 20 Apr 2021 19:23:30 GMT
x-ms-type:file
x-ms-version:2015-02-21
/account/share/test
Authorization header:
SharedKey account:XXXXXXXXXX
I've used the HMACSHA256 for hashing the authorization header and signature string, it all works when I use other endpoint (List Shares: https://learn.microsoft.com/en-us/rest/api/storageservices/list-shares), but I can't make it work for the file. I've seen this topic AZURE File Service - Upload PDF through REST API and I believe I am using very similar request, but with no success...
I appreciate any help : )
Edit:
I am not sure if I correctly set the content headers. For example, does the x-ms-content-length should be placed in the CanonicalizedHeaders string?
Edit2:
With regard to what Ivan Yang wrote, I made the code to work, but only when my CanonicalizedHeaders are built like that:
CanonicalizedHeaders := 'x-ms-content-length:1200'
+ LF + 'x-ms-date:' + UTCDateTimeText
+ LF + 'x-ms-file-attributes:Archive' + LF + 'x-ms-file-creation-time:Now' + LF + 'x-ms-file-last-write-time:Now' + LF + 'x-ms-file-permission:Inherit'
+ LF + 'x-ms-type:file' + LF + 'x-ms-version:2019-02-02';
If I have them in different order, then it crashes:
CanonicalizedHeaders := 'x-ms-date:' + UTCDateTimeText + LF +
'x-ms-content-length:1200' + LF +
'x-ms-version:2019-02-02' + LF +
'x-ms-file-attributes:Archive' + LF +
'x-ms-file-creation-time:Now' + LF +
'x-ms-file-last-write-time:Now' + LF +
'x-ms-file-permission:Inherit' + LF +
'x-ms-type:file';
How come does this make a difference?

Your signature string is incorrect(you're missing some "\n"). I'm using the x-ms-version:2019-02-02 instead of the older one x-ms-version:2015-02-21, and the correct signature string should look like this one:
"PUT\n"
+ "\n" // content encoding
+ "\n" // content language
+ "\n" // content length
+ "\n" // content md5
+ content_type + "\n" // content type
+ "\n" // date
+ "\n" // if modified since
+ "\n" // if match
+ "\n" // if none match
+ "\n" // if unmodified since
+ "\n" // range
+ "x-ms-content-length:" + content_length
+ "\nx-ms-date:" + dt.ToString("R")
+ "\nx-ms-file-attributes:Archive" + "\nx-ms-file-creation-time:Now" + "\nx-ms-file-last-write-time:Now" + "\nx-ms-file-permission:Inherit"
+ "\nx-ms-type:file" + "\nx-ms-version:" + apiversion + "\n" // headers
+ "/{0}/{1}/{2}", Account, FileShare, FileName);
Here is the c# code using create file api:
using System;
using System.Globalization;
using System.Net;
using System.Security.Cryptography;
namespace ConsoleApp25
{
class Program
{
static void Main(string[] args)
{
string Account = "storage_account_name";
string Key = "storage_account_key";
string FileShare = "file_share_name";
string FileName = "test555.txt";
string apiversion = "2019-02-02";
int content_length = 1200;
string content_type = "text/plain";
DateTime dt = DateTime.UtcNow;
string StringToSign = String.Format("PUT\n"
+ "\n" // content encoding
+ "\n" // content language
+ "\n" // content length
+ "\n" // content md5
+ content_type + "\n" // content type
+ "\n" // date
+ "\n" // if modified since
+ "\n" // if match
+ "\n" // if none match
+ "\n" // if unmodified since
+ "\n" // range
+ "x-ms-content-length:" + content_length
+ "\nx-ms-date:" + dt.ToString("R")
+ "\nx-ms-file-attributes:Archive" + "\nx-ms-file-creation-time:Now" + "\nx-ms-file-last-write-time:Now" + "\nx-ms-file-permission:Inherit"
+ "\nx-ms-type:file" + "\nx-ms-version:" + apiversion + "\n" // headers
+ "/{0}/{1}/{2}", Account, FileShare, FileName);
string auth = SignThis(StringToSign, Key, Account);
string method = "PUT";
string urlPath = string.Format("https://{0}.file.core.windows.net/{1}/{2}", Account, FileShare, FileName);
Uri uri = new Uri(urlPath);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = method;
request.ContentLength = 0;
request.Headers.Add("x-ms-content-length", $"{content_length}");
request.Headers.Add("Content-Type", content_type);
request.Headers.Add("x-ms-type", "file");
request.Headers.Add("x-ms-date", dt.ToString("R"));
request.Headers.Add("x-ms-version", apiversion);
request.Headers.Add("x-ms-file-attributes", "Archive"); //note it is case-sensitive.
request.Headers.Add("x-ms-file-permission", "Inherit");
request.Headers.Add("x-ms-file-creation-time", "Now");
request.Headers.Add("x-ms-file-last-write-time", "Now");
request.Headers.Add("Authorization", auth);
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
//read the response code
Console.WriteLine("the response is:" + response.StatusCode);
}
Console.WriteLine("**completed**");
Console.ReadLine();
}
private static String SignThis(String StringToSign, string Key, string Account)
{
String signature = string.Empty;
byte[] unicodeKey = Convert.FromBase64String(Key);
using (HMACSHA256 hmacSha256 = new HMACSHA256(unicodeKey))
{
Byte[] dataToHmac = System.Text.Encoding.UTF8.GetBytes(StringToSign);
signature = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));
}
String authorizationHeader = String.Format(
CultureInfo.InvariantCulture,
"{0} {1}:{2}",
"SharedKey",
Account,
signature);
return authorizationHeader;
}
}
}

Related

Signature did not match. String to sign used was rl

I am trying to create an Azure SAS to a blob. However, I am getting authentication failed error. Whatever I try, I am not able to construct a well formed stringToSign or signiture.
The error says:
"Signature did not match. String to sign used was rl 2021-03-11T08:08:46Z 2021-03-12T08:08:46Z /blob/{myAccountName}/quickstartcontainer/sampleFile2813061026464365578.txt 2020-02-10 b "
My stringToSign is :
String stringToSign= "rl\n"+
"2021-03-11T08:08:46Z" +"\n" +
"2021-03-12T08:08:46Z"+ "\n"+
"\n"+
"\n"+
"\n"+
"\n"+
2020-02-10"+
"\n"+"\n"+"\n"+"\n"+"\n";
String signature = getHMAC256(key, stringToSign);
My SAS token uri is :
String sasToken = "?sp=rl"
+ "&st=" + "2021-03-11T08:08:46Z"
+ "&se=" + "2021-03-12T08:08:46Z"
+ "&sv=" + "2020-02-10"
+ "&sr=b"
+ "&sig=" + URLEncoder.encode(signature, "UTF-8");
The encryption function is:
public static String computeHMac256(final String base64Key, final String stringToSign) {
try {
byte[] key = Base64.getDecoder().decode(base64Key);
Mac hmacSHA256 = Mac.getInstance("HmacSHA256");
hmacSHA256.init(new SecretKeySpec(key, "HmacSHA256"));
byte[] utf8Bytes = stringToSign.getBytes(StandardCharsets.UTF_8);
return Base64.getEncoder().encodeToString(hmacSHA256.doFinal(utf8Bytes));
} catch (NoSuchAlgorithmException | InvalidKeyException ex) {
throw new RuntimeException(ex);
}
}
How can I generate a well formed signiture for my sas uri?
According to the instructions provided here, your stringToSign should conform to the following structure:
StringToSign = signedPermissions + "\n" +
signedStart + "\n" +
signedExpiry + "\n" +
canonicalizedResource + "\n" +
signedIdentifier + "\n" +
signedIP + "\n" +
signedProtocol + "\n" +
signedVersion + "\n" +
signedResource + "\n"
signedSnapshotTime + "\n" +
rscc + "\n" +
rscd + "\n" +
rsce + "\n" +
rscl + "\n" +
rsct
Which is not the same as what you're doing. Essentially you're missing canonicalizedResource and signedResource parameters.
Please form your stringToSign properly and you should not get the error you're encountering. I think it should be something like (not tested though):
String stringToSign= "rl\n"+
"2021-03-11T08:08:46Z" +"\n" +
"2021-03-12T08:08:46Z"+ "\n"+
"/blob/{myAccountName}/quickstartcontainer/sampleFile2813061026464365578.txt 2020-02-10\n"+
"\n"+
"\n"+
"\n"+
2020-02-10"+"\n"+
"b\n"+"\n"+"\n"+"\n"+"\n"+"\n";

How to upload a binary file (PDF) to Azure using File Services

I'm trying to upload a binary file (PDF or Word) to Azure using the Services from C#. I'm restricted to using the REST API's and therefore I cannot use the SDK.
I've managed to create the file but which REST API should I use to upload the content. As far as I can see the PUT RANGE is only for text (not for binary content).
Which File Services REST API should i use? Or is the File Services API not meant for this and should i use the Blob API?
Best regards,
Michel
string endpoint_putrange = "https://" + ssStorageAccount + ".file.core.windows.net/" + ssShareNaam + "/" + ssBestandsNaam + "?comp=range";
Uri _endpoint_putrange = new Uri(endpoint_putrange);
HttpRequestMessage _requestMessage = new HttpRequestMessage(HttpMethod.Put, _endpoint_putrange);
HttpClient httpClient = new HttpClient();
httpClient.Timeout = new TimeSpan(20000 * 10000); // 20sec in ticks
httpClient.BaseAddress = _endpoint_putrange;
string RequestDateString = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);
// First set the header and construct the canonicalHeader string. See
// These must be in alpabetical order !!!!
string canonicalHeaders = string.Empty;
canonicalHeaders += setHeader(httpClient, "Content-Length", ssBestandsBinary.Length.ToString());
canonicalHeaders += setHeader(httpClient, "x-ms-date", RequestDateString);
canonicalHeaders += setHeader(httpClient, "x-ms-range", "bytes=0-"); // https://learn.microsoft.com/en-us/rest/api/storageservices/specifying-the-range-header-for-file-service-operations
canonicalHeaders += setHeader(httpClient, "x-ms-version", "2020-04-08"); // https://learn.microsoft.com/nl-nl/rest/api/storageservices/versioning-for-the-azure-storage-services
canonicalHeaders += setHeader(httpClient, "x-ms-write", "Update");
// Construct the canonicalResource string
string canonicalResource = "/" + ssStorageAccount + "/" + ssShareNaam + "/" + ssBestandsNaam;
// Construct the string to Sign
string stingToSign = "PUT\n" + /*HTTP Verb*/
"\n" + /*Content-Encoding*/
"\n" + /*Content-Language*/
ssBestandsBinary.Length.ToString() + "\n" + /*Content-Length (empty string when zero)*/
"\n" + /*Content-MD5*/
"\n" + /*Content-Type*/
"\n" + /*Date*/
"\n" + /*If-Modified-Since */
"\n" + /*If-Match*/
"\n" + /*If-None-Match*/
"\n" + /*If-Unmodified-Since*/
"bytes=0-" + "\n" + /*Range*/
canonicalHeaders + /*CanonicalizedHeaders*/
canonicalResource; /*CanonicalizedResource*/
// Sign it
HMACSHA256 hmac = new HMACSHA256();
byte[] dataToHmac = Encoding.UTF8.GetBytes(stingToSign);
string signature = Convert.ToBase64String(hmac.ComputeHash(dataToHmac));
// Add the authorization header
string authorizationHeader = ssStorageAccount + ":" + signature;
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("SharedKey", authorizationHeader);
// How to specify the binary content of the _requestmessage/httpClient
/// ?????????????????????????
// Do the (synchronous) call and collect the response
HttpResponseMessage httpResponse = httpClient.SendAsync(_requestMessage, HttpCompletionOption.ResponseContentRead, CancellationToken.None).GetAwaiter().GetResult();
..... etc etc
Please try by setting the Content property of your _requestMessage. I am assuming that variable ssBestandsBinary is a byte array so you can set the Content property as ByteArrayContent.
Your code could be something like (not tested though):
_requestMessage.Content = new ByteArrayContent(ssBestandsBinary);
HttpResponseMessage httpResponse = httpClient.SendAsync(_requestMessage, HttpCompletionOption.ResponseContentRead, CancellationToken.None).GetAwaiter().GetResult();

Generate Azure Blob SAS token with APEX

I want to generate SAS Token, as per the Azure Doc following is the example for BLOB. I am still getting error. Can you please identify what is the mistake I am doing.
StringToSign = signedpermissions + "\n" +
signedstart + "\n" +
signedexpiry + "\n" +
canonicalizedresource + "\n" +
signedidentifier + "\n" +
signedIP + "\n" +
signedProtocol + "\n" +
signedversion + "\n" +
rscc + "\n" +
rscd + "\n" +
rsce + "\n" +
rscl + "\n" +
rsct
URL = https://myaccount.blob.core.windows.net/mycontainer/test.txt
canonicalizedresource = "/blob/myaccount/mycontainer/test.txt
I am getting following error
<?xml version="1.0" encoding="utf-8"?>
<Error>
<Code>AuthenticationFailed</Code>
<Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
RequestId:4b1d1c59-c01e-0052-0197-ffe09f000000
Time:2020-03-21T15:44:34.2757923Z</Message>
<AuthenticationErrorDetail>Signature fields not well formed.</AuthenticationErrorDetail>
</Error>
Following is my code
public void getSasToken() {
this.storageName = 'zaindevtesting';
this.storageContainer = 'zaindevblob';
this.storageKey = 'xxxxxxZFQ==';
this.storageUrl ='https://zaindevtesting.blob.core.windows.net';
Datetime sasExpiry = Datetime.now();
sasExpiry.addMinutes(15);
Datetime sasStart = Datetime.now();
sasStart.addMinutes(-5);
string signedpermissions = 'rwdlac';
String signedstart = sasStart.formatGMT('YYYY-MM-dd\'T\'HH:mm:ss\'Z\'');
string signedexpiry = sasExpiry.formatGMT('YYYY-MM-dd\'T\'HH:mm:ss\'Z\'');
string signedservice = 'b';
String canonicalizedresource = '/blob/zaindevtesting/zaindevblob/test.txt';
string signedidentifier = '';
string signedIP = '';
string signedProtocol = 'https';
string signedversion = '2015-04-05';
string rscc='';
string rscd='';
string rsce='';
string rscl='';
string rsct='';
string stringToSign =signedpermissions + '\n' +
signedstart + '\n' +
signedexpiry + '\n' +
canonicalizedresource + '\n' +
signedidentifier + '\n' +
signedIP + '\n' +
signedProtocol + '\n' +
signedversion + '\n' +
rscc + '\n' +
rscd + '\n' +
rsce + '\n' +
rscl + '\n' +
rsct;
string signedExpiryEncode = EncodingUtil.urlEncode(signedexpiry, 'UTF-8');
string signedStartEncode = EncodingUtil.urlEncode(signedstart, 'UTF-8');
String sasToken = '';
Blob unicodeKey = EncodingUtil.base64Decode(storageKey);
Blob data = Crypto.generateMac('HMACSHA256', Blob.valueOf(stringToSign), unicodeKey);
sasToken = EncodingUtil.base64Encode(data);
String sasTokenString= '?sv=' + signedversion + '&se=' + signedexpiry +'&st='+signedstart+'&sr='+signedservice+'&sp=' + signedpermissions + '&sig=' + sasToken;
string sasURL = 'https://zaindevtesting.blob.core.windows.net/test.txt'+sasTokenString;
System.debug('sasURL--->'+sasURL);
System.debug(sasTokenString);
// return sasToken;
}
This code generate following SAS Link
https://zaindevtesting.blob.core.windows.net/test.txt??sv=2017-11-09&st=2020-03-21T15%3A44%3A07Z&se=2020-03-21T15%3A44%3A07Z&sr=b&sp=rwdc&sig=eJVbGWI4rcyjggOYAE308ilXEA/zAsFYbuNi24IZhX4=
Please try the following code:
public void generateSASToken()
{
string storageName = 'zaindevtesting';
string storageContainer = 'zaindevblob';
string storageKey = 'xxxx==';
string storageUrl ='https://zaindevtesting.blob.core.windows.net';
Datetime sasExpiry = Datetime.now();
sasExpiry = sasExpiry.addMinutes(15);
Datetime sasStart = Datetime.now();
sasStart = sasStart.addMinutes(-5);
string signedpermissions = 'r';
String signedstart = sasStart.formatGMT('YYYY-MM-dd\'T\'HH:mm:ss\'Z\'');
string signedexpiry = sasExpiry.formatGMT('YYYY-MM-dd\'T\'HH:mm:ss\'Z\'');
string signedservice = 'b';
String canonicalizedresource = '/blob/zaindevtesting/zaindevblob/test.txt';
string signedidentifier = '';
string signedIP = '';
string signedProtocol = '';
string signedversion = '2015-04-05';
string rscc='';
string rscd='';
string rsce='';
string rscl='';
string rsct='';
string stringToSign =signedpermissions + '\n' +
signedstart + '\n' +
signedexpiry + '\n' +
canonicalizedresource + '\n' +
signedidentifier + '\n' +
signedIP + '\n' +
signedProtocol + '\n' +
signedversion + '\n' +
rscc + '\n' +
rscd + '\n' +
rsce + '\n' +
rscl + '\n' +
rsct;
System.debug('stringToSign--->'+stringToSign);
string signedExpiryEncode = EncodingUtil.urlEncode(signedexpiry, 'UTF-8');
string signedStartEncode = EncodingUtil.urlEncode(signedstart, 'UTF-8');
String sasToken = '';
Blob unicodeKey = EncodingUtil.base64Decode(storageKey);
Blob data = Crypto.generateMac('HMACSHA256', Blob.valueOf(stringToSign), unicodeKey);
sasToken = EncodingUtil.base64Encode(data);
sasToken = EncodingUtil.urlEncode(sasToken, 'UTF-8');
String sasTokenString= '?sv=' + signedversion + '&se=' + signedexpiry +'&st='+signedstart+'&sr='+signedservice+'&sp=' + signedpermissions + '&sig=' + sasToken;
string sasURL = 'https://zaindevtesting.blob.core.windows.net/zaindevblob/test.txt'+sasTokenString;
System.debug('sasURL--->'+sasURL);
System.debug(sasTokenString);
// return sasToken;
}
I just tried it with your storage account key and I was able to download the blob.
Found the following issues with your code:
Permissions has to be in specific order. You're using string signedpermissions = 'rwdlac'; which is not the correct order. I just specified the read permission.
Signed Protocol was specified in stringToString but was not included in the SAS URL. I omitted that from both places.
SAS Token was not URL encoded.
You generated SAS Token for zaindevtesting.blob.core.windows.net/zaindevblob/test.txt URL but forgot to include the blob container name in the final URL.

Arduino upload a image file in sdcard to azure

I have a problem that arduino upload a file in sdcard to MS azure cognitive service, such as use a image file for face identify. I have try use multipart http post to upload, and it is useful for another service but not MS azure.
Does anyone have experience in using "application/octet-stream" to upload a file to Azure server?
my multipart post function show below:
void wifisendfile()
{
//connect to wifi
WiFi.begin(ssid.c_str(), pass.c_str());
Serial.print("Attempting to connect to SSID: ");
Serial.println(ssid);
while (WiFi.status() != WL_CONNECTED)
{
Serial.print(".");
// wait 0.5 second for connection:
delay(500);
}
Serial.print("Wifi Connected,IP address: ");
Serial.println(WiFi.localIP());
//prepare httpclient
Serial.println("Starting connection to server...");
Serial.println(host);
WiFiClient client;
//start http sending
if (client.connect(host.c_str(), 80))
{
//open file
myFile = SD.open("/pic.jpg");
int filesize=myFile.size();
Serial.print("filesize=");
Serial.println(filesize);
String fileName = myFile.name();
String fileSize = String(myFile.size());
Serial.println("reading file");
if (myFile)
{
String boundary = "CustomizBoundarye----";
String contentType = "image/jpeg";
// post header
String postHeader = "POST " + url + " HTTP/1.1\r\n";
postHeader += "Host: " + host + ":80 \r\n";
postHeader += "Content-Type: multipart/form-data; boundary=" + boundary + "\r\n";
postHeader += "Accept-Charset: utf-8;\r\n";
String keyHeader = "--" + boundary + "\r\n";
keyHeader += "Content-Disposition: form-data; name=\"key\"\r\n\r\n";
String requestHead = "--" + boundary + "\r\n";
requestHead += "Content-Disposition: form-data; name=\"\"; filename=\"" + fileName + "\"\r\n";
requestHead += "Content-Type: " + contentType + "\r\n\r\n";
// post tail
String tail = "\r\n--" + boundary + "--\r\n\r\n";
// content length
int contentLength = keyHeader.length() + requestHead.length() + myFile.size() + tail.length();
postHeader += "Content-Length: " + String(contentLength, DEC) + "\n\n";
// send post header
char charBuf0[postHeader.length() + 1];
postHeader.toCharArray(charBuf0, postHeader.length() + 1);
client.write(charBuf0);
//Serial.print("send post header=");
//Serial.println(charBuf0);
// send key header
char charBufKey[keyHeader.length() + 1];
keyHeader.toCharArray(charBufKey, keyHeader.length() + 1);
client.write(charBufKey);
//Serial.print("send key header=");
//Serial.println(charBufKey);
// send request buffer
char charBuf1[requestHead.length() + 1];
requestHead.toCharArray(charBuf1, requestHead.length() + 1);
client.write(charBuf1);
//Serial.print("send request buffer=");
//Serial.println(charBuf1);
// create buffer
const int bufSize = 2048;
byte clientBuf[bufSize];
int clientCount = 0;
// read myFile until there's nothing else in it:
while (myFile.available())
{
clientBuf[clientCount] = myFile.read();
clientCount++;
if (clientCount > (bufSize - 1))
{
client.write((const uint8_t *)clientBuf, bufSize);
clientCount = 0;
}
}
if (clientCount > 0)
{
client.write((const uint8_t *)clientBuf, clientCount);
//Serial.println("Sent LAST buffer");
}
// send tail
char charBuf3[tail.length() + 1];
tail.toCharArray(charBuf3, tail.length() + 1);
client.write(charBuf3);
//Serial.print(charBuf3);
}
Serial.println("end_request");
}
String lastline;
while(client.connected())
{
while(client.available())
{
String line = client.readStringUntil('\r');
lastline=line;
//Serial.print(line);
if(line.indexOf("{")>0)
{
client.stop();
}
}
}
Serial.println(lastline);
myFile.close();
Serial.println("closing connection");
}
After many days trying, I use sniffer to scan the package data in network. I found any data must be send by https to Azure Service. So that any file upload failed. After using HTTPS, files upload fine and get response from azure server.

Constructing an Account SAS (Shared Access Signature)

I am trying to generate Account SAS token:
MSDN DOC
When I am trying to use generated token, I get following:
AuthenticationFailed
Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
RequestId:89959111-0001-00c8-24d1-e0515b000000
Time:2016-07-18T08:49:00.8383767Z
Signature did not match. String to sign used was [accountName]
rl
b
sc
2017-01-01
2015-04-05
Here are my code:
var signedVersion = "2015-04-05";
var signedServices = "b";
var signedResourceTypes = "sc";
var signedPermission = "rl";
var signedExpiry = "2017-01-01";
var stringToSign =
accountName + "\n" +
signedPermission + "\n" +
signedServices + "\n" +
signedResourceTypes + "\n" +
signedExpiry + "\n" +
signedVersion + "\n"
;
var keyBytes = Encoding.UTF8.GetBytes(accountKey);
byte[] hash;
using (var mac = new HMACSHA256(keyBytes))
{
var stringToSignBytes = Encoding.UTF8.GetBytes(stringToSign);
hash = mac.ComputeHash(stringToSignBytes);
}
var str = Convert.ToBase64String(hash);
var sig = HttpUtility.UrlEncode(str);
var url = $"https://{accountName}.blob.core.windows.net/?comp=list&sv={signedVersion}&ss={signedServices}&srt={signedResourceTypes}&sp={signedPermission}&se={signedExpiry}&sig={sig}";
What am I doing wrong?
I noticed a few issues with the code:
First, to convert account key into byte array you would need to use Convert.FromBase64String(accountKey) instead of Encoding.UTF8.GetBytes(accountKey);.
Next, even if you're not using start time, signed protocol and signed IP addresses, you would need to include them in your stringToSign.
Once you do these things, the code should work. Based on these, I have included the modified code below. I tested it for listing containers in my storage account and it works.
static void AccountSas()
{
var signedVersion = "2015-04-05";
var signedServices = "b";
var signedResourceTypes = "sc";
var signedPermission = "rl";
var signedExpiry = "2017-01-01";
var signedStart = "";
var signedIP = "";
var signedProtocol = "";
var stringToSign =
accountName + "\n" +
signedPermission + "\n" +
signedServices + "\n" +
signedResourceTypes + "\n" +
signedStart + "\n" +
signedExpiry + "\n" +
signedIP + "\n" +
signedProtocol + "\n" +
signedVersion + "\n"
;
var keyBytes = Convert.FromBase64String(accountKey);
byte[] hash;
using (var mac = new HMACSHA256(keyBytes))
{
var stringToSignBytes = Encoding.UTF8.GetBytes(stringToSign);
hash = mac.ComputeHash(stringToSignBytes);
}
var str = Convert.ToBase64String(hash);
var sig = HttpUtility.UrlEncode(str);
var url = string.Format("https://{0}.blob.core.windows.net/?comp=list&sv={1}&ss={2}&srt={3}&sp={4}&se={5}&sig={6}", accountName, signedVersion, signedServices, signedResourceTypes, signedPermission, signedExpiry, sig);
}

Resources