Search code examples
androidencryptionrsa

Decryption error: Encrypte a message on Android and decrypte on Intelij IDEA


My server is on Intelij Idea Windows, and Client is on Android. I send the rsa public key from server to client, which encripte message of public key and send to Server. Server getting error when decrypte a message.

I tried to use Server not on Intelij, but Android and everything worked. I also tried to swap server and client, Server - Android, Client - Intelij, and also didnt work, but the result was different: Server getted and successfully decrypte the message, but with the message was many other symbols.

This is the code, where Server - Intelij and Client - Android.

Server:

ServerSocket serverSocket = new ServerSocket(5556);
Socket socket = serverSocket.accept();
System.out.println("Client connected");

DataOutputStream dOut = new DataOutputStream(socket.getOutputStream());
DataInputStream dIn  = new DataInputStream(socket.getInputStream());

//Create keys
Map<String, Object> RsaKey = rsa.initKey();
String RsaPrivateKey = rsa.getPrivateKey(RsaKey);
String RsaPublicKey = rsa.getPublicKey(RsaKey);

//send key
dOut.writeInt(RsaPublicKey.getBytes().length);
dOut.write(RsaPublicKey.getBytes());
Thread.sleep(1000);
System.out.println(RsaPublicKey);

//read encrypted message
int length_pk = dIn.readInt();
byte[] message = new byte[length_pk];
dIn.readFully(message, 0, message.length);

String st1 = new String(message);
System.out.println(st1 + " " + length_pk + " " + st1.length());

String str = new String(rsa.decryptByPrivateKey(message, RsaPrivateKey));
System.out.println(str);

Client:

Socket client = new Socket("192.168.1.103", 5556);
DataOutputStream dOut = new DataOutputStream(client.getOutputStream());
DataInputStream dIn = new DataInputStream(client.getInputStream());

//read key from server
int length_pk1 = dIn.readInt();
byte[] publickeyserver1 = new byte[length_pk1];
dIn.readFully(publickeyserver1, 0, publickeyserver1.length);
String RsaPublicKeyServer = new String(publickeyserver1);

//test key rsa
String str = "Who is there?";
byte[] encbyte = rsa.encryptByPublicKey(str.getBytes(), RsaPublicKeyServer);

dOut.writeInt(encbyte.length);
dOut.write(encbyte);

Rsa library:

public static final String KEY_ALGORITHM = "RSA";
public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
private static final String PUBLIC_KEY = "RSAPublicKey"; 
private static final String PRIVATE_KEY = "RSAPrivateKey";

public static byte[] decryptBASE64(String key) throws Exception {
   return Base64.getDecoder().decode(key);
}
public static byte[] decryptByPrivateKey(byte[] data, String key)throws Exception {
   byte[] keyBytes = decryptBASE64(key);

   PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
   KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
   Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);

   Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
   cipher.init(Cipher.DECRYPT_MODE, privateKey);
   return cipher.doFinal(data);
}
public static byte[] encryptByPublicKey(byte[] data, String key) throws Exception {
   byte[] keyBytes = decryptBASE64(key);
   X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
   KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
   Key publicKey = keyFactory.generatePublic(x509KeySpec);

   Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
   cipher.init(Cipher.ENCRYPT_MODE, publicKey);

   return cipher.doFinal(data);
}
public static String getPrivateKey(Map<String, Object> keyMap)throws Exception {
   Key key = (Key) keyMap.get(PRIVATE_KEY);
   return encryptBASE64(key.getEncoded());
}
public static String getPublicKey(Map<String, Object> keyMap)throws Exception {
   Key key = (Key) keyMap.get(PUBLIC_KEY);
   return encryptBASE64(key.getEncoded());
}
public static Map<String, Object> initKey() throws Exception {
   KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
   keyPairGen.initialize(1024);

   KeyPair keyPair = keyPairGen.generateKeyPair();
   RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
   RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();

   Map<String, Object> keyMap = new HashMap<String, Object>(2);

   keyMap.put(PUBLIC_KEY, publicKey);
   keyMap.put(PRIVATE_KEY, privateKey);
   return keyMap;
}

Stack Trace

Exception in thread "main" java.lang.RuntimeException: javax.crypto.BadPaddingException: Decryption error at Main.main(Main.java:110) Caused by: javax.crypto.BadPaddingException: Decryption error at java.base/sun.security.rsa.RSAPadding.unpadV15(RSAPadding.java:369) at java.base/sun.security.rsa.RSAPadding.unpad(RSAPadding.java:282) at java.base/com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:366) at java.base/com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:400) at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2206) at main.java.rsa.decryptByPrivateKey(rsa.java:134) at Main.main(Main.java:103)

I have been using the Rsa library for a long time, I don't think it's a problem. I think the problem is between IDE.


Solution

  • As I suggested it might be in the comments, the problem is your failure to specify the full "algorithm/mode/padding" transformation specification to Cipher.getInstance(). Instead, you are using keyFactory.getAlgorithm() as a specification. The KeyFactory algorithm just happens to return "RSA", and when you use Cipher.getInstance("RSA") you end with provider-specific defaults. On Android the padding default is "NoPadding" and on Java the padding default is "PKCS1Padding". It would be better to use RSA/ECB/PKCS1Padding on both sides. Even better than that is the more modern but more complicated ""RSA/ECB/OAEPwithSHA-256andMGF1Padding". The OAEPwithSHA-256andMGF1Padding scheme itself has parameters, and again although you can use defaults they are provider-specific and thus should be avoided. You returned an initialized, interoperable, modern RSA-based Cipher instance with a few lines of code:

        private static final String RSA_CIPHER_SPEC = "RSA/ECB/OAEPwithSHA-256andMGF1Padding";
    
    
        private static Cipher getRsaCipher(int direction, Key key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
            Cipher cipher = Cipher.getInstance(RSA_CIPHER_SPEC);
            OAEPParameterSpec oaepSpec = new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
            cipher.init(direction, key, oaepSpec);
    
            return cipher;
        }
    

    and this can be used in your encryption/decryption methods like so:

        public static byte[] decryptByPrivateKey(byte[] data, String key) throws Exception {
            byte[] keyBytes = decryptBASE64(key);
    
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
    
            Cipher cipher = getRsaCipher(Cipher.DECRYPT_MODE, privateKey);
            return cipher.doFinal(data);
        }
    
        public static byte[] encryptByPublicKey(byte[] data, String key) throws Exception {
            byte[] keyBytes = decryptBASE64(key);
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Key publicKey = keyFactory.generatePublic(x509KeySpec);
    
            Cipher cipher = getRsaCipher(Cipher.ENCRYPT_MODE, publicKey);
            return cipher.doFinal(data);
        }
    

    on both the Android and Java sides.

    There are other improvements that can be made to your code, for example eliminate base64 encoding/decoding. It seems to add complication with no benefits.