我最近开始编写一个用Java编写的在线游戏的Android版本。但是,我遇到了与加密不一致的问题。 Java应用程序工作正常 - 它从文件读取公钥,加密一些文本并将其传递到服务器,在那里使用私钥正确解密。在android上,一切似乎都有效(并且正在运行相同的代码),但是服务器有一个BadPaddingException试图解密消息。我已经在下面列出了所有相关代码和一步一步的事件序列:
连接到服务器时发生的第一件事是对称密钥的协议。这是在客户端上生成的,因此:
SecretKey symmetricKey = null;
try
{
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
symmetricKey = keyGen.generateKey();
}
catch (Throwable t)
{
Debug.stackTrace(t, "Failed to generate symmetric key.");
}
return symmetricKey;
然后将其转换为Base64字符串:
byte[] keyBytes = secretKey.getEncoded();
return base64Interface.encode(keyBytes);
使用公钥加密:
public static String encrypt(String messageString, Key key)
{
String encryptedString = null;
try
{
byte[] messageBytes = messageString.getBytes();
String algorithm = key.getAlgorithm()
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cipherData = cipher.doFinal(messageBytes);
encryptedString = base64Interface.encode(cipherData);
//Strip out any newline characters
encryptedString = encryptedString.replaceAll("\n", "");
encryptedString = encryptedString.replaceAll("\r", "");
}
catch (Throwable t)
{
Debug.append("Caught " + t + " trying to encrypt message: " + messageString);
}
return encryptedString;
}
在这种形式中,它被传递给使用私钥解密消息并恢复SecretKey对象的服务器:
public static String decrypt(String encryptedMessage, Key key)
{
String messageString = null;
try
{
byte[] cipherData = base64Interface.decode(encryptedMessage);
String algorithm = key.getAlgorithm();
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] messageBytes = cipher.doFinal(cipherData);
messageString = new String(messageBytes);
}
catch (Throwable t)
{
Debug.append("Caught " + t + " trying to decrypt message: " + encryptedMessage, failedDecryptionLogging);
}
return messageString;
}
但是,每当我对从Android应用程序传递的消息执行此操作时,doFinal行会产生以下异常:
11/07 12:55:55.975 javax.crypto.BadPaddingException: Decryption error
at sun.security.rsa.RSAPadding.unpadV15(Unknown Source)
at sun.security.rsa.RSAPadding.unpad(Unknown Source)
at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:354)
at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:380)
at javax.crypto.Cipher.doFinal(Cipher.java:2121)
at util.EncryptionUtil.decrypt(EncryptionUtil.java:85)
at server.MessageHandlerRunnable.handleUnencryptedMessage(MessageHandlerRunnable.java:226)
at server.MessageHandlerRunnable.getResponse(MessageHandlerRunnable.java:188)
at server.MessageHandlerRunnable.run(MessageHandlerRunnable.java:85)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
我的第一个想法是问题必须在Base64编码/解码中,因为这是Android和桌面版本之间不同的代码。但是,我已经做了一些测试并验证了这些是一致的,并且我的服务器代码可以使用任一编码方法恢复原始文本。
我的下一个想法是Android版本必须以某种方式使用错误的公钥。这是在启动时从两个平台通用的文件生成的,使用以下代码:
public static void generatePublicKey()
{
InputStream in = null;
ObjectInputStream oin = null;
try
{
in = KeyGeneratorUtil.class.getResourceAsStream("/assets/public.key");
oin = new ObjectInputStream(new BufferedInputStream(in));
BigInteger m = (BigInteger) oin.readObject();
BigInteger e = (BigInteger) oin.readObject();
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(m, e);
KeyFactory fact = KeyFactory.getInstance("RSA");
MessageUtil.publicKey = fact.generatePublic(keySpec);
}
catch (Throwable e)
{
Debug.stackTrace(e, "Unable to read public key - won't be able to communicate with Server.");
}
finally
{
if (in != null)
{
try {in.close();} catch (Throwable t) {}
}
if (oin != null)
{
try {oin.close();} catch (Throwable t) {}
}
}
}
当我查看两个平台上的键(使用toString())时,我看到以下内容(我已经截断了模数):
桌面:
Sun RSA公钥,1024位模数:11920225567195913955197820411061866681846853580 ...公开指数:65537
机器人:
OpenSSLRSAPublicKey {modulus = a9bfe8d8a199fc6a ...,publicExponent = 10001}
乍一看,这些看起来完全不同,但我现在确信它们与十进制(桌面)和另一个十六进制(Android)相同。十六进制中的10001相当于十进制中的65537,并且将十六进制模数放入在线转换器中会产生一个至少以十进制模数的正确数字开头的数字。那么,为什么我看到BadPaddingException?
最后值得注意的是,这似乎与一年前在这个问题中提出的问题相同:
RSA on Android is different from PC
然而,没有提出解决方案,我认为值得提出一个新问题,我可以提供我可以获得的所有信息。
答案 0 :(得分:3)
BadPaddingException
通常由以下之一引起:
传递给解密函数的密文不等于从加密函数接收的密文(只有一个比特差异会完全破坏该过程)。
使用不同的密钥(或不匹配的私钥)来解密数据。
该程序尝试使用方案A
取消用方案B
填充的消息。
由于您已经验证了1.和2.是正确的,3。很可能是您的问题根源。
您在两端使用相同的编程语言相同的代码,那么为什么会出现任何不兼容性?罪魁祸首是以下两行代码:
String algorithm = key.getAlgorithm();
Cipher cipher = Cipher.getInstance(algorithm);
由于密钥可以与任何填充方案结合使用,因此只保存其所用的算法,这会使key.getAlgorithm()
返回"RSA"
。调用Cipher.getInstance("RSA")
通常不是一个好主意,因为Java会自动为密码模式和填充选择依赖于平台的默认值。
这可以通过传递完整的密码字符串("<algorithm>/<mode>/<padding>"
)来避免,例如"RSA/ECB/PKCS1Padding"
。在使用JCE库时,对所有Cipher
实例执行此操作始终是个好主意。
答案 1 :(得分:0)
如果您想使用公钥 - 私钥对进行RSA加密/解密,这里有一个适用于Android以及任何服务器/桌面Java程序的代码示例
它有三种方法:
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import javax.crypto.Cipher;
public class Test {
private static final String RSA_ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";
public static void main(String[] args) {
String data = "Hello World";
KeyPair kp = generateRSAKeyPair();
PublicKey publicKey = kp.getPublic();
PrivateKey privateKey = kp.getPrivate();
byte[] encryptedValue = encryptRSA(publicKey, data.getBytes());
byte[] decrytpedValue = decryptRSA(privateKey, encryptedValue);
String decryptedData = new String(decrytpedValue);
System.out.println(decryptedData);
}
public static KeyPair generateRSAKeyPair() {
KeyPairGenerator keyGen;
try {
keyGen = KeyPairGenerator.getInstance("RSA");
SecureRandom rnd = new SecureRandom();
keyGen.initialize(2048, rnd);
KeyPair keyPair = keyGen.genKeyPair();
return keyPair;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
public static byte[] encryptRSA(Key key, byte[] data) {
byte[] cipherText = null;
try {
final Cipher cipher = Cipher.getInstance(RSA_ECB_PKCS1_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, key);
cipherText = cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
}
return cipherText;
}
public static byte[] decryptRSA(Key key, byte[] data) {
byte[] decryptedText = null;
try {
final Cipher cipher = Cipher.getInstance(RSA_ECB_PKCS1_PADDING);
cipher.init(Cipher.DECRYPT_MODE, key);
decryptedText = cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
}
return decryptedText;
}
}