I am trying to understand how to verify signatures of JWT tokens using the .NET Framework. I am using the token found at https://jwt.io/ .
If I understand how this is supposed to work, I can use the HMACSHA256 hashing algorithm with the first two tokens and a secret value to get the last part of the token. If that matches, then the signature is valid.
The example on the https://jwt.io/ page shows computing the hash in the following way:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload), secret
)
Unfortunately the HMACSHA256 object in the .NET Framework has no such method. You must pass in a byte[] or a stream. There is no argument for secret either. There is, however a constructor that takes in a byte[] as a key. In order to work around, I have been converting the word "secret" to a byte[] instantiating the HMACSHA256 object with that.
I then convert the base64 encoded header.payload string to a byte[] and pass that to the ComputeHash
method of the HMACSHA256
object.
Here is where I run into problems. The output from ComputeHash
is a byte array. No matter how I try converting this byte[] back to a string, it never matches the signature. I don't understand where I am going wrong. Is the signature portion of the token a hash value or a base64 encoded hash value?
Here is my code:
string jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
string[] parts = jwt.Split(".".ToCharArray());
string headerDotPayload = string.Format("{0}.{1}", parts[0], parts[1]);
string signature = parts[2];
byte[] secret = System.Text.UTF8Encoding.UTF8.GetBytes("secret");
byte[] input = System.Text.UTF8Encoding.UTF8.GetBytes(headerDotPayload);
var alg = new HMACSHA256(secret);
byte[] hash = alg.ComputeHash(input);
//Attempting to verify
StringBuilder result = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
result.Append(hash[i].ToString("x2"));
}
string verify1 = result.ToString(); //Does not match signature
string verify2 = System.Text.UTF8Encoding.UTF8.GetString(hash); //Does not match signature
byte[] verify3 = System.Text.UTF8Encoding.UTF8.GetBytes(signature); //Does not match value in the hash byte[]
Is the signature portion of the token a hash value or a base64 encoded hash value?
It is a Base64 Url Encoded hash value. You would need to encode the computed hash value in order to verify equality.
Review the following unit test to see where you went wrong with your verification.
[TestClass]
public class JwtUnitTest {
[TestMethod]
public void VerifySignature() {
string jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
string[] parts = jwt.Split(".".ToCharArray());
var header = parts[0];
var payload = parts[1];
var signature = parts[2];//Base64UrlEncoded signature from the token
byte[] bytesToSign = getBytes(string.Join(".", header, payload));
byte[] secret = getBytes("secret");
var alg = new HMACSHA256(secret);
var hash = alg.ComputeHash(bytesToSign);
var computedSignature = Base64UrlEncode(hash);
Assert.AreEqual(signature, computedSignature);
}
private static byte[] getBytes(string value) {
return Encoding.UTF8.GetBytes(value);
}
// from JWT spec
private static string Base64UrlEncode(byte[] input) {
var output = Convert.ToBase64String(input);
output = output.Split('=')[0]; // Remove any trailing '='s
output = output.Replace('+', '-'); // 62nd char of encoding
output = output.Replace('/', '_'); // 63rd char of encoding
return output;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With