.NET 6中Python jwe.decrypt的等效JWT解密实现求助
.NET 6中解密JWE格式JWT令牌的正确实现
问题背景
我正在.NET 6中尝试解密JWE格式的JWT令牌,但始终无法成功。解密密钥为PEM编码的私钥,格式示例:
-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----
已有一段可正常运行的Python解密代码,现寻求对应的.NET 6等效实现。
参考Python代码
from jose import jwe, jwt, jwk, jws import base64 encrypted_jwe = "eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIn0.IDqmx1u_8Jd6XofUyTubUEAMWw9Zu2reF3JJsijRICCFRG_Dcb15pOs61DQx8DwhQbVSWA-Zbt_xlJ5dTtaC_JlP8PRFYbAkhHdOtPVUuEKIx9OEm5DOF5hi-d-VJd5JOrDaGgSSlz59vQHgY8whGzNTbJhfjyKwkz-uaEiyb8kblDAVd-mjBPcL1_1MOpMfyfuXgroi3RiR--zyjNgOUZ2R3hKZmlISAuRjrIJB8xKZntRu3GtJczX6jCgJFvWAYqrlHtMoRLuGrcOsM3WDx7urX9ur90A7idGuq2rj8aaOg2Ld-sAMfhoS0jKwK4Gj0CumAza4QvpZPVjVWaep7A.XlSCfsZVmku1CGxK.mCbCvPxsdWRDw_ZmlMtETrX_TLdxQQ0lxqFfUaX6Pv8TCChFodZb9PE0kUycW1pTTPYYbGh2d40CYgvLAMP_c1me27uFYOsy3P3CDoULKR-MNU4f19nCuRlMgWeMqG0e7GVmGQwem4TpcA4CZTdd5V0kzXBLm4ns3i3NlNoNrhfG7pW87Eb0oYo9s1wvALDmvgiFUb-apxUqJXaSIZ51pSSwkyPSLPtd_tq-8weNNuyf9SBsQUWEjRn8CHtmjuKhKOP4TaJYqjsA80ydLUUhmogrMzpqEgahitZ1ygDwv2UYwzykt5kklZ63OGPdZrYosM3t0kpi4CP9F5gyvx-jOiiMLcdz46d0t8315H9HEJW_Du3BkmW6IQ1r__mlrp2ylNMTVT7_oh_WQHUc2HEszJ-yELUSd0RM96vgXUGjWlwfex9hLYI2VaMSZ00MsarEl3HEUFnsTYIqeD8hLuJjeUGH_EXJRqh6n2-hdApO67IPRE3Eoy4rpAPZEsHrGP08WfFicjRr49PEaJlnsxVXsTxaO6smKWiCwh4JiFDYy7y-kw9orvRqyvR_8aVh1XhNFktUhtyRBOSXqN-nGxPnjD2C3lJgQAOck2i8lt_gcEiY2xqGiS0Qz-6119cRr3R2PkEXMFoVRL_8gK94OckenUg-OmIWydfJH66c_RaSGEsgozOjHCAiSiA0N2kzH_juBsLxH4CwR9VYm0lVitLVnoauVloz3fdm-so7uJ5ccFPPbfhCdKIOTwOSYxjVBJh_QWOz-BcfOeCfXIxP9PZ7DTsn2Esz2zT-hQgElOy01j0_rYfNOj92EEqRjbWGdtSr4onkAsQqIFhKJlSFQ7TbemgO6cliRWmB2Hje4XVJSVJBAv-qOh397sgCm4QYJknue0XejvIFSxaxGaoockQsA8VTl1Wtpj_Up1alKpm1tyIwVOEuQCdpy5XreSI3KxnnkKS17Ged1IT9RTL-F6X8xvgoig9rp00T9YnrXB4yNsLHkPkYHFPI4IN_O9ULrDSg_p6lN6ulc2DNY0ap-z9VSnOwKZuqnxyXxw5ymvraieRC.0fJ38Gr299EfDubtBpuRYg" decryption_key = "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ1pSS08rckhWeFBuRFkKN2Nndk1PT2lyMGVyb1NRLy8rUnhJN05CV1NraURlNCtDUFp5NzYyUlNFZ20yeHY4SGc0dmw1MnFMSzk3STd2Ngp1QnlIUlNtQU96dnduQUkrQXJkVVd3bG93T1FGSFRHQ0FQK1JrdExHOWp3VTlaK2ZBNFJaQTdvMm5Gd2ZzVDBjClBBZ3BSODlja2JkYXdtbWtDUHYxbTQ3M2hnUkNyNytRVWJoR2VWSkFVZU84SHBmOGozRmJIdHBwdTJnR1pwcnEKZnIzUUh2UW9JUFRqOVFwN1poaDhuOXpIQnd2TzBxMHRsbDJPV0V4YXh3Q2RwZ0ZUVUJiamZQWTd4Z0hqVk1TeQpBQTBocW1hcWNOdFpWdFU3MWdoYnVlOW02T1JNajhBTThmc3ZUdEFlZWdJcG9uYVBxM1RBTjVnTTRjMHFGOVJDCnZHSEt0MW5YQWdNQkFBRUNnZ0VBQ25ORUtPbVFYbWJRQWZLQWpqc3FvanFLUy9rQ1JEelo0aDc1aDNDaFhkbU8KVDN1RDlrVkNVK0FXVDFQc1BnMzhrS0VwVm1PU21YWlB5R0lLUyt6YXpndjc0OFYzWDZ3R3ZRRXlESXJ1WVN6aQo1Y2JVcGlQWkxFaTR3d01XWFp5N1J1aDZwMk9VNG5KT3hPQ1ErK09TRDNFK0M3bXF3MHo3aFAwVXkrcSsycDhQCkVOcUhPeVVyVlphTVZySjk2VVFGTXFtTGl3R2xVM2NTemlGRHlLYlpGK1RDU0kydnhMbFhRNnhtMyt5R0ZGTHEKWTAxelBFTk5BSzJ2WXUrdlNaYTVIYmpuNzFDQndSY2J1bUJYWm5PVWdLdk9XWVBEVEQwYWhSWlczVWhwWDZTbgpSTUREdUhwWDZyNjZLSTZnUy9kZ09RSHo4YTcvUVZvZm1sTmxsY21yZVFLQmdRREh6ckREeS9XMU5KczFOVCt1ClJ0M1Q2ZmZSelJzdUYzemVtL3FxUnlzbXpOWi9JWFMwZGZMdkprZmJZVVpYdVYzekxKM2ZOK0hqK2wyb1dsajQKVUVKNkJRNmtLMTA4TlJlRDdxQUNWTDBVYlhWUFRrWWZnOGFTYzNWVTNvSk1pUGM0WDNvRkZZenVOSnNqMThZTwpmVVh1a0cyNVIvdkV3NFZJeHlBZFlEWFJ5UUtCZ1FERVgxQlgzZHhNcHp2Yk9rRlVyeFNyM2x5ZDJWNzdrUnRHCm54NXJtQ2ZBQmRuMkN1cmdkNjMvdHlBbElMSVEwUGRWNEkzSjhyOVNsRGd2c2RaMS9ZWXE3Qjg1QzE4QUZRWjMKTUlJcno3VHNCTU4xZ29sMWhvbjEweUxWZ1h0RCtYSmhSU1JLZVlyY2pVZ2s2YzVnL2NNbldaSk5Qck5IQnZabgpYVTNYcTFDZW53S0JnQmh4N3lwbFA3Qmx1VDl2UGxWQ0VrMms2RTdndXh5T2Z4WjFHdHZPVFRjNEJGNFZaOFBVClVsNXozb0syaWZhZGxTTDFLSE53ME5BbGgyUGJlZHNYWnFxckI3bUtIS1FMRXM5dldHQXdvZGxuOHR3N0JWVkIKdC8rZTVKSDNqeFZ5QkZ6L1FWeHg0SVljSlliakZUWWNtWHV3YTl3cE5HSkVVWUF2N0h2cmsyb1JBb0dCQUlueQpRdWhXckNrVTJaRjViT2RtVG1rbHZYNE5jeEhacjRWK3hjbmppeGp6ZzFlNTk2RlRzV0dQYlA5bjVtNjF2T29RCjRzbll3dG5wMFVaLzhSM1Z5T2hpRXRUcHpnU3F3RzMrSDVOVFI4NzgwYW04MTM4OTBiQmtiM1g0alhVRXlIclkKTFZRNzZYWmhzU3B0TEZNcU1HdDJHclkwczQvVHJraVNYTnFDT0JJVkFvR0FlZlhMMWJmUExnOVFKRlBLUmNZcgoycWRFRzFWRC8vSFF6K3lQaTlscVB3eGM3aGwreTcvcXMxQW1peUFFK2xESEJadEMwTGJ2Si9XNWR5K1ZtZFNWCkpvRkFXS1RUdTdORHZ4dTJ3RHNFRWVnZ3AxVnBId3ZzUzRDS2ZDa0l6ZlBzTFhyMTBFSExSSW43MXByKzdEa3oKSXkxdjZ0UkZONldFdHBHVzh4T0VOeEk9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K" decrypted_token = jwe.decrypt(encrypted_jwe, base64.urlsafe_b64decode(decryption_key))
我尝试的.NET 6代码(报错)
运行时出现**“输入超出RSA密码长度”**错误:
using System.Text; using System.Security.Cryptography; using System.Text.RegularExpressions; using Org.BouncyCastle.OpenSsl; using Org.BouncyCastle.Crypto.Engines; using System.IdentityModel.Tokens.Jwt; using AesGcm = System.Security.Cryptography.AesGcm; static string DecryptJwe2(string jweToken, string decryptionKey) { var privateKeyPemBytes = Convert.FromBase64String(decryptionKey); var privateKeyPem = Encoding.UTF8.GetString(privateKeyPemBytes); var jweTokenBytes = Convert.FromBase64String(SafeBase64String(jweToken)); jweToken = Encoding.UTF8.GetString(jweTokenBytes); // 导入私钥 PemReader pemReader = new PemReader(new StringReader(privateKeyPem)); var keyPair = pemReader.ReadObject(); // RSA解密 var decryptor = new RsaEngine(); decryptor.Init(false, (Org.BouncyCastle.Crypto.ICipherParameters)keyPair); byte[] decodedToken = Encoding.UTF8.GetBytes(jweToken); byte[] decryptedToken = decryptor.ProcessBlock(decodedToken, 0, decodedToken.Length); // 此处报错 return Encoding.UTF8.GetString(decryptedToken); } static string SafeBase64String(string base64String) { base64String = base64String.Replace('-', '+').Replace('_', '/'); int padding = base64String.Length % 4; if (padding > 0) base64String += new string('=', 4 - padding); return base64String; }
错误原因
直接对整个JWE令牌调用RSA解密是错误的。JWE的解密流程分阶段:先用RSA私钥解密出对称加密密钥,再用该密钥解密实际令牌内容。手动调用RSA引擎无法处理这一复杂流程,且RSA本身有明文长度限制,因此会报“输入超出长度”的错误。
正确的.NET 6实现
使用官方的System.IdentityModel.Tokens.Jwt库处理JWE解密,无需手动操作RSA引擎。先安装以下NuGet包:
Install-Package System.IdentityModel.Tokens.Jwt Install-Package Microsoft.IdentityModel.Tokens
完整代码实现
using System.IdentityModel.Tokens.Jwt; using Microsoft.IdentityModel.Tokens; using System.Security.Cryptography; using System.Text; static string DecryptJwe(string jweToken, string base64EncodedPemKey) { // 解码Base64格式的PEM私钥 var pemBytes = Convert.FromBase64String(base64EncodedPemKey); var pemString = Encoding.UTF8.GetString(pemBytes); // 导入RSA私钥 using var rsa = RSA.Create(); rsa.ImportFromPem(pemString.ToCharArray()); // 配置令牌验证参数 var tokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, TokenDecryptionKey = new RsaSecurityKey(rsa) }; // 处理JWE解密 var handler = new JwtSecurityTokenHandler(); handler.ReadJwtToken(jweToken); // 解析令牌结构 var result = handler.ValidateToken(jweToken, tokenValidationParameters, out _); // 返回解密后的令牌内容 return result.ToString(); }
代码说明
- 导入私钥:使用.NET 6内置的
RSA.ImportFromPem方法直接导入PEM格式私钥,无需依赖BouncyCastle。 - 配置验证参数:设置
TokenDecryptionKey为导入的RSA密钥,关闭发行者和受众验证(可根据实际需求调整)。 - 解密JWE:
JwtSecurityTokenHandler自动处理完整解密流程——用RSA私钥解密出对称密钥,再用对称密钥解密令牌内容,最后返回明文。
内容的提问来源于stack exchange,提问作者João Antunes
相关产品推荐
相关产品推荐

