javasslniojssesslengine

SSLEngine unwrap() javax.crypto.BadPaddingException: bad record MAC


This has been driving me crazy for a few days now. I created a client using java nio with ssl encryption using an SSLEngine. Handshake works fine, and I write a GET request to a website and it works fine (I get the header with 200 code). The problem is that when the website sends the packets back, on the second packet I get a BadPaddingException. Here is my read method:

    public void read(SelectionKey key,ByteBuffer readBuffer) throws IOException, BadPaddingException {

    SocketChannel socketChannel = (SocketChannel) key.channel();

    ByteBuffer clientSSLData = ByteBuffer.allocate(getPacketBufferSize());
    System.out.println("Reading data. PacketBufferSize: "+(getPacketBufferSize()));
    int length = socketChannel.read(clientSSLData);

    System.out.println("read "+length+" bytes");
    if (length == -1){
        System.out.println("Length is -1 which means nothing was read from the channel  ");
        socketChannel.close();
        return;
    }
    clientSSLData.flip();
    readBuffer.clear();
    SSLEngineResult res = sslEngine.unwrap(clientSSLData, readBuffer);
    System.out.println(res.toString());
}

My get request is something simple like this: "GET \r\nHost: www.google.com\r\n\r\n"

Basically if its a small website like https://www.example.com, I have no problem receiving because it sends it in one read. But if I do something like https://www.google.com, I get the BadPaddingException. Any ideas? Thank you!

EDIT: Exception is...

javax.net.ssl.SSLException: bad record MAC at sun.security.ssl.Alerts.getSSLException(Unknown Source) at sun.security.ssl.SSLEngineImpl.fatal(Unknown Source) at sun.security.ssl.SSLEngineImpl.readRecord(Unknown Source) at sun.security.ssl.SSLEngineImpl.readNetRecord(Unknown Source) at sun.security.ssl.SSLEngineImpl.unwrap(Unknown Source) at javax.net.ssl.SSLEngine.unwrap(Unknown Source) at ssl.engine.impl.SecureIO.read(SecureIO.java:244) at ssl.engine.impl.ChannelHandler.read(ChannelHandler.java:144) at ssl.engine.impl.ChannelHandler.run(ChannelHandler.java:69) at java.lang.Thread.run(Unknown Source) Caused by: javax.crypto.BadPaddingException: bad record MAC at sun.security.ssl.EngineInputRecord.decrypt(Unknown Source) ... 8 moreClosing down


Solution

  • You're doing this wrong. When you need to get data you should:

    1. Get from your app receiver buffer.
    2. If that's empty, try unwrap().
    3. If that gives you a buffer underflow, read the channel.

    Similarly when you need to put data, you should:

    1. Put to your app send buffer.
    2. If that fills, wrap().
    3. Write if that gives you a buffer overflow.

    Or do all that when you need to flush.

    Your primary interface should be with the engine, and only with the channel as a result of what the engine tells you about buffer underflows and overflows.

    Similarly you must let the engine dictate the handshake (NEED_WRAP/NEED_UNWRAP) rather than try to dictate to it.

    The SSLEngine is a very difficult thing to get right. Many have tried: few have succeeded. For one working success, that is the basis for a commercial product, see the SSLEngineManager class in the source code for my book Fundamental Networking in Java, Springer 2006, here.