如何将Java的X509EncodedKeySpec/RSA加密转为.NET 4.8的C#代码?
如何将指定Java RSA加密代码转换为.NET Framework 4.8的C#代码?
问题背景
需要将以下Java RSA加密代码转换为适用于.NET Framework 4.8的C#代码,已尝试两种BouncyCastle实现但无法通过单元测试。
原Java代码
private static String getBearerToken(String publicKeyBase64, String apiKey) { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); Cipher cipher = Cipher.getInstance("RSA"); byte[] encodedPublicKey = Base64.decodeBase64(publicKeyBase64); X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedPublicKey); PublicKey pk = keyFactory.generatePublic(publicKeySpec); cipher.init(Cipher.ENCRYPT_MODE, pk); byte[] encryptedApiKey = Base64.encodeBase64(cipher.doFinal(apiKey.getBytes("UTF-8"))); return new String(encryptedApiKey, "UTF-8"); }
未通过测试的C#尝试
第一种尝试(未完成):
public static string GetBearerToken(string publicKeyBase64, string apiKey) { var key = Convert.FromBase64String(publicKeyBase64); var info = SubjectPublicKeyInfo.GetInstance(key); var pk = PublicKeyFactory.CreateKey(info); var x = ???(pk); using (var rsa = new RSACryptoServiceProvider(???)) { var parameters = new RSAParameters() { Modulus = x.???, Exponent = x.??? }; rsa.ImportParameters(parameters); var data = Encoding.UTF8.GetBytes(apiKey); var encryptedBytes = rsa.Encrypt(data, true); return Convert.ToBase64String(encryptedBytes); } }
第二种尝试(可运行但不通过测试):
public static string GetBearerToken(string publicKeyBase64, string apiKey) { var keyBytes = Convert.FromBase64String(publicKeyBase64); var info = SubjectPublicKeyInfo.GetInstance(keyBytes); var keyParameter = PublicKeyFactory.CreateKey(info); var encryptEngine = new RsaEngine(); encryptEngine.Init(true, keyParameter); var dataToEncrypt = Encoding.UTF8.GetBytes(apiKey); var encryptedBytes = encryptEngine.ProcessBlock(dataToEncrypt, 0, dataToEncrypt.Length); return Convert.ToBase64String(encryptedBytes); }
单元测试用例
[TestMethod] public void Utils_GetBearerToken() { var publicKey = "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAszE+xAKVB9HRarr6/uHYYAX/RdD6KGVIGlHv98QKDIH26ldYJQ7zOuo9qEscO0M1psSPe/67AWYLEXh13fbtcSKGP6WFjT9OY6uV5ykw9508x1sW8UQ4ZhTRNrlNsKizE/glkBfcF2lwDXJGQennwgickWz7VN+AP/1c4DnMDfcl8iVIDlsbudFoXQh5aLCYl+XOMt/vls5a479PLMkPcZPOgMTCYTCE6ReX3KD2aGQ62uiu2T4mK+7Z6yvKvhPRF2fTKI+zOFWly//IYlyB+sde42cIU/588msUmgr3G9FYyN2vKPVy/MhIZpiFyVc3vuAAJ/mzue5p/G329wzgcz0ztyluMNAGUL9A4ZiFcKOebT6y6IgIMBeEkTwyhsxRHMFXlQRgTAufaO5hiR/usBMkoazJ6XrGJB8UadjH2m2+kdJIieI4FbjzCiDWKmuM58rllNWdBZK0XVHNsxmBy7yhYw3aAIhFS0fNEuSmKTfFpJFMBzIQYbdTgI28rZPAxVEDdRaypUqBMCq4OstCxgGvR3Dy1eJDjlkuiWK9Y9RGKF8HOI5a4ruHyLheddZxsUihziPF9jKTknsTZtF99eKTIjhV7qfTzxXq+8GGoCEABIyu26LZuL8X12bFqtwLAcjfjoB7HlRHtPszv6PJ0482ofWmeH0BE8om7VrSGxsCAwEAAQ=="; var apiKey = "aaaab09uz9f3asdcjyk7els777ihmwv8"; var expectedToken = "rfNjFso4uJbzhwl8E9vizqmHEuD7XDmPqfsRx1L62UoTmURGGLAGgJSl9lCPbgy03Q7NwozFYD4r9BFQY5QpvErHximBDU8HE25urVahm0HnB8VyCIobs684XGSN4GjdequePDrG6xUAxxpvmhqZRlGt1tUjUBeBg6kYqp4EnKHsiaBtvd0THGLZbefpT6UaShASQWYNiEPwEon5wtUMaDwnyQEazDu1H2ieN3r8cCVM3hsak59J/1MP07FQjdFbxdCLfA0DuxgpeKpvLs7WrA767WJSB1QZy7hcP1igSGRfd7Zrp6E7gIukdpC0DApqPKa4XsNTo2AMpG4AwiET2WeKvHn539gbwREXf79kZlYdFDCgTc0Zs7OfDx5ZXMCBKHOS/H3tVFJqXTfEfIF5LOzrFU5pPE0HeNBV0Q2vm8qRwQX0RijnvMOGpdcmXb0qoph4oy8Mj+vjRfFRboMAafttDozBhRmWEmeBB3EjYASm1fToQp5ey6ltCiEt8rjL5PlexxB0u3u2LVJQcDzMVNiiq10t1xyw8qtc6BMOyrKVlIANWglRYOKr9saVBVvDFUcCfsghMjUTDeAwHom4A3cSDWmVlNF9Vs/WqCoUzjQCV0BFPDzeAUbQqt7h7OgFno/+D9n5j1eMro0aXbbHNx71u8YmgPJhdixzFhxM1Pw="; var token = Utils.GetBearerToken(publicKey, apiKey); Assert.AreEqual(expectedToken, token, "GetBearerToken"); }
正确实现方案
关键点说明
Java中Cipher.getInstance("RSA")默认使用RSA/ECB/PKCS1Padding填充模式,之前的C#代码使用RsaEngine时未指定填充,导致结果不匹配。需要明确指定PKCS1填充才能和Java代码行为一致。
方案1:使用BouncyCastle的PaddedBufferedBlockCipher
using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; using System; using System.Text; public static string GetBearerToken(string publicKeyBase64, string apiKey) { var keyBytes = Convert.FromBase64String(publicKeyBase64); var publicKeyInfo = SubjectPublicKeyInfo.GetInstance(keyBytes); var rsaPublicKey = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyInfo); // 指定PKCS1填充,匹配Java默认的RSA/ECB/PKCS1Padding var cipher = new PaddedBufferedBlockCipher(new RsaEngine(), new Org.BouncyCastle.Crypto.Paddings.Pkcs7Padding()); cipher.Init(true, rsaPublicKey); var dataBytes = Encoding.UTF8.GetBytes(apiKey); var encryptedBytes = new byte[cipher.GetOutputSize(dataBytes.Length)]; var length = cipher.ProcessBytes(dataBytes, 0, dataBytes.Length, encryptedBytes, 0); cipher.DoFinal(encryptedBytes, length); return Convert.ToBase64String(encryptedBytes); }
方案2:使用.NET自带的RSACryptoServiceProvider结合BouncyCastle解析公钥
using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; using System; using System.Security.Cryptography; using System.Text; public static string GetBearerToken(string publicKeyBase64, string apiKey) { var keyBytes = Convert.FromBase64String(publicKeyBase64); var publicKeyInfo = SubjectPublicKeyInfo.GetInstance(keyBytes); var rsaParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyInfo); // 将BouncyCastle的RsaKeyParameters转换为.NET的RSAParameters var dotNetParams = new RSAParameters { Modulus = rsaParams.Modulus.ToByteArrayUnsigned(), Exponent = rsaParams.Exponent.ToByteArrayUnsigned() }; using (var rsa = new RSACryptoServiceProvider()) { rsa.ImportParameters(dotNetParams); // false对应PKCS1填充,匹配Java默认行为 var encryptedBytes = rsa.Encrypt(Encoding.UTF8.GetBytes(apiKey), false); return Convert.ToBase64String(encryptedBytes); } }
失败原因分析
- 第一种未完成代码:未掌握BouncyCastle密钥到.NET密钥的转换逻辑,需先将
AsymmetricKeyParameter强制转换为RsaKeyParameters,再提取Modulus和Exponent。 - 第二种代码:使用
RsaEngine时未添加PKCS1填充,与Java默认填充模式不匹配,导致加密结果不一致。
内容的提问来源于stack exchange,提问作者Etherman
相关产品推荐
相关产品推荐

