Transport Layer Security (TLS)

Why 100% isn’t always the best score

Please do not try to reach a 100% score on SSLabs.

I’ve seen this numerous times on the Let’s Encrypt forums and so I felt the need to scream out: Please don’t do this.

A small introduction to SSLabs and it’s ratings

The website SSLabs provides a service that checks SSL/TLS hosts for their security level, scans for typical TLS issues and displays a final score upon finishing scanning.

Users not familiar with the inner workings of TLS probably don’t understand much of the displayed data. It is therefore natural for these users to interpret the data they understand most – the score. The score is displayed at the top of the results page and may look like this:

Screenshot taken from my own blog, on 18.09.2020 @
Screenshot taken from my own blog, on 18.09.2020 @

As you can see here, there’s an “overall rating”. The highest possible value is A+, the lowest rating is F (or various connection error states). Besides the rating, there’s also a percentage score (0 – 100%), divided into four categories:

  • Certificate – The score of the site’s certificate
  • Protocol support – A score, based on which protocol versions are supported by the server
  • Key Exchange – Score based on the strength of all available key exchanges
  • Cipher strength – Score based on the strength of all available cipher suites

If you look at my screenshot, you should notice that I’ve got 100% only in two categories – Certificate and Protocol Support. The other two categories are only at 90%. Why is that, and why shouldn’t I fix that?

Before we dive into the details, the reader should be aware that the rating system of SSLabs constantly changes. Future versions may work differently and this information may not be up to date.

Category 1 – the certificate

Getting 100% is pretty simple here, and most users don’t need to do anything to achieve this. According the the SSLabs rating guide, the certificate is evaluated by typical misconfigurations or weaknesses, and points are deducted if anything is out of the usual. Most CA’s (like Let’s Encrypt) will generally not issue “bad” certificates, so there’s not much room for issues here. There are some things to misconfigure though, e.g a bad or incomplete cert chain – SSLabs will show a warning in the results if something was found.

Category 2 – protocol support

We could talk ages about what SSL/TLS version exist, and which you should use or shouldn’t use, but I’m going to make it short: Trust me, or trust Mozilla’s Guidelines, or trust one of the million sites that have great scores on SSLabs:

Use TLSv1.2 and TLSv1.3 only. Do not enable anything lower (anything newer doesn’t exist at the time of writing). Doing this will also get you the 100% score on SSLabs in terms of protocol support. TLSv1.2 is twelve years old, so compatibility isn’t a factor unless you need to support extremly old things (in that case you should consider isolating the legacy systems).

Category 3 – key exchange

Now it gets interesting. This is one of two categories where I don’t have 100%. And you shouldn’t either. In order to get 100% on key exchange, you would need to make sure that all algorithms used in the key exchange have a theoretical bit security strength that is greater than some arbitrary value set by SSLabs. That is currently at least 4096 Diffie-Hellman, or a 384-bit EC curve or higher. You’re not allowed to offer anything with less size, that would decrease your score (usually to 90%, which is what you see on my site).

So, why are larger keys bad?

They’re not bad, but their increase in security is mostly theoretical. We cannot break a 256 bit EC key, and we also cannot break a 384 bit key. We may be able to do this in the future though, which is why some folks tend to prefer larger keys. However, some researchers say that it is much more likely that a curve is broken by a technical flaw, rather than by true brute-force power. If such a flaw is found, it will most likely affect both the smaller and the larger curves. The value in practice is small.

The downside of a larger key is however, that not all clients support it (also performance). Google Chrome, for example, has dropped support for one the largest NIST curves, secp521r1. You cannot use this curve together with Chromium-based browser, which is the majority of the web. Android 7.0 has a bug, where the only supported elliptic curve is prime256v1. Prime256v1 is one of the smaller curves (256 bits), but statistically the most used ECDSA curve in the public internet.

So, using larger curves will not really increase your strength against an attacker, but will lock out clients without reason. That is why I’m reiterating: A DHE key exchange of 2048-bit is still okay today, and you should also offer smaller curves. You obviously can offer larger values – but do not drop support for the smaller ones yet. Except for things like 1024-bit RSA/DHE1 – that’s dangerously low. But 2048-bit to 4096-bit RSA/DHE is a reasonable range to offer.

Category 4 – cipher strength

The second category where I only have 90%. The reasoning is very similar to category 3 – a higher score kills of clients support, without a strong increase in security level.

Getting 100% here requires all offered block ciphers to have a key length of 256 bits or greater. Anything lower must not be offered. The most famous (and used) block cipher is probably AES. The largest key size available for AES is 256, the lowest 128 bits.

