简体   繁体   中英

Problem while trying to send an object encrypted with AES

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM