将C#的RSACryptoServiceProvider翻译成JAVA代码

15

Web服务团队编写了这段C#代码,用于公开一些我打算使用的Web服务。 我的密码需要使用这个代码进行加密,这样Web服务就知道如何在其端解密它。

using(RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
    rsa.FromXmlString(publicKey);
    byte[] plainBytes = Encoding.Unicode.GetBytes(clearText);
    byte[] encryptedBytes = rsa.Encrypt(plainBytes, false);
    return Convert.ToBase64String(encryptedBytes);
}
我正在使用Java调用该Web服务,但现在翻译那个 #C 代码为Java代码存在问题,因为该Web服务无法正确解密我的密码。
这是我当前的失败尝试:-
// my clear text password
String clearTextPassword = "XXXXX";

// these values are provided by the web service team
String modulusString = "...";
String publicExponentString = "...";

BigInteger modulus = new BigInteger(1, Base64.decodeBase64(modulusString.getBytes("UTF-8")));
BigInteger publicExponent = new BigInteger(1, Base64.decodeBase64(publicExponentString.getBytes("UTF-8")));

KeyFactory keyFactory = KeyFactory.getInstance("RSA");

RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(modulus, publicExponent);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);

String encodedEncryptedPassword = new String(Base64.encodeBase64(cipher.doFinal(clearTextPassword.getBytes("UTF-8"))));

我做错了什么?非常感谢。

2013-08-07 - 更新

我在阅读这个网站时意识到我的模数值和公共指数值不是16进制的。所以,我稍微修改了一下代码,并尝试使用RSA/ECB/PKCS1PADDING,就像@Dev所提到的那样。

// my clear text password
String clearTextPassword = "XXXXX";

// these are the actual values I get from the web service team
String modulusString = "hm2oRCtP6usJKYpq7o1K20uUuL11j5xRrbV4FCQhn/JeXLT21laKK9901P69YUS3bLo64x8G1PkCfRtjbbZCIaa1Ci/BCQX8nF2kZVfrPyzcmeAkq4wsDthuZ+jPInknzUI3TQPAzdj6gim97E731i6WP0MHFqW6ODeQ6Dsp8pc=";
String publicExponentString = "AQAB";

Base64 base64Encoder = new Base64();

String modulusHex = new String(Hex.encodeHex(modulusString.getBytes("UTF-8")));
String publicExponentHex = new String(Hex.encodeHex(publicExponentString.getBytes("UTF-8")));

BigInteger modulus = new BigInteger(modulusHex, 16);
BigInteger publicExponent = new BigInteger(publicExponentHex);

KeyFactory keyFactory = KeyFactory.getInstance("RSA");

RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(modulus, publicExponent);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);

String encodedEncryptedPassword = new String(base64Encoder.encode(cipher.doFinal(clearTextPassword.getBytes("UTF-8"))));

当我访问Web服务时,出现了这个错误:"The data to be decrypted exceeds the maximum for this modulus of 128 bytes." 看起来明文密码仍然没有被正确加密。

非常感谢任何帮助或建议。谢谢。

2013-08-09 - 解决方案

我在下面发布了我的最终工作解决方案。

3个回答

15

找到解决方案了。

String modulusString = "hm2oRCtP6usJKYpq7o1K20uUuL11j5xRrbV4FCQhn/JeXLT21laKK9901P69YUS3bLo64x8G1PkCfRtjbbZCIaa1Ci/BCQX8nF2kZVfrPyzcmeAkq4wsDthuZ+jPInknzUI3TQPAzdj6gim97E731i6WP0MHFqW6ODeQ6Dsp8pc=";
String publicExponentString = "AQAB";

byte[] modulusBytes = Base64.decodeBase64(modulusString);
byte[] exponentBytes = Base64.decodeBase64(publicExponentString);
BigInteger modulus = new BigInteger(1, modulusBytes);
BigInteger publicExponent = new BigInteger(1, exponentBytes);

RSAPublicKeySpec rsaPubKey = new RSAPublicKeySpec(modulus, publicExponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PublicKey pubKey = fact.generatePublic(rsaPubKey);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);

byte[] plainBytes = clearTextPassword.getBytes("UTF-16LE");
byte[] cipherData = cipher.doFinal(plainBytes);
String encryptedStringBase64 = Base64.encodeBase64String(cipherData);

我有一种感觉,这最终会成为登录协议问题,而不是密码问题。我们已经涵盖了几乎所有其他方面。很高兴你解决了它。 - Dev
当我看到 Encoding.Unicode.GetBytes(String) 我就会想到 UTF-8,而不是 UTF-16LE,显然这是错误的假设,你能发现这点很好。 - Dev
谢谢你,解决了我的问题。看起来.NET中的人经常使用Encoding.Unicode.GetBytes / Encoding.Unicode.GetString,没有意识到它不是UTF-8。 - mvmn
如果这个解决方案对您有用,请考虑点赞以确保未来遇到类似问题的用户能够注意到它。 - limc
我在你的 C# 代码中看不到 modulusStringpublicExponentString,请问这两个值在你的代码中是如何工作的? - Panadol Chong
你对以下错误有什么想法吗?java.lang.RuntimeException:error:03000068:bignum routines:OPENSSL_internal:CALLED_WITH_EVEN_MODULUS https://stackoverflow.com/questions/55743331/java-lang-runtimeexception-error03000068bignum-routinesopenssl-internalcall - Amit Thakkar

2
根据 MSDN 上的文档 RSACryptoServiceProvider.Encrypt,当第二个参数为 false 时,密码使用 PKCS#1 v1.5 填充。因此,你的密码规范不正确。

请尝试使用 RSA/ECB/PKCS1PADDING

在第二个代码示例中,你将密钥材料转换得过多,导致数据损坏,使您的密码认为你有比实际更多的密钥材料,并使您的消息过长(触发错误),并且无法被解密的密码解密。请直接转换为字节数组并将其传递给 BigInteger

String modulusString = "...";
String publicExponentString = "...";

byte[] mod = Base64.decodeBase64(modulusString);
byte[] e = Base64.decodeBase64(publicExponentString);

BigInteger modulus = new BigInteger(1, mod);
BigInteger publicExponent = new BigInteger(1, e);

我已经尝试过使用“RSA/ECB/PKCS1PADDING”和“RSA”密码算法,但都失败了。 - limc
@limc 你使用的是哪个Base64类? - Dev
我目前正在使用Commons Codec。我最初使用了Spring的Codec,然后我认为那可能是问题所在,所以我切换到了Commons Codec...但它们产生了相同的结果。 - limc
我更新了我的帖子。如果您发现任何明显的问题,请告诉我。顺便说一下,我会为您的有用建议点赞。 - limc
我更新了JAR版本,但仍然出现相同的“登录”错误。 - limc
显示剩余4条评论

0

使用 RSA/ECB/OAEPWithSHA-1AndMGF1Padding 似乎是解决这个问题的方案。

不要忘记以下这些替换操作:

编码为 Base64。使用 System.Convert 将输入转换为 Base64。

将 + 替换为 -,将 / 替换为 _。例如: Foo+bar/=== 变成 Foo-bar_===。

将字符串末尾的 = 数量替换为整数。例如:Foo-bar_=== 变成 Foo-bar_3。

从 Base64 解码。将字符串末尾的数字替换为相同数量的 = 符号。例如:Foo-bar_3 变成 Foo-bar_===。

将 - 替换为 +,将 _ 替换为 /。例如:Foo-bar_=== 变成 Foo+bar/===。

使用 System.Convert 对预处理的输入进行 Base64 解码。


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