C# How to verify signature on JWT token?
Asked Answered
A

1

12

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[] 
Aundreaaunson answered 2/8, 2016 at 15:44 Comment(0)
L
12

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;
    }
}
Landry answered 14/8, 2016 at 3:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.