C# RSACryptoServiceProvider - JWT RS256 验证失败
C# RSACryptoServiceProvider - JWT RS256 Validation Fails
(我已经通过很多 Whosebug/google 结果试图找到解决这个问题的方法。)
我正在使用默认的 C# JwtSecurityTokenHandler 验证使用 RS256 签名的 JWT。在某些情况下,验证会在不应该的时候失败。具体来说,来自给定授权服务器的令牌可以正确验证,而来自另一个授权服务器的令牌则不会。
但是...在 JWT.IO 上使用相同的 JWT 和 RSA 证书可成功验证所有令牌。这是让我相信 C# 实现中有某些东西 wrong/unusual 的部分。 我还能够使用 oidc-client JavaScript 库 使用相同的证书验证相同的 JWT。验证有时会失败的一个地方是在 C# 中。
我将错误追溯到 JwtSecurityTokenHandler 的 ValidateSignature 方法。搜索原始 github 代码并在谷歌上搜索 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),我能够导出 Public 密钥,使我能够验证 JWT 成功 在 JWT.IO.
string publicKey = RSAKeys.ExportPublicKey(rsa);
我无法为此 post 提供实际的 JWT(它们无论如何都会过期),但是有没有人知道特定于 C# 的加密行为可以解释这些验证错误,这不会发生在 JavaScript 也不在 JWT.IO ?
如果是这样,有什么解决办法吗?
谢谢,
马丁
https://www.rfc-editor.org/rfc/rfc7518#section-6.3.1.1
Note that implementers have found that some cryptographic libraries
prefix an extra zero-valued octet to the modulus representations they
return, for instance, returning 257 octets for a 2048-bit key, rather
than 256. Implementations using such libraries will need to take
care to omit the extra octet from the base64url-encoded
representation.
对于您在其他地方的本期副本中提供的其中一个标记,模数的解码包括一个前缀为 0x00 的字节。这会导致下游问题。但是你可以修复他们的 non-conformance.
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 将签名视为不透明字节,因此它将对其进行编码 as-is。所以你可能不需要这个更正(尽管这是我开始调查的地方):
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;
}
(我已经通过很多 Whosebug/google 结果试图找到解决这个问题的方法。)
我正在使用默认的 C# JwtSecurityTokenHandler 验证使用 RS256 签名的 JWT。在某些情况下,验证会在不应该的时候失败。具体来说,来自给定授权服务器的令牌可以正确验证,而来自另一个授权服务器的令牌则不会。
但是...在 JWT.IO 上使用相同的 JWT 和 RSA 证书可成功验证所有令牌。这是让我相信 C# 实现中有某些东西 wrong/unusual 的部分。 我还能够使用 oidc-client JavaScript 库 使用相同的证书验证相同的 JWT。验证有时会失败的一个地方是在 C# 中。
我将错误追溯到 JwtSecurityTokenHandler 的 ValidateSignature 方法。搜索原始 github 代码并在谷歌上搜索 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),我能够导出 Public 密钥,使我能够验证 JWT 成功 在 JWT.IO.
string publicKey = RSAKeys.ExportPublicKey(rsa);
我无法为此 post 提供实际的 JWT(它们无论如何都会过期),但是有没有人知道特定于 C# 的加密行为可以解释这些验证错误,这不会发生在 JavaScript 也不在 JWT.IO ?
如果是这样,有什么解决办法吗?
谢谢, 马丁
https://www.rfc-editor.org/rfc/rfc7518#section-6.3.1.1
Note that implementers have found that some cryptographic libraries prefix an extra zero-valued octet to the modulus representations they return, for instance, returning 257 octets for a 2048-bit key, rather than 256. Implementations using such libraries will need to take care to omit the extra octet from the base64url-encoded representation.
对于您在其他地方的本期副本中提供的其中一个标记,模数的解码包括一个前缀为 0x00 的字节。这会导致下游问题。但是你可以修复他们的 non-conformance.
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 将签名视为不透明字节,因此它将对其进行编码 as-is。所以你可能不需要这个更正(尽管这是我开始调查的地方):
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;
}