This post has been edited (2019-11-24) my original assumption about how DateTime.Kind unspecified was handled were incorrect.
As I was testing my Azure MQTT Test Client I noticed some oddness with MQTT connection timeouts and this got me wondering about token expiry times. So, I went searching again and found this Azure IoT Hub specific sample code
public static string generateSasToken(string resourceUri, string key, string policyName, int expiryInSeconds = 3600) { TimeSpan fromEpochStart = DateTime.UtcNow - new DateTime(1970, 1, 1); string expiry = Convert.ToString((int)fromEpochStart.TotalSeconds + expiryInSeconds); string stringToSign = WebUtility.UrlEncode(resourceUri) + "\n" + expiry; HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(key)); string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign))); string token = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}", WebUtility.UrlEncode(resourceUri), WebUtility.UrlEncode(signature), expiry); if (!String.IsNullOrEmpty(policyName)) { token += "&skn=" + policyName; } return token; }
This code worked first time and was more flexible than mine which was a bonus. Though while running my MQTTNet based client I noticed the connection would drop after approximately 10mins (EDIT this was probably an unrelated networking issue).
A long time ago (25 years) I had issues sharing a Unix time value between an applications written with Borland C and Microsoft Visual C which made me wonder about Unix epoch base offsets.
So to test my theory I built a Unix epoch test harness console application
using System; namespace UnixEpocTest { class Program { static void Main(string[] args) { TimeSpan ttl = new TimeSpan(0, 0, 0); Console.WriteLine("Current time"); Console.WriteLine($"Local {DateTime.Now} {DateTime.Now.Kind}"); Console.WriteLine($"UTC {DateTime.UtcNow} {DateTime.UtcNow.Kind}"); Console.WriteLine($"Unix DIY {new DateTime(1970, 1, 1)} {new DateTime(1970, 1, 1).Kind}"); Console.WriteLine($"Unix DIY+ {new DateTime(1970, 1, 1).ToUniversalTime()} {new DateTime(1970, 1, 1).ToUniversalTime().Kind}"); Console.WriteLine($"Unix DIY {new DateTime(1970, 1, 1, 0,0,0, DateTimeKind.Utc)}"); Console.WriteLine($"Unix {DateTime.UnixEpoch} {DateTime.UnixEpoch.Kind}"); Console.WriteLine(); TimeSpan fromEpochStart = DateTime.UtcNow - new DateTime(1970, 1, 1); TimeSpan fromEpochStartUtc = DateTime.UtcNow - new DateTime(1970, 1, 1,0,0,0, DateTimeKind.Utc); TimeSpan fromEpochStartUnixEpoch = DateTime.UtcNow - DateTime.UnixEpoch; Console.WriteLine("Epoch comparison"); Console.WriteLine($"Local {fromEpochStart} {fromEpochStart.TotalSeconds.ToString("f0")} sec"); Console.WriteLine($"UTC {fromEpochStartUtc} {fromEpochStartUtc.TotalSeconds.ToString("f0")} sec"); Console.WriteLine($"Epoc {fromEpochStartUnixEpoch} {fromEpochStartUnixEpoch.TotalSeconds.ToString("f0")} sec"); Console.WriteLine(); TimeSpan afterEpoch = DateTime.UtcNow.Add(ttl) - new DateTime(1970, 1, 1); TimeSpan afterEpochUtC = DateTime.UtcNow.Add(ttl) - new DateTime(1970, 1, 1).ToUniversalTime(); TimeSpan afterEpochEpoch = DateTime.UtcNow.Add(ttl) - DateTime.UnixEpoch; Console.WriteLine("Epoch calculation"); Console.WriteLine($"Local {afterEpoch}"); Console.WriteLine($"UTC {afterEpochUtC}"); Console.WriteLine($"Epoch {afterEpochEpoch}"); Console.WriteLine(); Console.WriteLine("Epoch DateTime"); Console.WriteLine($"Local :{new DateTime(1970, 1, 1)}"); Console.WriteLine($"UTC :{ new DateTime(1970, 1, 1).ToUniversalTime()}"); Console.WriteLine("Press ENTER to exit"); Console.ReadLine(); Console.WriteLine("Hello World!"); } } }
EDIT: I now think the UtcNow to “unspecified” kind mathematics was being handled correctly. I have updated the code to use the DateTime.UnixEpoch constant so the code is more readable.

public static string generateSasToken(string resourceUri, string key, string policyName, int expiryInSeconds = 900) { TimeSpan fromEpochStart = DateTime.UtcNow - DateTime.UnixEpoch; string expiry = Convert.ToString((int)fromEpochStart.TotalSeconds + expiryInSeconds); string stringToSign = WebUtility.UrlEncode(resourceUri) + "\n" + expiry; HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(key)); string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign))); string token = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}", WebUtility.UrlEncode(resourceUri), WebUtility.UrlEncode(signature), expiry); if (!String.IsNullOrEmpty(policyName)) { token += "&skn=" + policyName; } return token; }
I need to test the expiry of my SAS Tokens some more especially with the client running on my development machine (NZT which is currently UTC+13) and in Azure (UTC timezone)
Pingback: Azure IoT Hub SAS Tokens revisited yet again | devMobile's blog