I need to create a hash-signature in c#.
The pseudo-code example that i need to implement in my c# code:
Signatur(Request) = new String(encodeBase64URLCompatible(HMAC-SHA-256(getBytes(Z, "UTF-8"), decodeBase64URLCompatible(getBytes(S, "UTF-8")))), "UTF-8")
Z: apiSecret S: stringToSign
The coding for expectedSignatur
and apiSecret
is Base64 URL Encoding [RFC 4648 Section 5]
My problem is that I always get the wrong result.
public static string Base64Decode(string base64EncodedData)
{
var base64EncodedBytes = Convert.FromBase64String(base64EncodedData);
return Encoding.UTF8.GetString(base64EncodedBytes);
}
public static string Base64Encode(string plainText)
{
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
return Convert.ToBase64String(plainTextBytes);
}
private static byte[] HmacSha256(string data, string key)
{
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
{
return hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
}
}
static void Main(string[] args)
{
var apiSecret = "JrXRHCnUegQJAYSJ5J6OvEuOUOpy2q2-MHPoH_IECRY=";
var stringToSign = "f3fea5f3-60af-496f-ac3e-dbb10924e87a:20160201094942:e81d298b-60dd-4f46-9ec9-1dbc72f5b5df:Qg5f0Q3ly1Cwh5M9zcw57jwHI_HPoKbjdHLurXGpPg0yazdC6OWPpwnYi22bnB6S";
var expectedSignatur = "ps9MooGiTeTXIkPkUWbHG4rlF3wuTJuZ9qcMe-Y41xE=";
apiSecret = apiSecret.Replace('-', '+').Replace('_', '/').PadRight(apiSecret.Length + (4 - apiSecret.Length % 4) % 4, '=');
var secretBase64Decoded = Base64Decode(apiSecret);
var hmac = Convert.ToBase64String(HmacSha256(secretBase64Decoded, stringToSign));
var signatur = hmac.Replace('+', '-').Replace('/', '_');
Console.WriteLine($"signatur: {signatur}");
Console.WriteLine($"expected: {expectedSignatur}");
Console.WriteLine(signatur.Equals(expectedSignatur));
Console.ReadLine();
}
You're assuming that your key was originally text encoded with UTF-8 - but it looks like it wasn't. You should keep logically binary data as binary data - you don't need your Base64Encode
and Base64Decode
methods at all. Instead, your HmacSha256
method should take a byte[]
as a key, and you can just use Convert.FromBase64String
to get at those bytes from the base64-encoded secret:
using System;
using System.Text;
using System.Security.Cryptography;
class Test
{
private static byte[] HmacSha256(byte[] key, string data)
{
using (var hmac = new HMACSHA256(key))
{
return hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
}
}
static void Main(string[] args)
{
var apiSecret = "JrXRHCnUegQJAYSJ5J6OvEuOUOpy2q2-MHPoH_IECRY=";
var stringToSign = "f3fea5f3-60af-496f-ac3e-dbb10924e87a:20160201094942:e81d298b-60dd-4f46-9ec9-1dbc72f5b5df:Qg5f0Q3ly1Cwh5M9zcw57jwHI_HPoKbjdHLurXGpPg0yazdC6OWPpwnYi22bnB6S";
var expectedSignatur = "ps9MooGiTeTXIkPkUWbHG4rlF3wuTJuZ9qcMe-Y41xE=";
apiSecret = apiSecret.Replace('-', '+').Replace('_', '/').PadRight(apiSecret.Length + (4 - apiSecret.Length % 4) % 4, '=');
var secretBase64Decoded = Convert.FromBase64String(apiSecret);
var hmac = Convert.ToBase64String(HmacSha256(secretBase64Decoded, stringToSign));
var signatur = hmac.Replace('+', '-').Replace('/', '_');
Console.WriteLine($"signatur: {signatur}");
Console.WriteLine($"expected: {expectedSignatur}");
Console.WriteLine(signatur.Equals(expectedSignatur));
}
}
Personally I'd change your HmacSha256
method to:
private static byte[] ComputeHmacSha256Hash(byte[] key, byte[] data)
{
using (var hmac = new HMACSHA256(key))
{
return hmac.ComputeHash(data);
}
}
so that it's more general purpose, maybe adding another method to compute the hash after encoding as UTF-8 for convenience. That way you can sign any data, not just strings.
See more on this question at Stackoverflow