TLS Configuration: Cipher Suites and Protocols

David B. Templeton
8 min readAug 3, 2017

--

Thoughtfully setting the list of protocols and cipher suites that a HTTPS server uses is rare; most configurations out there are copy-and-pasted from others’ guides or configuration generators. Discounting managed services where formal security teams choose these settings for you (eg. AWS Cloudfront), most TLS configurations leave a lot to be desired.

If, in 2017, your cipher suite and protocol settings look like this:

SSLCipherSuite 'ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP'
SSLProtocol all -SSLv2

…then your configuration earns the following scores:

ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP

We’re going to push our score on security way up, while maintaining desired compatibility, and wiping out incompetence!

The only two clients used in the world today that may require insecure settings are IE6 on XP, and IE8 on XP. For the former, you’ll need to leave SSLv3 enabled, which has been broken for years. For the latter, you can use TLSv1, but the only somewhat-secure cipher suite it supports uses the Triple DES cipher, which was put out to pasture by NIST. It is recommended to finally allow your application to break on these old clients, especially if your main audience isn’t in China (where most of these clients still reside), as the broken cryptography you’d have to enable for them is too big of a burden to bear.

If you can safely shut off access to these clients, you’ll be able to configure a fully-secure HTTPS server against practical attacks. I use the word “practical” because there are some theoretical, academic attacks against TLSv1 that are safe to ignore for now for non-National-Security applications.

Protocols

Choosing what protocols to enable is easy: you should enable all versions of TLS (TLSv1, TLSv1.1, TLSv1.2) if you want wide compatibility among modern and slightly old devices (like Android 4.0-4.3), and you can deploy just TLSv1.2 if you need only the newest devices to connect and you can’t suffer the thought of being vulnerable to impractical attacks on TLSv1.

While you can whitelist protocol versions, I believe it is better to blacklist the ones you don’t want, as you’ll automatically get newer versions as you upgrade your server over the years (TLSv1.3 is on the horizon). Example protocol lists that enable just TLSv1.2 (at the moment, top) and all versions of TLS (bottom):

all -SSLv3 -TLSv1 -TLSv1.1
all -SSLv3

Cipher Suites

We’ll need to focus on three elements of a cipher suite: the key exchange, the symmetric cipher, and the Hash-based Message Authentication Code (HMAC).

Key Exchange

Assuming you are using a currently-supported version of OpenSSL, you’ll be able to enable cipher suites using key exchanges that have so-called “perfect forward secrecy” (PFS). These use Diffie-Hellman exchanges which allow two parties to agree on a key, without allowing an eavesdropper to learn any information about said key. This means that even if an adversary stores the key exchange and subsequent ciphertexts, and later on cracks the certificate’s private key, they still won’t be able to reconstruct the key and decrypt the ciphertexts. These “ephemeral” key exchanges are abbreviated DHE and ECDHE (the former based on factoring math, the latter based on elliptic curve algebraic geometry).

ECDHE is to be preferred over DHE, because elliptic curve math is much quicker than factoring math, for a given amount of security. Computing a key exchange with a 256-bit EC field is about an order of magnitude faster than using 3072-bit DH group (which are roughly equivalent in strength), and significantly faster than a 2048-bit DH group as well. The only reason to allow key exchanges that do not feature PFS is if you need the IE6/8 XP compatibility.

(I will discuss tuning the strength of EDH (through group generation) and EECDH (through curve selection) in a future post.)

Ciphers

While there are some newer symmetric ciphers, such as CHACHA20, that provide some needed diversity in the ecosystem and have some niche uses, as of 2017, your main cipher is going to be AES. Of course, it’s not that simple to just use AES; you also need to consider bit strength and modes.

Given almost all leaf and root certificates still use 2048-bit RSA key pairs, which can be broken with about 2¹⁰³ operations, there isn’t much to be gained making the rest of the chain extremely strong when it can be broken by its weakest link. In this respect, 128-bit AES is still totally, completely, indisputably unbroken and has many years of life left. Given the performance penalty to upgrade to AES256, I’d advise preferring AES128 suites and only allow AES256 at the end of your list for compatibility.

Cipher modes get into the actual mathematics of how the cipher is applied. While the details of this process are beyond the scope of this article, I can say that the “legacy” mode CBC is widely supported but difficult to properly implement. For this reason, the newer GCM mode is preferred; GCM is easier to implement properly, and its construction makes the HMAC an integral part of the encipherment (making AES-GCM an authenticated encryption algorithm).

HMACs

HMACs have names similar to hashing functions (e.g. MD5, SHA, SHA256) because they use them internally. Hashes have only a message as input, while HMACs have both a message and a key as inputs (which is why they are also called keyed hashes). This distinction is important, because these different types of functions have different vulnerabilities and attacks. For example, while MD5 is utterly broken as a hashing function, collision resistance is less important with HMACs, and thus HMAC-MD5 is less broken (although you still shouldn’t use it).

The HMACs you’ll use in cipher suites are SHA (i.e. SHA-1) and SHA256/SHA384 (of the SHA-2 family). The latter is preferred, although the former is still acceptable. Any vulnerabilities you’ve heard about for SHA-1 aren’t applicable in this use case; HMAC-SHA-1 for a TLS connection is secure. (The vulnerabilities in SHA-1 are applicable, though, for certificate signing.)

Forming a Cipher List

