Cipher Suites for Nginx and HAProxy

As a website administrator, I have to secure transactions between client browsers and a web server. For me, that means HAProxy and Nginx. The most obvious security is the SSL/TLS certificate, but there's a lot more to it than that. Recently, I've been taking a close look at the Cipher Suites used for server-browser communication after the initial handshake is completed. TLS (Transport Layer Security) comes in four different versions (1.0, 1.1, 1.2, and 1.3) so far, each supporting different ciphers. As you might expect, the most recent one has the best security and supports the strongest ciphers. But the reality is that most websites have to support older browsers that don't understand newer encryption standards: the trade-off is loss of customers. So, despite the relative weakness of TLS 1.0, most websites still implement it (alongside 1.1 and 1.2 - 1.3 is uncommon so far). If you ever find a website using the very elderly and incredibly insecure SSL 3.0 - the previous standard - do not, DO NOT continue to use that site.

HAProxy implements this part of security with the following lines:

ssl-default-bind-ciphers <cipher-suites>
ssl-default-server-ciphers <cipher-suites>

And Nginx implements the same thing with:

ssl_ciphers <cipher-suites>;

I'm skipping everything not related to the Cipher Suites, and a lot of detail even there, but I'll get into it a bit more as we go.

The simplest and fastest solution is to trust Mozilla and use their Configuration Generator (this will show you the security details I skipped over too). But there are a LOT of encryption schemes, and they rise and fall alarmingly quickly. Note that the configurator has you choose your OpenSSL version: that's where most (all?) of the ciphers come from, and it varies by version. Nginx's documentation points out that you can see a list of available ciphers with the openssl ciphers command, but doesn't mention (the HAProxy documentation happily does) that to understand it better you need to do man ciphers (note OpenSSL's confusing practice of putting the man pages in the name of the sub-command).

So Mozilla suggests the following cipher string as a default: ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';

While this is the "easy" path, it's important to make the correct settings - not just the versions of your web server and OpenSSL on your machines, but also understanding the "Modern, Intermediate, Old" choice: take a look at their linked documentation here: https://wiki.mozilla.org/Security/Server_Side_TLS . Most sites should use "Intermediate" which is the default.

Note that sequence matters in the ssl_ciphers line: most-preferred ciphers should come first.

How exactly did they get to that list of cipher suites? On a recent Fedora system, we see similar output from openssl ciphers: ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-CCM8:ECDHE-ECDSA-AES256-CCM:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-CCM8:ECDHE-ECDSA-AES128-CCM:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-CAMELLIA256-SHA384:ECDHE-RSA-CAMELLIA256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-CAMELLIA128-SHA256:ECDHE-RSA-CAMELLIA128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES256-CCM8:AES256-CCM:AES128-GCM-SHA256:AES128-CCM8:AES128-CCM:AES256-SHA256:CAMELLIA256-SHA256:AES128-SHA256:CAMELLIA128-SHA256:AES256-SHA:CAMELLIA256-SHA:AES128-SHA:CAMELLIA128-SHA:DES-CBC3-SHA:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-CCM8:DHE-RSA-AES256-CCM:DHE-DSS-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-CCM8:DHE-RSA-AES128-CCM:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-CAMELLIA256-SHA256:DHE-DSS-CAMELLIA256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-CAMELLIA128-SHA256:DHE-DSS-CAMELLIA128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DHE-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:DHE-RSA-DES-CBC3-SHA:DHE-DSS-DES-CBC3-SHA:PSK-AES256-GCM-SHA384:PSK-CHACHA20-POLY1305:PSK-AES256-CCM8:PSK-AES256-CCM:PSK-AES128-GCM-SHA256:PSK-AES128-CCM8:PSK-AES128-CCM:PSK-AES256-CBC-SHA384:PSK-AES256-CBC-SHA:PSK-CAMELLIA256-SHA384:PSK-AES128-CBC-SHA256:PSK-AES128-CBC-SHA:PSK-CAMELLIA128-SHA256:PSK-3DES-EDE-CBC-SHA:DHE-PSK-AES256-GCM-SHA384:DHE-PSK-CHACHA20-POLY1305:DHE-PSK-AES256-CCM8:DHE-PSK-AES256-CCM:DHE-PSK-AES128-GCM-SHA256:DHE-PSK-AES128-CCM8:DHE-PSK-AES128-CCM:DHE-PSK-AES256-CBC-SHA384:DHE-PSK-AES256-CBC-SHA:DHE-PSK-CAMELLIA256-SHA384:DHE-PSK-AES128-CBC-SHA256:DHE-PSK-AES128-CBC-SHA:DHE-PSK-CAMELLIA128-SHA256:DHE-PSK-3DES-EDE-CBC-SHA:ECDHE-PSK-CHACHA20-POLY1305:ECDHE-PSK-AES256-CBC-SHA384:ECDHE-PSK-AES256-CBC-SHA:ECDHE-PSK-CAMELLIA256-SHA384:ECDHE-PSK-AES128-CBC-SHA256:ECDHE-PSK-AES128-CBC-SHA:ECDHE-PSK-CAMELLIA128-SHA256:ECDHE-PSK-3DES-EDE-CBC-SHA