Similar to key exchanges, more bits are not neccessarily more secure. A larger key means more work brute-force wise and generally increases computation work for many attacks. But, there are also attacks, like Cache-timing attacks, that can work even on the large AES-256 keys. So yes, a larger key is a bit better, but it is also a lot slower and security doesn’t increase dramatically.

The major issue again here is, besides performance, that not all clients talk AES256 (in all cipher combinations). For example, according to SSLLabs, Firefox 47 on Windows 7 only speaks AES128-GCM, not AES256-GCM. 256-bit AES-CBC is supported, but we don’t want to talk CBC. So requiring the use of AES256 will again lock out lots of clients.

It is therefore far better to offer both – AES128-GCM and AES256-GCM. If the client supports both, you can still select AES256 by turning on server-side cipher preference (see my older posts on TLS). But do not force it, unless you like killing of clients.

I hope this post has cleared up some misconceptions about the scores on SSLabs, and why a full 100%-score is sacrificing support without better overall security. I also want to remind server operators, that a secure TLS connection is only a part of the deal – a secure site also requires a well-engineered and maintained application behind it. I plan to make some more web-related posts about typical attacks, security headers and similar things in the future.

If you have any questions, feel free to ask below or contact me directly – my email is here somewhere.

Transport Layer Security (TLS)

Are CBC cipher suites still used in HTTPS?

This is a question I’ve asked myself about two months ago. The question was whether I should follow Mozilla’s Guidelines for Server Side TLS and throw old, legacy ciphers (like CBC-based ciphers), overboard. This depends on if they’re still used in practice. There will certainly always be clients that only support legacy stuff. I have no interest in maintaining absurd backwards compatiblity by sacrifcing security. But I do want some reasonable amount of backwards compatibilty – I certainly don’t want to lock out legitimate users using only slightly outdated software.

I’m already requiring HTTPS on my sites since 2016. In 2018 I dropped support for TLS 1.1 and below – TLS 1.2 was published in 2008, so if you’re TWELVE years behind: Sorry, but that isn’t going to end well.

In the past I’ve also killed of more legacy ciphers such as those using Cipher Block Chaining Mode (CBC). I’ve now reenabled these for testing purposes. The CBC mode has some serious issues: For one, there are (possible) padding oracle attacks against CBC. This isn’t just a theoretical thing, actual attacks were performed in the past. While we’re at it: Don’t forget the Poodle variants… There are even more dragons lurking around, such as Lucky Thirteen (a timing side channel based attack on CBC). But it doesn’t stop here: CBC (in combination with TLS) has also suffered from indirect attacks like OpenSSL’s 0-Length Bug. There’s also a whole bunch of issues with ciphers that separate encryption and authentication. As a conclusion, ciphersuites that use CBC aren’t really preferable in a modern, secure encryption scheme.

TLS 1.2 introduced AEAD (authenticated encryption) ciphers. Those do both things (authentication & encryption) together, eliminating many oracle issues. They don’t solve all issues, but as of now (2020) they’re the best we have at this time. Because TLS 1.2 is already 12 years old, these secure AEAD ciphersuites have seen great adoption in practice.

Now, Mozilla recommends to turn of CBC entirely and only use those AEAD ciphersuites. I would love to do that, but before I turn my back on CBC I want to be sure that all common clients do support the newer AEAD things. Qualys SSL Labs seems to suggest that many outdated macOS computers do not support any AEAD cipher (Note that we do not care about clients that do not even speak TLS 1.2 – that’s already off the table).

Because I couldn’t find any usage study on cipher suites used in the wild, I decided to do my own, non-scientific study. For the past two months I logged all TLS ciphersuites used by clients that performed at least one complete HTTPS request [to my server]. This means that most TLS scans, which do not make any HTTP(S) requests, were excluded from the logging. This way most data gathered is from actually meaningful clients (crawlers & humans plus a few additional bots).

I’m actively monitoring whether I see clients that really do not speak anything better than CBC. For the past two months, I haven’t had one (except for two clients which were definetly TLS scanners). However, at this point I’m still not done with the survey. I will continue to monitor for some more and once I have gathered sufficient data, I will decide whether to ditch CBC or not.

Note that this is not a scientific study of any kind, and as such I will not compile any beautfiul data sets. I may post some stats here in the future if I feel like it. I’m not getting paid for this, so involved effort is low.

If you want to see what my current TLS setups looks like, you can see for yourself on Qualys. I’m also planning on writing a more detailed blog post about how the setup looks and the reasoning behind it. It’s all on my TODO list, I promise.

Update (1 month later)

