将C#的RSACryptoServiceProvider代码翻译成Java

5

我需要为项目相关目的加密字符串,并由供应商提供以下代码。

public static string EncryptString(string StringToEncrypt)
{
    RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
    string xmlString = "<RSAKeyValue><Modulus>qqoWhMwGrrEBRr92VYud3j+iIEm7652Fs20HvNckH3tRDJIL465TLy7Cil8VYxJre69zwny1aUAPYItybg5pSbSORmP+hMp6Jhs+mg3qRPvHfNIl23zynb4kAi4Mx/yEkGwsa6L946lZKY8f9UjDkLJY7yXevMML1LT+h/a0a38=</Modulus><Exponent>AQAB</Exponent><P>20PwC7nSsfrfA9pzwSOnRYdbhOYivFSuERxvXHvNjCll5XdmFYYp1d2evXcXbyj3E1k8azce1avQ9njH85NMNQ==</P><Q>x0G0lWcQ13NDhEcWbA7R2W5LPUmRqcjQXo8qFIaHk7LZ7ps9fAk/kOxaCR6hvfczgut1xSpXv6rnQ5IGvxaHYw==</Q><DP>lyybF2qSEvYVxvFZt8MeM/jkJ5gIQPLdZJzHRutwx39PastMjfCHbZW0OYsflBuZZjSzTHSfhNBGbXjO22gmNQ==</DP><DQ>NJVLYa4MTL83Tx4vdZ7HlFi99FOI5ESBcKLZWQdTmg+14XkIVcZfBxDIheWWi3pEFsWqk7ij5Ynlc/iCXUVFvw==</DQ><InverseQ>X5Aw9YSQLSfTSXEykTt7QZe6SUA0QwGph3mUae6A2SaSTmIZTcmSUsJwhL7PLNZKbMKSWXfWoemj0EVUpZbZ3Q==</InverseQ><D>jQL4lEUYCGNMUK6GEezIRgiB5vfFg8ql3DjsOcXxnOmBcEeD913kcYnLSBWEUFW55Xp0xW/RXOOHURgnNnRF3Ty5UR73jPN3/8QgMSxV8OXFo3+QvX+KHNHzf2cjKQDVObJTKxHsHKy+L2qjfULA4e+1cSDNn5zIln2ov51Ou3E=</D></RSAKeyValue>";
    provider.FromXmlString(xmlString);
    return Convert.ToBase64String(provider.Encrypt(Encoding.ASCII.GetBytes(StringToEncrypt), false));
}

然而,我需要修改或将其翻译成JAVA。我已经为此编写了以下方法。

public static String EncryptString(String strToBeEncrypted) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException
{
    String modulusString = "qqoWhMwGrrEBRr92VYud3j+iIEm7652Fs20HvNckH3tRDJIL465TLy7Cil8VYxJre69zwny1aUAPYItybg5pSbSORmP+hMp6Jhs+mg3qRPvHfNIl23zynb4kAi4Mx/yEkGwsa6L946lZKY8f9UjDkLJY7yXevMML1LT+h/a0a38=";
    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 = strToBeEncrypted.getBytes("US-ASCII");
    byte[] cipherData = cipher.doFinal(plainBytes);
    String encryptedStringBase64 = Base64.encodeBase64String(cipherData);

    return encryptedStringBase64;
}

但样本结果不匹配。

字符串是“4111111111111111”,加密后应该是:

PfU31ai9dSwWX4Im19TlikfO9JetkJbUE+btuvpBuNHTnnfrt4XdM4PmGA19z8rF+lPUC/kcOEXciUSxFrAPyuRJHifIDqWFbbJvPhatbf269BXUiAW31UBX3X5bBOqNWjh4LDitYY0BtarlTU4xzOFyb7vLpLJe9aHGWhzs6q0=

但来自Java代码的结果是

Cxp5AIzTHEkrU6YWwYo5yYvpED2qg9IC/0ct+tRgDZi9fJb8LAk+E1l9ljEt7MFQ2KB/exo4NYwijnBKYPeLStXyfVO1Bj6S76zMeKygAlCtDukq1UhJaJKaCXY94wi9Kel09VTmj+VByIYvAGUFqZGaK1CyLnd8QXMcdcWi3sA=


2
如果您再次运行C#和Java代码会发生什么?密文是否会改变?如果是这样,那么使用了随机填充(重要),您需要在一个中进行加密,在另一个中进行解密以检查兼容性。 - Artjom B.
谢谢 @ArtjomB。我刚刚检查了与供应商解密代码的兼容性,它可以正常工作。 - RahulMuk07
@ArtjomB。对于加密,必须使用随机填充。加密必须是非确定性的,否则您将获得相同明文的相同密文(与对称ECB模式加密相同)。只有课本RSA是确定性的,并且对于各种原因非常不安全。签名生成可能是确定性的。你能发一个回答吗? - Maarten Bodewes
@MaartenBodewes PKCS#1 v1.5类型1填充未随机化。虽然使用的可能性很小,但仍然存在。 - Artjom B.
@ArtjomB。干得好。我没有在实践中看到过,但我会确保它不会出现在后续问题中。 - Maarten Bodewes
1个回答

2
每个加密算法都需要随机化,以提供语义安全性。否则,攻击者可能会通过观察密文来注意到您已经再次发送了相同的消息。在对称密码中,这个属性是通过随机IV实现的。在RSA中,这是通过随机填充(PKCS#1 v1.5类型2和PKCS#1 v2.x OAEP是随机的)实现的。
您可以通过使用相同的密钥和明文再次运行加密,并将密文与先前的密文进行比较,以检查填充是否随机化。如果在执行期间C#或Java中的密文发生变化,则仅通过查看密文,您将无法确定加密是否兼容。
正确的检查方法是在一种语言中加密某些内容,然后在另一种语言中解密。为了完全兼容,您还应该尝试反向操作。
从您的代码来看,两者似乎是等效的,因为在RSACryptoServiceProvider#Encrypt中传递了false作为第二个参数,以使用PKCS#1 v1.5填充,并且Cipher.getInstance(“RSA / ECB / PKCS1PADDING”)请求相同的填充。输入/输出编码也似乎是等效的。因此,是的,这段代码将是等效的。
PKCS#1 v1.5 填充现在不应该使用,因为它容易受到 Bleichenbacher 攻击(参考文献)。你应该使用 OAEP 进行加密和 PSS 进行签名,这被认为是安全的。C# 和 Java 都支持 OAEP,但默认使用的哈希函数(哈希和 MGF1)可能会有所不同。

你好,我在这个问题中遇到了同样的情况。如果使用随机填充,那么如何在不解密数据库中的密码的情况下检查用户输入的密码是否与数据库中的密码相同?因为我听说解密密码不是一个好的做法。我们只检查加密值是否相同。然而,在这里我看不到如何做到这一点。唯一的方法似乎是从数据库中解密密码。 - nanospeck
@nanospeck 你绝不应该对用户的密码进行加密。相反,你应该使用哈希函数来进行处理,其中一些强大的哈希函数包括PBKDF2、bcrypt、scrypt和Argon2。由于哈希函数是单向函数,你将无法“解密”这些哈希值。为了验证用户身份,你可以再次将密码通过哈希函数处理,并与存储在数据库中的哈希值进行比较。详情请参考:如何安全地哈希密码? 使用RSA绝对不是正确的工具。 - Artjom B.
你的话对这个话题真的帮了我很多。不幸的是,我正在将一个已经在.NET中使用RSACryptoServiceProvider实现的项目移植到Java,所以无法重新实现密码加密。 - nanospeck

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