C#如何验证JWT签名?

22

我有一个令牌,是包含公钥的文件,我想要验证签名。

我试着基于这里的方法来验证签名。

但是,decodedCrypto 和 decodedSignature 不匹配。

这是我的代码:

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();
    }
我确定令牌的签名是有效的。我尝试在https://jwt.io/上验证,显示签名已验证。所以问题在于编码、解码的算法。有人可以解决这个问题吗?该算法是RS256。

enter image description here


最简单的答案在另一个线程中被@Thomas报告了 https://dev59.com/6Gkw5IYBdhLWcg3wNX33。 - Karthick Jayaraman
2
@KarthickJayaraman,我提到了上面的参考资料,但它不起作用。难道你没有仔细看问题吗? - anhtv13
4个回答

18

你可以考虑使用JwtSecurityTokenHandler,代码可能如下所示:

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

请注意,我没有测试过它,但我们在其中一个项目中使用了类似的实现。


2
这里的“BinarySecretSecurityToken”是什么?没有参考资料。 - anhtv13
这是IdentityModel库的一部分。 - bot_insane
System.IdentityModel是.NET Framework的一部分,而System.IdentityModel.Tokens.Jwt可以通过Nuget获取。 - Helikaon
如果您可以将密钥转换为或已经拥有证书,则可以使用IssuerSgningKey与X509SecurityKey替代。 - Ilya Chernomordik
这是 Microsoft.IdentityModel.Tokens 的哪个版本?5.2.1 没有 IssuerSigningToken 选项,我也没有看到 BinarySecretSecurityToken。 - MarzSocks
显示剩余3条评论

17

我最终从我的同事那里得到了一个解决方案。

对于那些有相同问题的人,请尝试我的代码:

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

对我来说这个算法可行。该算法是RS256。


2
这对我非常有帮助,非常感谢。注意:RSACryptoServiceProvider和SHA256是可处理的。 - zeromus
是否有类似于RS256的PS256实现?还是我必须更改哈希算法来解决这个问题?我不想传递私钥,只想传递上述公钥。 - foyss
Java和Python有库,允许您通过传递算法和公钥来动态执行此操作。我对.NET领域缺乏远近相当的等效物感到非常疯狂... - djsoteric
1
什么是 "Base64UrlDecode"?我收到了这个错误:当前上下文中不存在 'Base64UrlDecode' 的名称。 - Nagaraj Alagusundaram
1
@NagarajAlagusudaram,你是否添加了参考 https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.webutilities.webencoders.base64urldecode?view=aspnetcore-3.1 ? - anhtv13

11

我知道这是一个旧帖子,但是我可以向您推荐该库,而不是自己编写。它有一些良好的文档可供参考。我正在使用它,并没有遇到任何问题。


这个RS256实现库仍然不可用,因为它需要一个私钥来验证令牌签名。 - Max Xapi

0

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

这行代码是对JWT令牌的签名部分进行Base64解码,但我知道该部分并不是Base64编码的。请尝试使用以下代码。(我已经注释掉了不必要的行)

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

这行代码返回什么?(请提供值和类型) HashAlgorithms[GetHashAlgorithm(algorithm)](keyBytes, bytesToSign) - bot_insane
据我所知,RS256应该获取公钥和私钥来计算哈希值,但您只提供了一个。 - bot_insane
我只需要一个公钥来进行验证。 我在 https://jwt.io/ 上尝试了一下,它返回了签名已验证。 - anhtv13
你只在jwt.io网站上放置了公钥?因为我看到那里需要公钥/私钥。 - bot_insane
让我们在聊天中继续这个讨论 - bot_insane
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接