用Java重新实现RSA解密

时间:2012-12-19 20:53:36

标签: java rsa padding

我正在使用私有RSA密钥来加密使用默认Java RSA实现的随机AES密钥:

Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] cipherBytes = cipher.doFinal(plainText.getBytes());

由于我们无论如何都需要公钥,这是一种方便的方法来伪装密钥并确保它已使用我们的私钥加密。解密类似地完成:

Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] plainBytes = cipher.doFinal(cipherBytes);

这适用于Oracle的JDK,但由于IBM认为使用私钥进行加密不是一个有效的用例,因此IBM失败了。不幸的是,我必须支持两个JDK,所以我试图自己重新实现RSA解密。

这是我到目前为止的代码:

BigInteger big = new BigInteger(cipherBytes);
big = big.modPow(pub.getPublicExponent(), pub.getModulus()); 
System.out.println(new String(big.toByteArray()));

它几乎可以工作,但似乎存在填充问题。大部分时间我都会在原始文本前面加上一串点状符号,但有时它只是随机字节。

不幸的是,我无法确定默认使用哪种填充方案。有谁知道我的代码中缺少什么,或者至少可以提示处理填充的算法?

以下是输入和输出值的示例,如请求的那样。我使用了512位密钥来避免太大的数字。

Public modulus :  8117919732251191237549784557538073836207094968952416063837701691514861428726690140363567956265691836505266266364256892197254736023284927189008247933889303
Public exponent:  65537
Plaintext:        teststring
Plaintext as BN:  549665952565679142563431
Ciphertext as BN: 6304229782339071167863563708554898540621778162930150363326921290545577949349781053660336996882823758722402137580193903457839924005473545992074817339077456
"Decrypted" BN:   409173825987017733751648712103449894027080255755383098685411421012016724550584319360408761540738019643860835515945008876151848132891805352276483731047
Resultstring: ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇteststring

为了解决这个问题我为什么这样做:

公钥被硬编码到我的软件中。我使用私钥加密 AES的另一个密钥。因此,要使用AES实际解码任何内容,首先需要AES密钥。要获得此密钥,您必须首先使用公钥解密它。由于在没有严格操作的情况下无法修改公钥,因此只有使用私钥加密的AES密钥才能工作。您可以以某种方式提取公钥并解密AES密钥,但这是精心设计的,只能获得AES密钥来解密受保护的内容。还有一个使用私钥计算的签名,该私钥也使用公钥进行验证。所以操纵是不可能的。

所以是的,从技术上讲,签名就足够了,因为有方法可以阅读内容。但这些都是精心设计的,我不介意是否有人真正解决了所有麻烦,但我不想让事情变得简单。

3 个答案:

答案 0 :(得分:1)

公钥用于加密和验证签名。私钥用于解密和签名。公钥只是:public。如果你做得对,就没有理由隐藏公钥。

答案 1 :(得分:0)

您尝试做的事情看起来更像是签名而不是加密。使用单独的密钥对进行签名,因为它与加密不完全相同。

答案 2 :(得分:0)

好的,我已经通过阅读RSA规范来解决这个问题了。为了增加安全性,在加密之前添加填充并创建以下“字符串”:

0x00 + BT + Padding + 0x00 + Data

块类型(BT)表示填充的类型。在BT = 0x01时,填充是0xff并且BT = 0x02,填充是随机的但非零。然后对连接的字符串进行加密。

解密时,可以验证格式,但只需读取数据,就必须删除前导字节。它们都是非零,直到数据之前的0x00。因此,可以删除填充之后的所有内容,包括0x00。剩下的就是信息。

此代码现在可以使用:

// Decrypt
byte[] decryptedBytes = (new BigInteger(1, cipherBytes)).modPow(pub.getPublicExponent(), pub.getModulus()).toByteArray();

// Extract msg
int dataStart;
for (dataStart = 0; decryptedBytes[msgStart] != 0; dataStart++);
dataStart++;

byte finalBytes[] = new byte[decryptedBytes.length - msgStart];
System.arraycopy(decryptedBytes, msgStart, finalBytes, 0, finalBytes.length);

这也解释了我之前尝试过的字符串“^”。那些是填充字节,它们是0xff,BT = 0x01。

我只需要解密,但为了完整起见,这是加密代码:

int bitLength = 512;
String plainText = "teststring";

// Convert to bytes
byte plainBytes[] = plainText.getBytes(); 

byte encryptionBytes[] = new byte[bitLength / 8];

encryptionBytes[0] = 0; // Leading 0
encryptionBytes[1] = 1; // Block type

// Padding String
int paddingEnd = (bitLength / 8) - plainBytes.length - 2;
for (int i = 2; i < paddingEnd; i++) {
    encryptionBytes[i] = (byte) 0xff;
}
encryptionBytes[paddingEnd + 1] = 0;

// Actual data
System.arraycopy(plainBytes, 0, encryptionBytes, paddingEnd + 1, plainBytes.length);

// Encrypt
byte[] cipherBytes = (new BigInteger(1, encryptionBytes)).modPow(priv.getPrivateExponent(), priv.getModulus()).toByteArray();

希望这有助于任何人:)