I have continued to monitor ciphersuites and I did have some clients sending HTTP requests using CBC ciphers. All of these clients were some type of TLS scanner or vulnerability scanners. I couldn’t identify a single useful bot or human user with CBC. As a result I have turned of CBC completly. That also means that server cipher preference is now off and the client can choose it’s preferred cipher (as Mozilla recommends).

Another Update (many months later)

I did encounter some very old clients now only supporting CBC (but TLS 1.2) while setting up some new services. Those were IOT devices (sigh) and other embedded legacy stuff like webbrowsers from Smart TV’s and similar. One of these didn’t even knew what elliptic curves are (no ECDHE and no ECDSA). I’ve reenabled a single CBC cipher suite for those clients.

Another major problem with these clients is the upcoming switch of Let’s Encrypt’s Root Certificate, ISRG Root X1. Those clients are so old that they don’t have an up to date trust store and thus cannot validate certificates from the new root. I initially underestimated how many legacy client are out there, even my own household has some devices affected by this. Sadly, there’s no good strategy here, but that’s a topic for another post…

Transport Layer Security (TLS)

Undocumented openssl.cnf options and PrioritizeChaCha

Blarg, another long title. Again. Sorry.

So, this is something that I actually discovered a while (months) ago, so my memories are already a bit less fresh, but I think I still remember the important things.

I discovered this, because I had a common TLS CipherSuite config problem: I needed a server-site cipher order (e.g server has it’s own preferences), because I had some legacy weaker cipher enabled and we obviously don’t want clients with incorrect cipher order to connect with a potentially weak suite, when there are better suites available. But I also wanted to use ChaCha (more on that later).

If you don’t understand what I’m talking about, below is a summary on how TLS cipher suite negotiation works. If you know that stuff already, skip this chapter.

TLS cipher suite negotiation

TLS negotiates a cipher suite on each connection. There are many available – some are really secure, others are “okay-ish”, others are really bad. The really bad ones are usually disabled either at server or client-side (or both). But the “okay-ish” suites are generally enabled both at server and client side, even though client and server potentially also support the strong “good” ones.
What happens is that the client gives the server a list of cipher suites, which should be sorted in order of preference, and the server chooses one of them, depending on what is supported by both.

The server has generally two ways of choosing:

  • Server preference. The server maintains its own list of “preferred” cipher suites and chooses the best one on its list, depending on what the client supports.
  • Client preference. The server just chooses the first suite of the clients list that both parties support.

The thing with Client preference is, that there are (or were) some clients that send incorrectly ordered cipher suite lists – they have insecure ciphers at the top and more secure ones at the bottom. If the server lets the client choose, a weak cipher suite will be negotiated, even though both parties may support something stronger.

On the other hand, if the server maintains a correctly sorted list, one can guarantee that with server preference, the server will choose the most secure option depending on what the client supports. Thats the reason why server preference is the most common setting in real-world TLS.

Just use server cipher order and be done with it!

Yeah, that’s what many people do. And it was fine for some time. The thing is, TLS 1.2 at some point introduced a new cipher / cipher suite:


That is an entirely new encryption algorithm (ChaCha with 20 rounds) and Poly1305 authentication. While those may not be exactly new, they were newly introduced into TLS.

What’s so special about ChaCha?

I won’t go into details here, I’m sure there are already posts elsewhere that cover this. Basically, ChaCha is (probably) secure and fast. This is interesting for machines that are not AES hardware accelerated (no AES-NI). This applies for example to most ARM based systems, like smartphones. Other examples may be IOT or embedded devices. Those don’t have accelerated AES and ChaCha is noticeably faster on those devices, especially when comparing against AES-256. Again, I won’t post big benchmarks here, those are elsewhere.

In summary, we would really want to use ChaCha on such devices.

Then just set ChaCha as preferred server cipher and be done with it!

But… I still like AES! Because that’s the thing: While ChaCha may be the new cool thing, many devices do have AES-NI (hardware accelerated AES) and thus can do AES faster than ChaCha. Plus, AES has been around for quite a while and so far we consider it secure. It’s also FIPS and similar certified, for people that are into that stuff.

So we still want to use AES! We just don’t want to use it with devices that have no AES-NI.

How can you get the best of both worlds?

What we need is something like this: If the client says “I don’t have AES-NI”, use ChaCha. If the client doesn’t speak ChaCha, or if it has AES-NI, use AES.

But how to determine if a client doesn’t have AES-NI? Fortunately, the standards made this not that hard: Modern clients that speak ChaCha will put ChaCha on the top of their cipher list, if they don’t have AES-NI.

Older clients, or clients with AES-NI will have something else on top. So the easiest way to use ChaCha with clients is to let the clients choose: If they have ChaCha on the top, use that. If not, use whatever else they have at the top. But that puts us back to the original problem above! What if an old, broken client puts legacy stuff on the top? There was a reason why we used server cipher preference.

