C#RSACryptoServiceProvider - JWT RS256验证失败

问题描述 投票:0回答:1

(我已经通过很多Stackoverflow / google结果试图找到修复此问题。)

我正在使用默认的C#JwtSecurityTokenHandler来验证使用RS256签名的JWT。在某些情况下,验证失败了。具体而言,来自给定Authorization Server的令牌可以正确验证,而令牌形成另一个Authorization Server则不会。

但是......在JWT.IO上使用相同的JWT和RSA证书可以成功验证所有令牌。这是使我相信C#实现中存在错误/异常的部分。我还可以使用oidc-client JavaScript库使用相同的证书验证相同的JWT。验证有时失败的地方是C#。

我将错误追溯到JwtSecurityTokenHandler的ValidateSignature方法。搜索原始github代码并使用Google搜索RSA,我得到了这个简单的方法,它允许我在普通的控制台应用程序中重现问题:

static void ValidateJWT(string token, string modulus, string exponent)
{
    string tokenStr = token;
    JwtSecurityToken st = new JwtSecurityToken(tokenStr);
    string[] tokenParts = tokenStr.Split('.');

    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
    rsa.ImportParameters(
        new RSAParameters()
        {
            Modulus = FromBase64Url(modulus),
            Exponent = FromBase64Url(exponent)
        });

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

    RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
    rsaDeformatter.SetHashAlgorithm("SHA256");

    var valid = rsaDeformatter.VerifySignature(hash, FromBase64Url(tokenParts[2]));
    Console.WriteLine(valid); // sometimes false when it should be true
}

private 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);
}

它来自RSACryptoServiceProvider并使用来自此处的RSAKeys(https://gist.github.com/therightstuff/aa65356e95f8d0aae888e9f61aa29414),我能够导出公钥,允许我在JWT.IO上成功验证JWT。

string publicKey = RSAKeys.ExportPublicKey(rsa);

我无法为这篇文章提供实际的JWT(它们无论如何都会过期),但有没有人知道C#特有的加密行为可以解释这些验证错误,这些错误在JavaScript和JWT.IO中都不会发生?

如果是这样,任何解决方案?

谢谢,马丁

c# .net jwt rsa sha
1个回答
0
投票

https://tools.ietf.org/html/rfc7518#section-6.3.1.1

请注意,实现者发现某些加密库将额外的零值八位字节作为它们返回的模数表示的前缀,例如,为2048位密钥返回257个八位字节,而不是256.使用此类库的实现需要注意省略base64url编码表示中的额外八位字节。

对于您在其他地方的此问题的副本上提供的其中一个令牌的情况,模数的解码包括前缀0x00字节。这会导致下游问题。但你可以解决他们的不合规问题。

byte[] modulusBytes = FromBase64Url(modulus);

if (modulusBytes[0] == 0)
{
    byte[] tmp = new byte[modulusBytes.Length - 1];
    Buffer.BlockCopy(modulusBytes, 1, tmp, 0, tmp.Length);
    modulusBytes = tmp;
}

看起来RS256将签名视为不透明字节,因此它将按原样对其进行编码。所以你可能不需要这个校正(虽然它是我的调查开始的地方):

byte[] sig = FromBase64Url(tokenParts[2]);

if (sig.Length < modulusBytes.Length)
{
    byte[] tmp = new byte[modulusBytes.Length];
    Buffer.BlockCopy(sig, 0, tmp, tmp.Length - sig.Length, sig.Length);
    sig = tmp;
}
© www.soinside.com 2019 - 2024. All rights reserved.