Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 1 month ago by StarlitSatellite817

How can I generate a valid service SAS token for an Azure blob container without the SDK?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

I'm trying to create a service SAS token for a blob container without using the Azure SDK, following the guidelines in the Azure documentation. The token is based on a stored access policy so the permissions, start, and expiry values remain empty.

However, the resulting signature is invalid. I've tried various signedVersion values (from 2020-12-06 up to 2024-11-04) and different approaches to secret handling, but the signature still isn’t accepted.

Below is the Go code I’m using:

GO
package main import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "fmt" "net/url" ) func ComputeHmac256(message string, secret string) string { key := []byte(secret) h := hmac.New(sha256.New, key) h.Write([]byte(message)) return base64.StdEncoding.EncodeToString(h.Sum(nil)) } const accountKey = "xxxxxxx" var signedPermissions = "" var signedStart = "" var signedExpiry = "" var canonicalizedResource = "/blob/storamela/cont1" var signedIdentifier = "allperms" var signedIP = "" var signedProtocol = "https" var signedVersion = "2021-08-06" var signedResource = "c" var signedSnapshotTime = "" var signedEncryptionScope = "" var rscc = "" var rscd = "" var rsce = "" var rscl = "" var rsct = "binary" func main() { message := fmt.Sprintf( "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s", signedPermissions, signedStart, signedExpiry, canonicalizedResource, signedIdentifier, signedIP, signedProtocol, signedVersion, signedResource, signedSnapshotTime, signedEncryptionScope, rscc, rscd, rsce, rscl, rsct, ) secret,_ := base64.StdEncoding.DecodeString(accountKey) // Also tried secret := accountKey sig := ComputeHmac256(message, string(secret)) escapedSig := url.QueryEscape(sig) signedString := fmt.Sprintf("si=%%s&spr=%%s&sv=%%s&sr=%%s&sig=%%s", signedIdentifier, signedProtocol, signedVersion, signedResource, escapedSig) fmt.Println(signedString) }

Could this issue be related to setting response header fields (like rsct) when they should remain empty, or is there another mistake in the signature construction? Any insights are appreciated.

1

Comments 0

Answers 2

Views 64

2 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

Below is one likely culprit: when you’re building a SAS in access‐policy mode you must set all fields exactly as defined in the documentation. In your code you leave the permissions, start, and expiry strings empty (which is correct) but you’re still setting the optional response header field rsct to "binary". For a SAS that isn’t overriding any response headers you must leave all those (rscc, rscd, rsce, rscl, rsct) as empty strings. Otherwise your computed signature will not match what the storage service expects.

Other things to check include:

• Verify that the string‐to‐sign exactly follows the documented field order and that you are not including an extra trailing newline or misordering fields.
• Make sure you’re correctly decoding the account key from base‑64 before using it to compute the HMAC. A common mistake is to pass the encoded key or to mishandle the raw bytes as a UTF‑8 string.

For example, try updating your code so that none of the response header fields are set (unless you want to override them). For instance:


GO
package main import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "fmt" "net/url" ) func ComputeHmac256(message string, secret []byte) string { h := hmac.New(sha256.New, secret) h.Write([]byte(message)) return base64.StdEncoding.EncodeToString(h.Sum(nil)) } const accountKey = "xxxxxxx" var ( signedPermissions = "" // Not used when signing via stored access policy signedStart = "" signedExpiry = "" canonicalizedResource = "/blob/storamela/cont1" signedIdentifier = "allperms" signedIP = "" signedProtocol = "https" signedVersion = "2021-08-06" signedResource = "c" signedSnapshotTime = "" signedEncryptionScope = "" rscc = "" rscd = "" rsce = "" rscl = "" rsct = "" // leave this empty if no response header override is needed ) func main() { // Construct the string-to-sign as defined for your SAS version message := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s", signedPermissions, signedStart, signedExpiry, canonicalizedResource, signedIdentifier, signedIP, signedProtocol, signedVersion, signedResource, signedSnapshotTime, signedEncryptionScope, rscc, rscd, rsce, rscl, rsct, ) // Decode the base-64 account key into its raw bytes. decodedKey, err := base64.StdEncoding.DecodeString(accountKey) if err != nil { panic(err) } sig := ComputeHmac256(message, decodedKey) escapedSig := url.QueryEscape(sig) signedString := fmt.Sprintf("si=%s&spr=%s&sv=%s&sr=%s&sig=%s", signedIdentifier, signedProtocol, signedVersion, signedResource, escapedSig, ) fmt.Println(signedString) }

Here the only change (besides tidying up the secret handling) is that rsct (and any other response header override you aren’t using) is set to an empty string. Double‑check all other fields (including the canonicalized resource – it should be “/blob/youraccount/yourcontainer”) to make sure they exactly match the values used when you configured the stored access policy.

This should help produce a valid signature.

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Answer by SolarScientist067 1 month ago

0

Create a service SAS for a blob container - Without SDK.

The error may be occurred due to you missed rsct parmater in sas token.

In my environment, I created access policy with exipry and all permissions.
enter image description here

Now, you can use the below code to generate sas token for blob container.

Code:

GO
package main import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "fmt" "net/url" ) func ComputeHmac256(message string, secret []byte) string { h := hmac.New(sha256.New, secret) h.Write([]byte(message)) return base64.StdEncoding.EncodeToString(h.Sum(nil)) } const accountKey = "xxxxx" var signedPermissions = "" var signedStart = "" var signedExpiry = "" var canonicalizedResource = "/blob/<accountname>/<container name>" var signedIdentifier = "demovenkat" // Must match the identifier of the access policy var signedIP = "" var signedProtocol = "https" var signedVersion = "2021-08-06" var signedResource = "c" // 'c' for container-level SAS var signedSnapshotTime = "" var signedEncryptionScope = "" var rscc = "" var rscd = "" var rsce = "" var rscl = "" var rsct = "binary" func main() { // Construct the string-to-sign message := fmt.Sprintf( "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s", signedPermissions, signedStart, signedExpiry, canonicalizedResource, signedIdentifier, signedIP, signedProtocol, signedVersion, signedResource, signedSnapshotTime, signedEncryptionScope, rscc, rscd, rsce, rscl, rsct, ) // Decode the storage account key secret, err := base64.StdEncoding.DecodeString(accountKey) if err != nil { fmt.Println("Failed to decode account key:", err) return } // Compute the signature sig := ComputeHmac256(message, secret) escapedSig := url.QueryEscape(sig) // Construct the SAS token signedString := fmt.Sprintf("si=%s&spr=%s&sv=%s&sr=%s&sig=%s&rsct=%s", signedIdentifier, signedProtocol, signedVersion, signedResource, escapedSig, rsct) fmt.Println(signedString) }

Output:

si=demovenkat&spr=https&sv=2021-08-06&sr=c&sig=6zeHtxxxxxxFf8%3D&rsct=binary

enter image description here

Verified in the portal with Blob URL.

Request:

BASH
GET https://venkat326123.blob.core.windows.net/test/scenery.jpg?si=demovenkat&spr=https&sv=2021-08-06&sr=c&sig=redacted&rsct=binary

Output:
enter image description here

No comments yet.

Discussion

No comments yet.