Search this blog ...

Friday, July 10, 2015

Java SSL HttpUrlConnection Performance Slow using TLS 1.0 with CBC

The fix Oracle implemented in the JVM to combat the BEAST attack can have a significant performance impact when using TLS 1.0 with CBC. This is particularly noticeable when performing large streaming uploads with HttpURLConnection using the setFixedLength streaming mode (rather than its default mode where it buffers the request payload in full).

When performing writes to HttpURLConnection's OutputStream in setFixedLength streaming mode using a BufferedOutputStream based on the default 8k buffer [OutputStream out = new BufferedOutputStream(uc.getOutputStream())], you can see a pattern like that below when running with the system property -Djavax.net.debug=ssl,handshake set.

As disabling the CBC protection is not viable in production, I looked at what could be done to minimize the occurrence of the 32 byte packets when using TLS 1.0 with CBC. In turns out by increasing the buffer size of the BufferedOutputStream wrapping HttpURLConnection’s OutputStream from the default 8kb to something much larger e.g. to 256kb, the number of 32 byte packets reduced significantly resulting in a significant performance increase.

The larger buffer however as expected had minimal (or no) impact with Java 1.8 based on the TLS 1.2 connection. Java 1.7 can support TLS 1.2, though will by default negotiate TLS 1.0 unless explicitly instructed otherwise:

Footnote 1 - Although SunJSSE in the Java SE 7 release supports TLS 1.1 and TLS 1.2, neither version is enabled by default for client connections. Some servers do not implement forward compatibility correctly and refuse to talk to TLS 1.1 or TLS 1.2 clients.

Oracle’s acknowledgement of the BEAST exploit when using TLS 1.0 with CBC (Cipher Block Chaining) is part of CVE-2011-3389:

To combat the exploit, the fix Oracle did was to split each write() to the underlying OutputStream in to at least two separate TLS records with every record having a different initialization vector. TLS itself caps the maximum record size at 16384 (this is the size of the raw unencrypted bytes). http://blog.fourthbit.com/2014/12/23/traffic-analysis-of-an-ssl-slash-tls-sessionSo a write of 16k of client data to the underlying OutputStream at a time with the fix above would result in one TLS record containing the first byte encrypted, and the second TLS record containing the remaining 16383 bytes encrypted. Whereas a write of 32k of client data to the underlying OutputStream at a time would result in three TLS records, one containing the first byte encrypted, the second containing the next 16384 bytes, and the third containing the remaining 16383 bytes encrypted. So when using TLS 1.0 with CBC, the bigger the buffer associated with the write, the fewer one byte encrypted TLS records you are going to see.

Each SSL record obviously has a reasonable amount of processing time, both client to encrypt/hash, network from a TCP perspective, and server to validate/decrypt the SSL payload.So ideally going forward Java 1.8 using TLS 1.2 is what you want to strive for. If stuck with TLS 1.0, then the large buffer will definitely help with performance.