21

I have a token, a file containing public key and I want to verify the signature. I tried to verify signature based on this.

However, decodedCrypto and decodedSignature don't match.

Here is my code:

public static string Decode(string token, string key, bool verify)
    {
        var parts = token.Split('.');
        var header = parts[0];
        var payload = parts[1];
        byte[] crypto = Base64UrlDecode(parts[2]);

        var headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
        var headerData = JObject.Parse(headerJson);
        var payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
        var payloadData = JObject.Parse(payloadJson);

        if (verify)
        {
            var bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload));
            var keyBytes = Encoding.UTF8.GetBytes(key);
            var algorithm = (string)headerData["alg"];
            var signature = HashAlgorithms[GetHashAlgorithm(algorithm)](keyBytes, bytesToSign);
            var decodedCrypto = Convert.ToBase64String(crypto);
            var decodedSignature = Convert.ToBase64String(signature);

            if (decodedCrypto != decodedSignature)
            {
                throw new ApplicationException(string.Format("Invalid signature. Expected {0} got {1}", decodedCrypto, decodedSignature));
            }
        }

        return payloadData.ToString();
    }

I'm sure that the signature of token is valid. I try to verify on https://jwt.io/ and it showed that Signature verified. So the problem is the algorithm to encode, decode.

Is there anyone can solve this problem? The algorithm is RS256

enter image description here

Community
  • 1
  • 1
anhtv13
  • 1,636
  • 4
  • 30
  • 51
  • The simplest answer was reported in another thread https://stackoverflow.com/questions/10055158/is-there-any-json-web-token-jwt-example-in-c by @Thomas. – Karthick Jayaraman Jul 18 '18 at 18:28
  • 2
    @KarthickJayaraman I mention the reference above, but it doesn't work. Don't you look at the question carefully? – anhtv13 Jul 19 '18 at 08:00

4 Answers4

17

How about using JwtSecurityTokenHandler? it could look something like this:

public bool ValidateToken(string token, byte[] secret)
{
    var tokenHandler = new JwtSecurityTokenHandler();

    var validationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningToken = new BinarySecretSecurityToken(secret)
    };

    SecurityToken validatedToken;
    try
    {
        tokenHandler.ValidateToken(token, validationParameters, out validatedToken);
    }
    catch (Exception)
    {
       return false;
    }

    return validatedToken != null;
}

Be aware I haven't tested it but we used a similar implementation in one of the projects

Helikaon
  • 1,490
  • 13
  • 30
  • 2
    What is "BinarySecretSecurityToken" here? No reference here – anhtv13 Mar 23 '16 at 07:38
  • @ANguyen: https://msdn.microsoft.com/en-us/library/system.servicemodel.security.tokens.binarysecretsecuritytoken(v=vs.110).aspx – Helikaon Mar 23 '16 at 07:41
  • It is part of `IdentityModel` library. – bot_insane Mar 23 '16 at 07:42
  • System.IdentityModel is part of .NET Framework and System.IdentityModel.Tokens.Jwt u can get via Nuget – Helikaon Mar 23 '16 at 08:18
  • You can use IssuerSgningKey with X509SecurityKey instead if you can transform the key to or already have a certificate. – Ilya Chernomordik Mar 23 '16 at 08:49
  • What version of Microsoft.IdentityModel.Tokens is this? 5.2.1 does not have a IssuerSigningToken option and I'm also not seeing a BinarySecretSecurityToken. – MarzSocks Feb 15 '18 at 14:18
  • Not clear to me too what BinarySecretSecurityToken is. – Admiral Apr 10 '18 at 10:29
  • @theAdmiral https://msdn.microsoft.com/en-us/library/system.servicemodel.security.tokens.binarysecretsecuritytoken(v=vs.110).aspx and thanks for downvoting because something is not clear to you – Helikaon Apr 11 '18 at 11:22
17

I finally got a solution from my colleague.

For those who have the same problem, try my code:

public static string Decode(string token, string key, bool verify = true)
{
    string[] parts = token.Split('.');
    string header = parts[0];
    string payload = parts[1];
    byte[] crypto = Base64UrlDecode(parts[2]);

    string headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
    JObject headerData = JObject.Parse(headerJson);

    string payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
    JObject payloadData = JObject.Parse(payloadJson);

    if (verify)
    {
        var keyBytes = Convert.FromBase64String(key); // your key here

        AsymmetricKeyParameter asymmetricKeyParameter = PublicKeyFactory.CreateKey(keyBytes);
        RsaKeyParameters rsaKeyParameters = (RsaKeyParameters)asymmetricKeyParameter;
        RSAParameters rsaParameters = new RSAParameters();
        rsaParameters.Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned();
        rsaParameters.Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned();
        RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
        rsa.ImportParameters(rsaParameters);

        SHA256 sha256 = SHA256.Create();
        byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(parts[0] + '.' + parts[1]));

        RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
        rsaDeformatter.SetHashAlgorithm("SHA256");
        if (!rsaDeformatter.VerifySignature(hash, FromBase64Url(parts[2])))
            throw new ApplicationException(string.Format("Invalid signature"));
    }

    return payloadData.ToString();
}

