Search code examples
javasocketsencryptioninputstream

Java CipherInputStream using AES/GCM encryption wont read until close


I need an encrypted in/output stream for my sockets. I have tried just about everything out there to fix this with no luck. I know the socket will receive the data once the socket or output stream are closed. However this is not useful what so ever. I would love any input on this.

"This is not a duplicate, there are other questions about this subject, however none provide an answer to this."

For anyone that is struggling with this same issue I made a custom cipher stream and added DataInputStream / DataOutputStream to it. It will work fine with GCM with some minor tweaks.

https://gist.github.com/DrBrad/0148f54cb1e5f5b04414c7756b97996a

public static void testC()throws Exception {

    String password = "PASSWORD";

    Socket socket = new Socket("127.0.0.1", 7000);

    CipherInputStream in = new CipherInputStream(socket.getInputStream(), getEN(password, true));
    CipherOutputStream out = new CipherOutputStream(socket.getOutputStream(), getEN(password, false));

    out.write("HELLO WORLD".getBytes());
    out.flush();
    out.close();
}

public static void testS(){
    new Thread(new Runnable() {
        private Socket socket;
        @Override
        public void run() {
            try {
                ServerSocket serverSocket = new ServerSocket(7000);
                String password = "PASSWORD";

                System.out.println("STARTED");

                while((socket = serverSocket.accept()) != null){
                    CipherInputStream in = new CipherInputStream(socket.getInputStream(), getEN(password, true));
                    CipherOutputStream out = new CipherOutputStream(socket.getOutputStream(), getEN(password, false));

                    byte[] buffer = new byte[4096];

                    int length = in.read(buffer);

                    System.out.println(new String(buffer, 0, length));

                    socket.close();
                }
            }catch (Exception e){

            }
        }
    }).start();
}


public static Cipher getEN(String password, boolean t)throws Exception {
    byte[] salt = new byte[8], iv = new byte[16];

    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
    KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
    SecretKey secretKey = factory.generateSecret(keySpec);
    SecretKey secret = new SecretKeySpec(secretKey.getEncoded(), "AES");

    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    GCMParameterSpec spec = new GCMParameterSpec(128, iv);
    if(t){
        cipher.init(Cipher.DECRYPT_MODE, secret, spec);
    }else{
        cipher.init(Cipher.ENCRYPT_MODE, secret, spec);
    }

    return cipher;
}

Solution

  • CipherOutputStream is not suitable for this kind of use, because of how its flush() method behaves. It only flushes data up to a complete cipher block boundary. If the length of the message you're sending is not an exact multiple of the cipher block length, some of the data is going to stay in the stream's buffer until you write more data or close the stream. Closing the stream pads the final input so it aligns to a block boundary.

    This limitation is reasonable, since block ciphers operate on one block of data at a time.