Okay, so we want both client AND server cipher preference. That’s sadly something that OpenSSL can’t do. But OpenSSL developers saw the issue and offered us a solution:

OpenSSL has a flag for this!

Yep, that’s right. OpenSSL has a flag, called “PrioritizeChaCha” that does exactly what I described above: It will choose ChaCha if the client says “thats my most preferred cipher suite”, but will still honor the server cipher order in all other cases.

However, that is generally a compile-time flag. Meaning that you need to compile OpenSSL with this option if you want it. This isn’t really what I consider ideal. Many people use pre-compiled packages – after all, most distros ship programs this way. Especially when talking about security relevant stuff like OpenSSL, I personally like relying on the debian security team to update important packages. I just don’t favor the idea of compiling OpenSSL myself – it’s a lot more work for me, with not much benefits.

There’s another way: Setting this without re-compiling OpenSSL!

Yup, and that’s what this blog post was supposed to be about. The entire text wall above was just introduction for this.

OpenSSL has a config file. If you didn’t know that, I don’t blame you: I didn’t either, until last year or so. Since OpenSSL is mostly used as a library, there isn’t much to configure. On Debian-like systems, the file is /etc/ssl/openssl.cnf. 99% of that file is related to Certificate Authority settings. I think that is because you can make your own CA with OpenSSL and that file is for persistently setting parameters needed for a real CA.

But you can set more than just CA-specific parameters! The file can do more. But here’s the thing:

It’s fucking undocumented (except for the CA stuff)!

At least that’s what I think. If you find a detailed documentation for that file, that explains all the flag options that it has, please link it here! In that case you’re better than me in researching, because I found hardly ANY infos about that file.

I had to read the source code of OpenSSL to get a bit of understanding on what you can do with this file. If you have to read the source code to understand something, the documentation is shit.

I will now try to give some guidance for that file. Ignore all the CA stuff – I’m not going to explain that. I’m interested in what this file can do to modify the TLS behavior.

Basically, the file is structured in sections, which are formatted like this: [section_name] followed by a list of settings valid for that section. A section ends when the next section starts.

In order to configure TLS options, we need to look at what sections are reponsible for that. The starting point is the setting openssl_conf which is part of some kind of “root section” that has no specific name. The default value of openssl_conf is set to default_conf. In order to define what default_conf should be, you can define a section called default_conf that sets everything you want.

Since we’re interested in TLS (SSL) specific settings, we set the ssl_conf parameter in our custom default_conf to the name of a section, let’s say ssl_sect (short for ssl_section). In the ssl_sect we then declare a setting system_default which holds some TLS options, unless the application overrides them explicitly. We set system_default to system_default_sect, again that is short for “system default section”.

In this section, we finally get to set our TLS/SSL parameters. You can for example define custom TLS min/max versions, default cipher suites and most importantly flags like PrioritizeChaCha.

ssl_conf = ssl_sect

system_default = system_default_sect

MinProtocol = TLSv1.2
Options = PrioritizeChaCha,NoRenegotiation

The example above sets default parameters for each application that doesn’t specify anything else on it’s own:

  • Minimum TLS version is set to TLS 1.2
  • Some insecure cipher suites are disabled (to be honest, I’m not excactly sure what seclevel 2 is exactly)
  • Two flags are set: PrioritizeChaCha and NoRenegotation

The important things here are the flags: PrioritizeChaCha is explained in the beginning of this post. This config would enable the flag by default for all applications using OpenSSL. The other flag, NoRenegotation, disables TLS renegotiation. That’s mostly “for fun”. Renegotation was broken in the past, has not much use and was even killed in TLS 1.3, so not much reason to have it on in TLS 1.2 either.

There are more than just these flags. However, as explained, I couldn’t find documentation for them. I found the available flags by browsing the source code. From the code, the following flags are available in the config file:

  • SessionTicket
  • EmptyFragments
  • Bugs
  • Compression
  • ServerPreference
  • NoResumptionOnRenegotiation
  • DHSingle
  • ECDHSingle
  • UnsafeLegacyRenegotiation
  • EncryptThenMac
  • NoRenegotiation
  • AllowNoDHEKEX
  • PrioritizeChaCha
  • MiddleboxCompat
  • AntiReplay
  • ExtendedMasterSecret
  • CANames

I won’t explain all these flags in detail here. If you have a specific question about one of these, feel free to ask. You can also look up the source code yourself and see if that answers your question.

Since I feel like this post is already way longer than intended, I will stop here. If you have any further questions about some of the topics covered here, feel free to use the comment section below. Or email me directly – my email is here somewhere.