Putting this all together: ECDHE key exchanges are great, DHE ones are good, non-PFS exchanges may or may not be acceptable, AES128 is preferable to AES256 for performance reasons, the GCM mode is preferable to CBC, and SHA-2 HMACs are preferred to SHA-1. So how does one turn these sentiments into a cipher list?

Using OpenSSL on the command line is your friend. On the web server itself, you should test what cipher suites are supported by your version of OpenSSL by trying the following command:

$ openssl ciphers -v 'ALL'

You will see a lot of crap in this list, depending on your OpenSSL version:

  • ECDSA and DSS are only for use with special kinds of certificates; you probably have an RSA one. If you have an ECDSA cert, great! You won’t have a DSS one. It’s ok to see these in the cipher list; they won’t be used if you don’t have that type of cert.
  • “Static” ECDH and DH are for non-ephemeral key exchanges that have been out of use for a while
  • SRP and PSK are beyond the scope of a normal web server
  • CAMEILLA, SEED, RC4, 3DES, and DES are deprecated ciphers
  • AECDH and ADH are anonymous ciphers, which means no authentication
  • MD5 is a poor HMAC
  • EXP means “export grade” and are terrible

In place of ALL, you can list “cipher strings”, which are the primitives mentioned throughout this article: ECDHE, DHE, SHA, and many more. Check out the complete list of cipher strings for OpenSSL 1.0.2 and 1.1.0. You may combine strings logically using “+”; for example, “ECDHE+AES” would include all cipher suites with both an ECDHE key exchange and an AES cipher, with any HMAC. You can separate multiple strings in your cipher list using “:”. If you’re getting errors, it means the cipher suite is either not supported or just named differently for your version of OpenSSL; for example, ECDHE needs to be called EECDH for version 1.0.1 (which reached end-of-life in 2016). An example of a cipher list is earlier in this post:

ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP

Once all of the cipher suites you want to support are listed, you must then pay attention to the ordering. You want the resultant cipher suites to be listed in descending order of preference. You may prefix cipher strings with a “+” to push them to the end, or with “!” to remove them. You can also put “@STRENGTH” at any point to sort the cipher list, at that point, by OpenSSL’s determination of strength.

$ openssl ciphers -v 'ECDHE+AES:@STRENGTH:+AES256'
ECDHE+AES:@STRENGTH:+AES256

This specifies all of the ECDHE key exchange suites with an AES cipher, sorts them by strength (placing stronger modes and HMACs in front), and then shifts all of the AES256 ciphers to the end (keeping them in the same order otherwise). This is a modern cipher suite that still has high compatibility (assuming you include the TLSv1 protocol). If you need to add support for Android 2.3 (which you shouldn’t, since it has 0.7% of worldwide share and Android 4.0 came out in 2011), and you don’t need Server Name Indication (SNI, which it doesn’t support), add the DHE suites:

$ openssl ciphers -v 'ECDHE+AES:DHE+AES:@STRENGTH:+AES256'
ECDHE+AES:DHE+AES:@STRENGTH:+AES256

Finally, add the only cipher suite that is secure for IE6 and IE8 (which also don’t support SNI), if you really need that, all the way at the end:

$ openssl ciphers -v 'ECDHE+AES:DHE+AES:@STRENGTH:+AES256:kRSA+3DES'
ECDHE+AES:DHE+AES:@STRENGTH:+AES256:kRSA+3DES

Going all the way in the other direction, if you want super-strict, unbreakable settings, compatibility be damned:

$ openssl ciphers -v 'ECDHE+AESGCM:@STRENGTH:!AES128'
ECDHE+AESGCM:@STRENGTH:!AES128

This only enables one cipher suite each for RSA and ECDSA certificates. Large swaths of the internet will be unable to connect, but perhaps your site is for a limited audience of newer clients. All newer software should support this.

Forming a Configuration

We’re finally ready to turn our cipher list and protocol list into actual Apache and Nginx configurations. Examples:

# Apache
SSLCipherSuite 'ECDHE+AES:@STRENGTH:+AES256'
SSLCipherSuite 'ECDHE+AES:DHE+AES:@STRENGTH:+AES256:kRSA+3DES'
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLProtocol all -SSLv3
SSLHonorCipherOrder on# Nginx
ssl_ciphers 'ECDHE+AES:@STRENGTH:+AES256';
ssl_ciphers 'ECDHE+AES:DHE+AES:@STRENGTH:+AES256:kRSA+3DES';
ssl_protocols all -SSLv3 -TLSv1 -TLSv1.1;
ssl_protocols all -SSLv3;
ssl_prefer_server_ciphers on;

The SSLHonorCipherOrder and ssl_prefer_server_ciphers directives are to make our ordering selections actually stick; if these aren’t turned on, the client will pick, and we don’t trust clients to pick correctly.

You can test and then install your configs with (as root):

Apache
# apachectl configtest
# apachectl graceful
Nginx
# nginx -t
# nginx -s reload

If you’re getting errors, make sure you test the cipher lists used in your web server config with the openssl ciphers command on that same installation. After your web server successfully restarts, test your configuration using the Qualys SSL Server Test, one of the most useful free services for sysadmins on the internet. In the “Handshake Simulation” section, it will verify your knowledge about what clients can and cannot connect.

We’ve only begun looking into TLS configuration, although you’re free to stop here if the topic bores you and you’re getting an “A” on the Qualys test. Future topics will include:

  • DH group generation and ECDH curve selection
  • HTTP Public Key Pinning and OCSP Stapling with Let’s Encrypt

--

--