But this list is a lot longer than the one suggested by Mozilla: how did they get from one to the other? In part, they've removed algorithms like PSK, DSS, and 3DES - in fact they explicitly called out DSS as not-to-be-used with the :!DSS at the end of the configuration line.

The Algorithms

Something you keep coming across as you read about encryption algorithms: those that are considered secure always say "if implemented properly."

  • 3DES = Triple Data Encryption Algorithm: "This can be considered broken, as the whole 3des keyspace can be searched thoroughly by affordable consumer hardware today (2017)." (Wikipedia) [ https://en.wikipedia.org/wiki/Triple_DES ]
  • AES128/AES256 = Advanced Encryption Standard followed by a key size, a NIST standard: "It supersedes the Data Encryption Standard (DES)." "At present, there is no known practical attack that would allow someone without knowledge of the key to read data encrypted by AES when correctly implemented." [ https://en.wikipedia.org/wiki/Advanced_Encryption_Standard ]
  • CAMELLIA256 = Camellia cipher: "a symmetric key block cipher with a block size of 128 bits and key sizes of 128, 192 and 256 bits." "Camellia is considered a modern safe cipher. Even using the smaller key size option (128 bits), it's considered infeasible to break it by brute-force attack on the keys with current technology." [ https://en.wikipedia.org/wiki/Camellia_(cipher) ]
  • CBC/CBC3 = Cipher Block Chaining: not strictly encryption, "each block of plaintext is XORed with the previous ciphertext block before being encrypted," which makes it very much harder to decrypt a message in pieces(?). CBC3 appears to be an OpenSSL-only thing, indicating the use of 3DES. [ https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CBC ]
  • CHACHA20 = A variant of the Salsa20 stream cipher: "Google has selected ChaCha20 along with Bernstein's Poly1305 message authentication code as a replacement for RC4 in TLS." [ https://en.wikipedia.org/wiki/Salsa20#ChaCha_variant ]
  • DES = Data Encryption Standard: "This cipher has been superseded by the Advanced Encryption Standard (AES). Furthermore, DES has been withdrawn as a standard by the National Institute of Standards and Technology." In fact, it's flat out broken.
  • DHE = Diffie-Hellman Ephemeral key exchange: "a method of securely exchanging cryptographic keys over a public channel." Considered secure - so long as you generate your own DH parameters instead of using the defaults. [ https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange ] DSS = Digital Signature Standard: I can't find out why this is deprecated, but there seems to be wide agreement on exactly that. [ https://en.wikipedia.org/wiki/Digital_Signature_Algorithm ]
  • ECDHE = Elliptic-Curve Diffie-Hellman, Ephemeral keys: "an anonymous key agreement protocol that allows two parties, each having an elliptic-curve public–private key pair, to establish a shared secret over an insecure channel." [ https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman ]
  • ECDSA = Elliptic Curve Digital Signature Algorithm: there have been attacks against ECDSA, but they appear to have been related to specific bad implementations. However: "the trustworthiness of NIST-produced curves being questioned after revelations that the NSA willingly inserts backdoors into softwares, hardware components and published standards were made; well-known cryptographers have expressed doubts about how the NIST curves were designed, and voluntary tainting has already been proved in the past." [ https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm ]
  • EDE = Encrypt-Decrypt-Encrypt - honestly, I have no comprehension of how this is useful, and have found no useful reference source. It seems to only be associated with 3DES? Which makes life easier for me as that's a weak algorithm I won't be using.
  • GCM = Galois/Counter Mode - "a mode of operation for symmetric key cryptographic block ciphers that has been widely adopted because of its efficiency and performance." [ https://en.wikipedia.org/wiki/Galois/Counter_Mode ]
  • POLY1305: "a cryptographic message authentication code (MAC) created by Daniel J. Bernstein. It can be used to verify the data integrity and the authenticity of a message. It has been standardized in RFC 7539." [ https://en.wikipedia.org/wiki/Poly1305 ]
  • PSK = Pre-Shared Key: not a useful/practical algorithm for a public web server [ https://en.wikipedia.org/wiki/TLS-PSK ]
  • RC4 = Rivest Cipher 4: "While remarkable for its simplicity and speed in software, multiple vulnerabilities have been discovered in RC4, rendering it insecure." (Wikipedia) [ https://en.wikipedia.org/wiki/RC4 ]
  • RSA = Rivest-Shamir-Adleman: an older algorithm, but - surprisingly - still considered secure [ https://en.wikipedia.org/wiki/RSA_(cryptosystem) ]
  • SHA/SHA256/SHA384 = Secure Hash Algorithms: SHA appears to be considered insecure, while SHA-2 (the numbered values) is generally okay. [ https://en.wikipedia.org/wiki/Secure_Hash_Algorithms ]
  • SRP = Secure Remote Password: like PSK, not practical for a public web server [ https://en.wikipedia.org/wiki/TLS-SRP ]

I'm going to go with this (or something close) for Nginx, and something similar for HAProxy: ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:!RC4:!3DES:!DES:!MD5:!PSK:!SRP:!DSS'; I've cut out DES and added a bunch of "NOT" ciphers (including DES and 3DES). But do yourself a favour: do your due diligence and don't just copy from some blog. That's how I ended up needing to do this research myself, I copied from the wrong blog.