I’m seeing some weird behavior when trying to take some relatively simple C# code and implementing it in Windows PowerShell.
My preference is to NOT use the embedded C# code in my script, and I have sample script below that demonstrates the issue I’m seeing.
The goal is to create SAS Tokens to be used with Azure REST API calls. I need to be able to do this without any external dependencies, hence the need to create these SAS tokens by hand.
The sample script below shows two methods to generate these. The C# code works, meaning the token generated is valid and accepted by Azure. The token generated by the PowerShell function is not valid, meaning the signature portion of the token is bad.
Running the sample script will spit out two tokens so the difference is obvious. I can’t seem to get the PowerShell function to create the signature correctly. When running under a debugger, and the C# code running under a debugger in Visual Studio, I can see the HMACSHA256 object being created correctly in both environments, the Key fields are correct in both, but the resultant HASH coming out are NOT the same. The only thing I can attribute this to is PowerShell is doing something that I’m not expecting.
The process is to
- build the string to be signed
- create a HMACSHA256 signature
- Build the token string including the signature.
I can confirm that the strings being signed are exactly the same, meaning the byte array representation of the two are identical.
Here’s the script I’m using to test this:
Set-StrictMode -Version 2 $source = @" using System; using System.Text; using System.Security.Cryptography; using System.Globalization; using System.Net; public class SASTokenGenerator { public static string createToken(string resourceUri, string keyName, string key) { return createToken(resourceUri, keyName, key, 0); } public static string createToken(string resourceUri, string keyName, string key, int expiry) { if (0 == expiry) { TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1); expiry = (int)sinceEpoch.TotalSeconds + 3600; //EXPIRES in 1h } string stringToSign = WebUtility.UrlEncode(resourceUri) + "\n" + Convert.ToString(expiry); HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key)); var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign))); var sasToken = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}", WebUtility.UrlEncode(resourceUri), WebUtility.UrlEncode(signature), expiry, keyName); return sasToken; } } "@ Function New-MJAzureSASToken { param ( $ResourceURI, $KeyName, $Key, $expiry = 0 ) if ($expiry -eq 0) { [TimeSpan]$ts = (Get-Date).ToUniversalTime() - (get-date -year 1970 -Month 1 -Date 1) [int]$expiry = ([int]($ts.TotalSeconds) + 3600).ToString() } [string]$stringToSign = [System.Net.WebUtility]::UrlEncode($ResourceURI) + "\n" + $expiry; [Byte[]]$KeyBytes = [System.Text.Encoding]::UTF8.GetBytes($Key) $hmac = New-Object System.Security.Cryptography.HMACSHA256 $signature = $hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($stringToSign)) $signature = [System.Convert]::ToBase64String($signature) [string]$sasToken = [System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture,"SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}", [System.Net.WebUtility]::UrlEncode($ResourceURI), [System.Net.WebUtility]::UrlEncode($signature), $expiry, $KeyName) return $sasToken } Function New-MJAzureEventHubMessage { param ( $payload, $namespace, $eventhub, $key, $keyname, $devicename ) $b = $payload | ConvertTo-Json #create http request $Method = "POST" $Headers = @{} $Headers.Add("Content-Length", $b.length ) $URI = "https://$namespace" + ".servicebus.windows.net/$eventhub/publishers/$devicename/messages" $SAS = New-MJAzureSASToken -ResourceURI ($URI) -KeyName "Send" -Key "LfrDxs7GdyhKw2E7dwIJZsLAOyqmLZ7PDMuDOT48qQ8=" Invoke-RestMethod -Method $Method -Uri $URI -Body $b -Headers $Headers -Verbose -Debug } Add-Type -TypeDefinition $source -ErrorAction SilentlyContinue $testuri = "https://mjenne1010.servicebus.windows.net/hub1010/publishers/edison01.local/messages" $testkeyname = "Send" $testkey = "LfrDxs7GdyhKw2E7dwIJZsLAOyqmLZ7PDMuDOT48qQ8=" [SASTokenGenerator]::createToken($testuri, $testkeyname ,$testkey,1444349270); New-MJAzureSASToken -ResourceURI $testuri -KeyName $testkeyname -Key $testkey -expiry 1444349270
Note that the 1444349270 value in the above lines is used to make sure both token generators run with the exact same expiry value. This value represents the expiration time as expressed in the number of seconds since Jan 1, 1970.
Any comments or feedback on this would be appreciated.