2015-07-11 40 views
5

我最近开始编写我使用Java编写的在线游戏的Android版本。但是,我遇到了与加密不一致的问题。 Java应用程序工作正常 - 它从文件中读取公钥,加密一些文本并将其传递到使用私钥正确解密的服务器。在android上,一切都似乎工作(并且正在运行相同的代码),但服务器有一个BadPaddingException尝试解密消息。我已经包含了以下所有相关代码和一步一步的事件:非对称加密差异 - Android vs Java

连接到服务器时发生的第一件事是对称密钥的协议。这是在客户端上产生的,从而:

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()),我看到以下(:这是从文件中启动它是常见的使用下面的代码在这两个平台产生我已经被截断模量):

桌面:

孙RSA公共密钥,1024位模数:11920225567195913955197820411061866681846853580 ...公用指数:65537

安卓

OpenSSLRSAPublicKey {模= a9bfe8d8a199fc6a ...,publicExponent = 10001}

乍一看这似乎是完全不同的,但是我现在相信,它们是等价的一个十进制(桌面)表示,另以十六进制(Android)。 10001十六进制等于65537十进制,并将十六进制模数放入在线转换器中产生一个数字,该数字至少以小数模的正确数字开始。那么,为什么我看到BadPaddingException?

最后一点值得注意的是,这似乎是在这个问题提出了大约一年前,同样的问题:

RSA on Android is different from PC

然而,没有解决方案提出,我认为这值得我们作出一个新的问题,我可以提供我可以获得的所有信息。

+1

你传递给'Cipher.getInstance()'什么字符串?我曾经有一个非常类似的问题,因为只传递了“RSA”,导致应用程序和服务器使用平台相关的默认值,这些默认值恰好不同。指定完整的密码字符串,即“RSA/ECB/PKCS1Padding”,为我解决了这个问题。 –

+0

它在Android和桌面上都以“RSA”出现。我已经快速调整了它,它看起来很有前途 - 一旦我(希望)有它的工作,我会发布我的全部发现! –

+0

指定'RSA/ECB/PKCS1Padding'可以在两个平台上运行,并解决了我的问题 - 将其作为答案发布,以便我可以接受它作为解决方案。谢谢! –

回答

2

BadPaddingException s的通常由下列之一引起的:

  1. 传递给解密函数中的密文不等于从所述加密函数(只是一个单一的比特差接收到的密文将彻底打破这一过程)。

  2. 使用不同的密钥(或不匹配的私钥)来解密数据。

  3. 程序尝试使用方案B解除用方案A填充的消息。

由于您已经验证过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实例执行此操作总是一个好主意。

0

如果要使用公共 - 私有密钥对做RSA加密/解密,这里是一个代码示例,在Android的作品,以及任何服务器/桌面Java程序

它有三种方法:

  1. generateRSAKeyPair - 生成公用密钥对。您必须将公钥存储在您的服务器上的应用程序和私钥上。
  2. encryptRSA - 此方法使用公钥加密给定的数据。它返回表示加密值的字节数组。如果您需要,您可以对此值执行base 64编码,以便您可以在JSON中使用它
  3. decryptRSA - 使用私钥解密加密值。确保将原始加密值传递给此方法,而不是基本64编码值。

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; 
    } 

}