It works for me. The algorithm is RS256.

Fedor
  • 1,548
  • 3
  • 28
  • 38
anhtv13
  • 1,636
  • 4
  • 30
  • 51
  • 2
    This worked great for me, thanks very much. watch out: RSACryptoServiceProvider and SHA256 are disposable – zeromus Nov 13 '18 at 10:39
  • Is there a PS256 implementation similar to this RS256 one? Or do I have to change the hash algorithm to solve that? I do not wish to pass in the private key, only the public key as above – foyss Aug 27 '19 at 16:13
  • Man.. Java and Python have libraries which allow you to do this dynamically just by passing in the algorithm and public key. I am going crazy at the lack of a remotely close equivalent for .NET land... – djsoteric Dec 10 '19 at 03:32
  • 1
    What is "Base64UrlDecode"? I'm getting this error: The name 'Base64UrlDecode' does not exist in the current context – Nagaraj Alagusundaram Aug 28 '20 at 05:50
  • 1
    @NagarajAlagusudaram Did you add the reference https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.webutilities.webencoders.base64urldecode?view=aspnetcore-3.1 ? – anhtv13 Aug 31 '20 at 03:43
11

I know this is an old thread but I could have recommended you to use this library instead of writing on your own. It has got some good documentation to get started. Am using it without any issues.

Coder Absolute
  • 5,417
  • 5
  • 26
  • 41
  • This library RS256 implementation is still not functional because it expects a private key to verify the token signature – Max Xapi May 24 '19 at 08:12
0

byte[] crypto = Base64UrlDecode(parts[2]);

In this line you are base64 decoding signature part of JWT token, but as I know that part isn't base64 encoded. Please try this code. ( I have commented out unnecessary lines )

public static string Decode(string token, string key, bool verify)
{
    var parts = token.Split('.');
    var header = parts[0];
    var payload = parts[1];
    // byte[] crypto = Base64UrlDecode(parts[2]);
    var jwtSignature = parts[2];

    var headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
    var headerData = JObject.Parse(headerJson);
    var payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
    var payloadData = JObject.Parse(payloadJson);

    if (verify)
    {
        var bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload));
        var keyBytes = Encoding.UTF8.GetBytes(key);
        var algorithm = (string)headerData["alg"];
        var computedJwtSignature = Encoding.UTF8.GetString(HashAlgorithms[GetHashAlgorithm(algorithm)](keyBytes, bytesToSign));
        // var decodedCrypto = Convert.ToBase64String(crypto);
        // var decodedSignature = Convert.ToBase64String(signature);

        if (jwtSignature != computedJwtSignature)
        {
            throw new ApplicationException(string.Format("Invalid signature. Expected {0} got {1}", decodedCrypto, decodedSignature));
        }
    }

    return payloadData.ToString();
}
bot_insane
  • 2,545
  • 18
  • 40
  • computedJwtSignature returns "{�3�R4p/�����g�o����1�G��#�i" And they dont match. My algorithm is RS256 – anhtv13 Mar 23 '16 at 07:44
  • What does this line return? ( value and type please ) `HashAlgorithms[GetHashAlgorithm(algorithm)](keyBytes, bytesToSign)` – bot_insane Mar 23 '16 at 07:45
  • As I know RS256 should get both public and private keys for computing hash, but you are giving only one. – bot_insane Mar 23 '16 at 07:54
  • I just need a public key to verify. I tried on https://jwt.io/ and it returns signnature verified. – anhtv13 Mar 23 '16 at 07:57
  • You put only public key in jwt.io website ? As I see the public/private keys are required there. – bot_insane Mar 23 '16 at 07:59
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/107103/discussion-between-aram-kocharyan-and-a-nguyen). – bot_insane Mar 23 '16 at 07:59
  • @bot_insane public key, located here https://appleid.apple.com/auth/keys consists of 2 parts, `n` and `e`. How does `key` parameter in your example is being constructed? – Rafael Mar 28 '22 at 15:30