I'm having a problem trying to send an object through a CipherOutputStream in order to encrypt it with AES and receive it with a CipherInputStream in order to decrypt it.
The problem is that the server is unable to receive the object:
Client > INFO > created ObjectOutputStream
Client > INFO > sent a Person object through ObjectOutputStream
Server > INFO > created cipherInputStream
Server > INFO > created ObjectInputStream
(It blocks before this:)
Person p = (Person) objectInputStream.readObject();
System.out.println("Server > INFO > received a Person object: " + p.toString());
While if instead of the CipherOutputStream in the client, and the CipherInputStream in the server I had directly used the ObjectOutputStream and the ObjectInputStream then it would have received it correctly (I know because I tried it).
Do you have any alternative methods to suggest to send AES encrypted objects or an idea of how to solve the problem?
Thanks in advance.
Main
public class Main {
public static void main (String[] args) {
Server s = new Server();
Client c = new Client(s.serverSocket.getInetAddress().getHostName());
}
}
Client
import javax.crypto.*;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.security.*;
import java.util.Arrays;
public class Client {
private final int port = 3535;
private PublicKey publicKey;
private PrivateKey privateKey;
private SecretKey aesKey;
public Client(String hostname) {
try {
Socket socket = new Socket(hostname, port);
System.out.println("Client > INFO > Connected to server: " + socket.toString());
KeyPair keyPair = Cryptography.rsaKey();
if(keyPair == null) {
System.out.println("Client > ERROR > keyPair is null!");
return;
}
publicKey = keyPair.getPublic();
privateKey = keyPair.getPrivate();
//System.out.println("Client > publicKey > " + publicKey.toString());
//System.out.println("Client > privateKey created");
aesKey = Cryptography.aesKey();
if(aesKey == null) {
System.out.println("Client > ERROR > aesKey is null!");
return;
}
System.out.println("Client > INFO > aesKey > " + Arrays.toString(aesKey.getEncoded()));;
new Thread(new Runnable() {
@Override
public void run() {
try {
// Receiving the RSA - PublicKey
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
System.out.println("Client > INFO > created objectInputStream: " + objectInputStream.toString());
PublicKey publicKey1 = (PublicKey) objectInputStream.readObject();
System.out.println("Client > INFO > publicKey received from server: " + publicKey1.toString());
// Creating a Cipher object
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
// Initializing the Cipher object for encrypting
rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey1);
byte[] encryptedKey = rsaCipher.doFinal(aesKey.getEncoded());
//System.out.println("Client > INFO > sending encrypted aesKey: " + Arrays.toString(encryptedKey));
// Sending the AES - SecretKey encrypted with the RSA - PublicKey
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
dataOutputStream.writeInt(encryptedKey.length);
dataOutputStream.flush();
dataOutputStream.write(encryptedKey);
dataOutputStream.flush();
// Creating an ObjectOutputStream over a CipherOutputStream
Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
Person p = new Person();
p.setName("John");
p.setSurname("Smith");
CipherOutputStream cipherOutputStream = new CipherOutputStream(socket.getOutputStream(), aesCipher);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(cipherOutputStream);
System.out.println("Client > INFO > created ObjectOutputStream");
objectOutputStream.writeObject(p);
objectOutputStream.flush();
System.out.println("Client > INFO > sent a Person object through ObjectOutputStream");
} catch (IOException | ClassNotFoundException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
}
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Server
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.*;
import java.util.Arrays;
public class Server {
private final int port = 3535;
public ServerSocket serverSocket;
private PublicKey publicKey;
private PrivateKey privateKey;
public Server() {
try {
serverSocket = new ServerSocket(port);
KeyPair keyPair = Cryptography.rsaKey();
if(keyPair == null) {
System.out.println("Server > ERROR > keyPair is null!");
return;
}
publicKey = keyPair.getPublic();
privateKey = keyPair.getPrivate();
System.out.println("Server > publicKey > " + publicKey.toString());
System.out.println("Server > privateKey created");
ServerSocket finalServerSocket = serverSocket;
new Thread(new Runnable() {
@Override
public void run() {
Socket socket;
try {
socket = finalServerSocket.accept();
System.out.println("Server > INFO > A client connected: " + socket.toString());
// Sending the RSA - PublicKey
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
System.out.println("Server > INFO > created objectOutputStream: " + objectOutputStream.toString());
objectOutputStream.writeObject(publicKey);
objectOutputStream.flush();
// Receiving the encrypted AES - SecretKey
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
int length = dataInputStream.readInt();
System.out.println("Server > INFO > received encrypted aesKey's length: " + length);
byte[] encryptedKey;
if(length > 0) {
encryptedKey = new byte[length];
dataInputStream.readFully(encryptedKey, 0, encryptedKey.length);
} else {
System.out.println("Server > ERROR > length received is <= 0");
return;
}
System.out.println("Server > INFO > received encrypted aesKey: " + Arrays.toString(encryptedKey));
// Decrypting the encrypted AES - SecretKey
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedKey = rsaCipher.doFinal(encryptedKey);
// Converting the decrypted AES - SecretKey to a SecretKey
SecretKey aesKey = new SecretKeySpec(decryptedKey, 0, decryptedKey.length, "AES");
System.out.println("Server > INFO > converted decrypted aesKey: " + Arrays.toString(aesKey.getEncoded()));
Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
aesCipher.init(Cipher.DECRYPT_MODE, aesKey);
// Creating an ObjectInputStream over a CipherInputStream
CipherInputStream cipherInputStream = new CipherInputStream(socket.getInputStream(), aesCipher);
System.out.println("Server > INFO > created cipherInputStream");
ObjectInputStream objectInputStream = new ObjectInputStream(cipherInputStream);
System.out.println("Server > INFO > created ObjectInputStream");
Person p = (Person) objectInputStream.readObject();
System.out.println("Server > INFO > received a Person object: " + p.toString());
} catch (IOException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | ClassNotFoundException ex) {
ex.printStackTrace();
}
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Person
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private String surname;
public Person() {}
public void setName(String name) {
this.name = name;
}
public void setSurname(String surname) {
this.surname = surname;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", surname='" + surname + '\'' +
'}';
}
}
Cryptography
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
public class Cryptography {
public static KeyPair rsaKey() {
try {
// Creating a Signature object
Signature sign = Signature.getInstance("SHA256withRSA");
// Creating KeyPair generator object
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
// Initializing the key pair generator
keyPairGen.initialize(2048);
// Generating the pair of keys
return keyPairGen.generateKeyPair();
} catch (NoSuchAlgorithmException ignored) {}
return null;
}
public static SecretKey aesKey() {
try {
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(128); // The AES key size in number of bits
return generator.generateKey();
} catch (NoSuchAlgorithmException ignored) {}
return null;
}
}
Firstly: thanks for the fine example that I needed for my project already :-)
The problem in your code is on the Client-side because you missed closing the ObjectOutputStream. Adding one line of code after your flushing:
objectOutputStream.writeObject(p);
objectOutputStream.flush();
// new line
objectOutputStream.close();
let the example run like expected:
...
Server > INFO > created cipherInputStream
Server > INFO > created ObjectInputStream
Server > INFO > received a Person object: Person{name='John', surname='Smith'}
Edit 1:
As you wrote in your question the complete workflow runs WITHOUT using CipherOutput/InputStream so the reason for the behavior is in the CipherOutputStream.
Please see the Javadocs (https://docs.oracle.com/javase/7/docs/api/javax/crypto/CipherOutputStream.html#flush()) and you find this:
Flushes this output stream by forcing any buffered output bytes that have already been processed by the encapsulated cipher object to be written out. Any bytes buffered by the encapsulated cipher and waiting to be processed by it will not be written out.
For example, if the encapsulated cipher is a block cipher, and the total number of bytes written using one of the write methods is less than the cipher's block size, no bytes will be written out.
As solution (depends on the amount of data) I would recommend to encrypt the data (after serialization) in memory on Client side and pass the encrypted data as byte[] to dataOutputStream.write and vice versa on Server side. That way you don't need to close the objectOutputStream and the socket is still open for next transmission.