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.
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.