Asymmetric Encryption Mismatch - Android vs. Java

I recently started writing a version of Android for an online game written in Java. However, I am facing inconsistency with encryption. The java application works fine - it is read in a public key from a file, encrypts the text and sends it to the server, where it is decrypted correctly using the private key. Everything works on android (and works through the same code), but the server has a BadPaddingException that tries to decrypt the message. I have included all the relevant code and a step-by-step sequence of events below:

The first thing that happens when you connect to the server is the consent of the symmetric key. This is generated on the client, thus:

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; 

Then it is converted to the Base64 string:

 byte[] keyBytes = secretKey.getEncoded(); return base64Interface.encode(keyBytes); 

And encrypted using the public key:

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

In this form, it is passed to the server, which uses the private key to decrypt the message and restore the SecretKey object:

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

However, whenever I do this from a message passed from an Android application, the doFinal line throws the following exception:

 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) 

My first thought was that the problem with Base64's encoding / decoding was that it was the only bit of code that was different from the versions of Android and desktop computers. However, I did some testing and checked that they are consistent and that my server code can restore the source text using either the encoding method.

My next thought was that the version of Android must somehow use the wrong public key. This is generated when launched from a file that is common to both platforms using the following code:

 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) {} } } } 

When I look at the key on two platforms (using toString ()), I see the following (I trimmed the modules):

Desktop:

RS RSS public key, 1024 bit module: 11920225567195913955197820411061866681846853580 ... public indicator: 65537

Android:

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

At first glance, they seem completely different, but I am now convinced that they are equivalent to one expression in decimal (Desktop) and another in hex (Android). 10001 in hexadecimal equivalent is 65537 in decimal, and including the hex module in an online converter gives a number that at least starts with the correct digits for the decimal module. Why am I seeing a BadPaddingException?

One great thing worth noting is that it seems to be the same issue that was raised on this issue about a year ago:

RSA on Android is different from PC

However, no solution was presented, and I thought it was worth asking a new question, where could I provide all the information available to me for me.

+5
source share
2 answers

BadPaddingException usually BadPaddingException one of the following:

  • The encrypted text passed to the decryption function is not equal to the encrypted text received from the encryption function (only a one-difference difference will completely destroy the process).

  • To decrypt the data, another key is used (or a non-corresponding private key).

  • The program tries to unpack the message, supplemented by scheme A , using scheme B

Since you already checked 1. and 2. correctly, 3. most likely the root of your problem.

You use the same code in the same programming language at both ends, so why should there be any incompatibilities? The criminals are the following two lines of code:

 String algorithm = key.getAlgorithm(); Cipher cipher = Cipher.getInstance(algorithm); 

Since the key can be used in conjunction with any padding scheme, it only stores the algorithm, which has the value key.getAlgorithm() returns "RSA" . Calling Cipher.getInstance("RSA") is generally not a good idea, because Java automatically selects default platform values ​​for encryption and padding mode.

This can be avoided by passing full string lines ( "<algorithm>/<mode>/<padding>" ), for example, "RSA/ECB/PKCS1Padding" . It is always recommended to do this for all Cipher instances when using the JCE library.

+2
source

If you want to make RSA encryption / decryption using a Public-Private key pair, here is an example of code that works on Android, as well as any server / desktop Java program

It has three methods:

  • generateRSAKeyPair . This generates a pair of public private keys. You will have to store the public key in the application and the private key on your server.
  • encryptRSA . This method encrypts data using a public key. It returns an array of bytes that represents the encrypted value. If you need, you can do basic 64 encoding of this value so that you can use it in JSON
  • decryptRSA . This decrypts the encrypted value with the private key. Make sure you pass the raw encrypted value to this method, not the encoded base value of 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; } } 
0
source

All Articles