Verifying JWT signed with the RS256 algorithm using public key in C#

后端 未结 6 467
遥遥无期
遥遥无期 2020-11-27 12:26

Ok, I understand that the question I am asking may be pretty obvious, but unfortunately I lack the knowledge on this subject and this task seems to be quite tricky for me.

相关标签:
6条回答
  • 2020-11-27 12:48

    .NET JWT Signature Verification using System.Security.Cryptography - No 3rd Party DLLs

    var errorMessage = string.Empty;
    
    // Google RSA well known Public Key data is available at https://accounts.google.com/.well-known/openid-configuration by navigating to the path described in the "jwks_uri" parameter.
    // {
    //     e: "AQAB",        // RSA Exponent
    //     n: "ya_7gV....",  // RSA Modulus aka Well Known Public Key
    //     alg: "RS256"      // RSA Algorithm
    // }
    
    var verified = VerifyJWT_RS256_Signature(
        jwt: "oicjwt....", 
        publicKey: "ya_7gV....", 
        exponent: "AQAB",
        errorMessage: out errorMessage);
    
    if (!verified)
    {
        // TODO: log error: 
        // TODO: Do something
    }
    

    NOTE: The following method verifies OpenID Connect JWT Signatures signed with Asymetric RS256 keys. OpenID Connect providers may opt to use other versions of Asymetric keys or even Symetric keys like HS256. This method does not directly support other key types.

    public static bool VerifyJWT_RS256_Signature(string jwt, string publicKey, string exponent, out string errorMessage)
    {
        if (string.IsNullOrEmpty(jwt))
        {
            errorMessage = "Error verifying JWT token signature: Javascript Web Token was null or empty.";
            return false;
        }
    
        var jwtArray = jwt.Split('.');
        if (jwtArray.Length != 3 && jwtArray.Length != 5)
        {
            errorMessage = "Error verifying JWT token signature: Javascript Web Token did not match expected format. Parts count was " + jwtArray.Length + " when it should have been 3 or 5.";
            return false;
        }
    
        if (string.IsNullOrEmpty(publicKey))
        {
            errorMessage = "Error verifying JWT token signature: Well known RSA Public Key modulus was null or empty.";
            return false;
        }
    
        if (string.IsNullOrEmpty(exponent))
        {
            errorMessage = "Error verifying JWT token signature: Well known RSA Public Key exponent was null or empty.";
            return false;
        }
    
        try
        {
            string publicKeyFixed = (publicKey.Length % 4 == 0 ? publicKey : publicKey + "====".Substring(publicKey.Length % 4)).Replace("_", "/").Replace("-", "+");
            var publicKeyBytes = Convert.FromBase64String(publicKeyFixed);
    
            var jwtSignatureFixed = (jwtArray[2].Length % 4 == 0 ? jwtArray[2] : jwtArray[2] + "====".Substring(jwtArray[2].Length % 4)).Replace("_", "/").Replace("-", "+");
            var jwtSignatureBytes = Convert.FromBase64String(jwtSignatureFixed);
    
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.ImportParameters(
                new RSAParameters()
                {
                    Modulus = publicKeyBytes,
                    Exponent = Convert.FromBase64String(exponent)
                }
            );
    
            SHA256 sha256 = SHA256.Create();
            byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(jwtArray[0] + '.' + jwtArray[1]));
    
            RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
            rsaDeformatter.SetHashAlgorithm("SHA256");
            if (!rsaDeformatter.VerifySignature(hash, jwtSignatureBytes))
            {
                errorMessage = "Error verifying JWT token signature: hash did not match expected value.";
                return false;
            }
        }
        catch (Exception ex)
        {
            errorMessage = "Error verifying JWT token signature: " + ex.Message;
            return false;
            //throw ex;
        }
    
        errorMessage = string.Empty;
        return true;
    }
    

    NOTE: Verifying the signature of an OpenID Connect JWT (Javascript Web Token) is only one necessary step of the JWT verification process. Make sure to set a NONCE value which your system can use to prevent Replay attacks. Make sure to validate each parameter of the JWT package for completeness and accuracy.

    0 讨论(0)
  • 2020-11-27 12:58

    NET Core

    To use this in a .NET core web api (.NET Framework see below) in a AddJwtBearer() auth flow I enhanced NvMat's great answer:

    Very important is to not use the RSACryptoServiceProvider in an using statement.

        private TokenValidationParameters GetTokenValidationParameters(string key)
        {
            var rs256Token = key.Value.Replace("-----BEGIN PUBLIC KEY-----", "");
            rs256Token = rs256Token.Replace("-----END PUBLIC KEY-----", "");
            rs256Token = rs256Token.Replace("\n", "");
    
            var keyBytes = Convert.FromBase64String(rs256Token);
    
            var asymmetricKeyParameter = PublicKeyFactory.CreateKey(keyBytes);
            var rsaKeyParameters = (RsaKeyParameters)asymmetricKeyParameter;
            var rsaParameters = new RSAParameters
            {
                Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned(),
                Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned()
            };
            var rsa = new RSACryptoServiceProvider();
    
            rsa.ImportParameters(rsaParameters);
    
            var validationParameters = new TokenValidationParameters()
            {
                RequireExpirationTime = false,
                RequireSignedTokens = true,
                ValidateAudience = false,
                ValidateIssuer = false,
                IssuerSigningKey = new RsaSecurityKey(rsa),
            };
    
            return validationParameters;
        }
    

    Then you are able to use authentication in the startup like this:

    services.AddAuthentication(x =>
    {
        x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.RequireHttpsMetadata = false;
        options.SaveToken = true;
        options.IncludeErrorDetails = true;
        options.TokenValidationParameters = GetTokenValidationParameters(configuration["Key"]);
        options.Audience = configuration["ClientId"];
    });
    

    NET Framework

    It is also possible to use this approach in a .NET Framework web api project. All you have to do is add this line to your startup Configure() method:

    app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions()
    {
         TokenValidationParameters = GetTokenValidationParameters(ConfigurationManager.AppSettings["Key"])
    });
    

    One important thing: Make sure you use a verion >=5.0.0 of the JwtSecurityTokenHandler I had problems with the 4.X.X versions.

    0 讨论(0)
  • 2020-11-27 13:05

    Thanks to jwilleke, I have got a solution. To verify the RS256 signature of a JWT, it is needed to use the RSAPKCS1SignatureDeformatter class and its VerifySignature method.

    Here is the exact code for my sample data:

      string tokenStr = "eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogImlzcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5NzAsCiAiY19oYXNoIjogIkxEa3RLZG9RYWszUGswY25YeENsdEEiCn0.XW6uhdrkBgcGx6zVIrCiROpWURs-4goO1sKA4m9jhJIImiGg5muPUcNegx6sSv43c5DSn37sxCRrDZZm4ZPBKKgtYASMcE20SDgvYJdJS0cyuFw7Ijp_7WnIjcrl6B5cmoM6ylCvsLMwkoQAxVublMwH10oAxjzD6NEFsu9nipkszWhsPePf_rM4eMpkmCbTzume-fzZIi5VjdWGGEmzTg32h3jiex-r5WTHbj-u5HL7u_KP3rmbdYNzlzd1xWRYTUs4E8nOTgzAUwvwXkIQhOh5TPcSMBYy6X3E7-_gr9Ue6n4ND7hTFhtjYs3cjNKIA08qm5cpVYFMFMG6PkhzLQ";
      string[] tokenParts = tokenStr.Split('.');
    
      RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
      rsa.ImportParameters(
        new RSAParameters() {
          Modulus = FromBase64Url("w7Zdfmece8iaB0kiTY8pCtiBtzbptJmP28nSWwtdjRu0f2GFpajvWE4VhfJAjEsOcwYzay7XGN0b-X84BfC8hmCTOj2b2eHT7NsZegFPKRUQzJ9wW8ipn_aDJWMGDuB1XyqT1E7DYqjUCEOD1b4FLpy_xPn6oV_TYOfQ9fZdbE5HGxJUzekuGcOKqOQ8M7wfYHhHHLxGpQVgL0apWuP2gDDOdTtpuld4D2LK1MZK99s9gaSjRHE8JDb1Z4IGhEcEyzkxswVdPndUWzfvWBBWXWxtSUvQGBRkuy1BHOa4sP6FKjWEeeF7gm7UMs2Nm2QUgNZw6xvEDGaLk4KASdIxRQ"),
          Exponent = FromBase64Url("AQAB")
        });
    
      SHA256 sha256 = SHA256.Create();
      byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(tokenParts[0] + '.' + tokenParts[1]));
    
      RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
      rsaDeformatter.SetHashAlgorithm("SHA256");
      if (rsaDeformatter.VerifySignature(hash, FromBase64Url(tokenParts[2])))
        MessageBox.Show("Signature is verified");
    
    //...
      static byte[] FromBase64Url(string base64Url)
      {
        string padded = base64Url.Length % 4 == 0
            ? base64Url : base64Url + "====".Substring(base64Url.Length % 4);
        string base64 = padded.Replace("_", "/")
                              .Replace("-", "+");
        return Convert.FromBase64String(base64);
      }
    
    0 讨论(0)
  • 2020-11-27 13:05

    For anyone that is looking for a quick method to validate RS256 with a public key that has "-----BEGIN PUBLIC KEY-----"/"-----END PUBLIC KEY------"

    Here are two methods with the help of BouncyCastle.

        public bool ValidateJasonWebToken(string fullKey, string jwtToken)
        {
            try
            {
                var rs256Token = fullKey.Replace("-----BEGIN PUBLIC KEY-----", "");
                rs256Token = rs256Token.Replace("-----END PUBLIC KEY-----", "");
                rs256Token = rs256Token.Replace("\n", "");
    
                Validate(jwtToken, rs256Token);
                return true;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                return false;
            }
        }
    
        private void Validate(string token, string key)
        {
            var keyBytes = Convert.FromBase64String(key); // your key here
    
            AsymmetricKeyParameter asymmetricKeyParameter = PublicKeyFactory.CreateKey(keyBytes);
            RsaKeyParameters rsaKeyParameters = (RsaKeyParameters)asymmetricKeyParameter;
            RSAParameters rsaParameters = new RSAParameters
            {
                Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned(),
                Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned()
            };
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                rsa.ImportParameters(rsaParameters);
                var validationParameters = new TokenValidationParameters()
                {
                    RequireExpirationTime = false,
                    RequireSignedTokens = true,
                    ValidateAudience = false,
                    ValidateIssuer = false,
                    IssuerSigningKey = new RsaSecurityKey(rsa)
                };
                var handler = new JwtSecurityTokenHandler();
                var result = handler.ValidateToken(token, validationParameters, out var validatedToken);
            }
        }
    

    This is a combination of http://codingstill.com/2016/01/verify-jwt-token-signed-with-rs256-using-the-public-key/ and @olaf answer that uses system.IdentityModel.Tokens.Jwt

    0 讨论(0)
  • 2020-11-27 13:06

    You can do this very easily with Jwt.Net. This function will decode and verify the signature of a JWT and return the payload as a dictionary of claims:

    private IDictionary<string, object> Decode(string token, string modulus, string exponent)
    {
        var urlEncoder = new JwtBase64UrlEncoder();
    
        var rsaKey = RSA.Create();
        rsaKey.ImportParameters(new RSAParameters() {
            Modulus = urlEncoder.Decode(modulus),
            Exponent = urlEncoder.Decode(exponent)
        });
    
        var claims = new JwtBuilder()
            .WithAlgorithm(new RS256Algorithm(rsaKey))
            .MustVerifySignature()
            .Decode<IDictionary<string, object>>(token);
    
        return claims;
    }
    

    Sample use:

    string jwt = "eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogImlzcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5NzAsCiAiY19oYXNoIjogIkxEa3RLZG9RYWszUGswY25YeENsdEEiCn0.XW6uhdrkBgcGx6zVIrCiROpWURs-4goO1sKA4m9jhJIImiGg5muPUcNegx6sSv43c5DSn37sxCRrDZZm4ZPBKKgtYASMcE20SDgvYJdJS0cyuFw7Ijp_7WnIjcrl6B5cmoM6ylCvsLMwkoQAxVublMwH10oAxjzD6NEFsu9nipkszWhsPePf_rM4eMpkmCbTzume-fzZIi5VjdWGGEmzTg32h3jiex-r5WTHbj-u5HL7u_KP3rmbdYNzlzd1xWRYTUs4E8nOTgzAUwvwXkIQhOh5TPcSMBYy6X3E7-_gr9Ue6n4ND7hTFhtjYs3cjNKIA08qm5cpVYFMFMG6PkhzLQ";
    string modulus = "w7Zdfmece8iaB0kiTY8pCtiBtzbptJmP28nSWwtdjRu0f2GFpajvWE4VhfJAjEsOcwYzay7XGN0b-X84BfC8hmCTOj2b2eHT7NsZegFPKRUQzJ9wW8ipn_aDJWMGDuB1XyqT1E7DYqjUCEOD1b4FLpy_xPn6oV_TYOfQ9fZdbE5HGxJUzekuGcOKqOQ8M7wfYHhHHLxGpQVgL0apWuP2gDDOdTtpuld4D2LK1MZK99s9gaSjRHE8JDb1Z4IGhEcEyzkxswVdPndUWzfvWBBWXWxtSUvQGBRkuy1BHOa4sP6FKjWEeeF7gm7UMs2Nm2QUgNZw6xvEDGaLk4KASdIxRQ";
    string exponent = "AQAB";
    
    try
    {
        IDictionary<string, object> claims = Decode(jwt, modulus, exponent);
    }
    catch (SignatureVerificationException)
    {
        // signature invalid, handle it here
    }
    
    0 讨论(0)
  • 2020-11-27 13:08

    Here is an example using IdentityModel.Tokens.Jwt for validation:

    string tokenStr = "eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogImlzcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5NzAsCiAiY19oYXNoIjogIkxEa3RLZG9RYWszUGswY25YeENsdEEiCn0.XW6uhdrkBgcGx6zVIrCiROpWURs-4goO1sKA4m9jhJIImiGg5muPUcNegx6sSv43c5DSn37sxCRrDZZm4ZPBKKgtYASMcE20SDgvYJdJS0cyuFw7Ijp_7WnIjcrl6B5cmoM6ylCvsLMwkoQAxVublMwH10oAxjzD6NEFsu9nipkszWhsPePf_rM4eMpkmCbTzume-fzZIi5VjdWGGEmzTg32h3jiex-r5WTHbj-u5HL7u_KP3rmbdYNzlzd1xWRYTUs4E8nOTgzAUwvwXkIQhOh5TPcSMBYy6X3E7-_gr9Ue6n4ND7hTFhtjYs3cjNKIA08qm5cpVYFMFMG6PkhzLQ";
    
    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
    rsa.ImportParameters(
      new RSAParameters()
      {
          Modulus = FromBase64Url("w7Zdfmece8iaB0kiTY8pCtiBtzbptJmP28nSWwtdjRu0f2GFpajvWE4VhfJAjEsOcwYzay7XGN0b-X84BfC8hmCTOj2b2eHT7NsZegFPKRUQzJ9wW8ipn_aDJWMGDuB1XyqT1E7DYqjUCEOD1b4FLpy_xPn6oV_TYOfQ9fZdbE5HGxJUzekuGcOKqOQ8M7wfYHhHHLxGpQVgL0apWuP2gDDOdTtpuld4D2LK1MZK99s9gaSjRHE8JDb1Z4IGhEcEyzkxswVdPndUWzfvWBBWXWxtSUvQGBRkuy1BHOa4sP6FKjWEeeF7gm7UMs2Nm2QUgNZw6xvEDGaLk4KASdIxRQ"),
          Exponent = FromBase64Url("AQAB")
      });
    
    var validationParameters = new TokenValidationParameters
                    {
                        RequireExpirationTime = true,
                        RequireSignedTokens = true,
                        ValidateAudience = false,
                        ValidateIssuer = false,
                        ValidateLifetime = false,
                        IssuerSigningKey = new RsaSecurityKey(rsa)
                    };
    
    SecurityToken validatedSecurityToken = null;
    var handler = new JwtSecurityTokenHandler();
    handler.ValidateToken(tokenStr, validationParameters, out validatedSecurityToken);
    JwtSecurityToken validatedJwt = validatedSecurityToken as JwtSecurityToken;
    
    0 讨论(0)
提交回复
热议问题