Много кто писал о том, как подбирать шифры для nginx для получения высокой оценки по тесту от Qualys. При этом практически везде директивы ssl_ciphers и подобные даются как эдакие магические строки, которые нужно просто вставить, и надеяться на удачу. Объяснения почему именно такая строка и почему шифры именно в такой последовательности, как правило, не дается. Моя статья призвана исправить это недоразумение. Директива ssl_ciphers потеряет для вас всякую магию, а ECDHE и AES будут как друзья-братья.

Подготовка

Работать будем с Debian стабильного выпуска. Если у вас другой дистрибутив, то версия OpenSSL должна быть такая же или более новая.

# openssl version
OpenSSL 1.0.1t  3 May 2016

Настроим nginx для получения сертификатов от Let's Encrypt по инструкции по работе с Certbot, но только до получения сертификата для нашего домена.

certbot certonly -d $(hostname -d) -d www.$(hostname -d)

Конфиг для сайта используем минимальный. Ничего лишнего и всё по умолчанию.

server {
    server_name example.com www.example.com;
    listen 443 ssl default_server;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
}

Теперь прогоним тест для этого сервера.

Тест показывает оценку B, то есть тройка по-нашему.

Слабые параметры DH

С настройками по умолчанию результат на четвёрку, если по-нашему. Ругаются на слабые параметры для обмена ключами по алгоритму Диффи — Хеллмана (далее просто DH). Слабые параметры (пара специально выбранных простых чисел) принципиально позволяют АНБ, ЦРУ и подобным организациям с неограниченным ресурсом прочитать ваш трафик. Это не то, что вы хотите.

Можно было бы сделать усиленные параметры, но такие параметры не помогают ускорить соединение, и некоторыми старыми клиентами поддерживаются лишь ограниченно. У протокола DH на эллиптических кривых (ECDHE) таких недостатков нет, значит мы ничего не теряем и даже приобретаем в скорости, если мы полностью откажемся от медленных EDH шифров в пользу ECDHE, рекомендуемых в том числе создателями теста.

Protocol Support

Для получения 100% оценки по графе Protocol Support следовало бы отключить протоколы ниже TLS 1.2, но мы этого делать не будем, так как есть целая группа клиентов, которые не поддерживают ничего старше TLS 1.0, а значит не смогут достучаться до сайта без TLS 1.0.

Их легко видно в разделе Handshake Simulation: это IE любых версий младше 11, Java до SE 8, Android до версии 4.3 и даже некоторые версии Safari. Про всевозможный софт, собранный с OpenSSL версии 0.9.8 и младше, даже и не говорю.

Key Exchange

Для получения 100% оценки по графе Key Exchange следовало бы получить ключи на 4096 бит.

certbot certonly --renew-by-default --rsa-key-size 4096 -d $(hostname -d) -d www.$(hostname -d)

Мы этого не будем делать потому такие ключи непосредственно влияют на скорость установки соединения, при этом не слишком лучше ключей на 2048 бит.

$ openssl speed rsa2048 rsa4096
               sign      verify      sign/s verify/s
rsa 2048 bits  0.002381s 0.000071s    420.0  14051.0
rsa 4096 bits  0.016790s 0.000260s     59.6   3852.8

Подпись на стороне сервера делается в семь раз медленнее. Проверка подписи на клиенте - почти в четыре раза медленнее. Это плата за усиление стойкости шифра на какие-то 16%.

Cipher Strength

Для получения 100% оценки по графе Cipher Strength мы должны были избавиться от всех шифров слабей 256 бит, остановившись на следующих для сертификата с RSA ключем.

ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES256-SHA384;

И выбрать другую, более надежную, кривую.

В используемой версии OpenSSL можно выбрать только одну.

ssl_ecdh_curve secp384r1;

Мы ни того, ни другого делать не будем. Шифры на 256 бит не помогают ускорить соединение, что особенно касается мобильных устройств с маломощными процессорами и прочих клиентов без AES-NI (аппаратного ускорения AES).

for cipher in aes-128-gcm aes-256-gcm chacha20-poly1305
do openssl speed -decrypt -evp $cipher 2>/dev/null | grep ^$cipher
done | column -t

На Celeron 1007U шифр ChaCha20/Poly1305 будет в два раза быстрее AES-128, а последний на треть быстрее AES-256. На Core i7 с AES-NI картина другая: ChaCha20/Poly1305 будет медленнее AES-128 на треть, а AES-256 лишь немного медленнее AES-128. При шифровании картина примерно та же.

Обмен ключами с используемой по умолчанию кривой prime256v1 (она же secp256r1 или NIST P-256) будет существенно быстрее, чем с усиленной кривой secp384r1 (NIST P-384).

$ openssl speed ecdsap256 ecdsap384 ecdhp256 ecdhp384
                              sign    verify    sign/s verify/s
 256 bit ecdsa (nistp256)   0.0001s   0.0003s   7923.8   3214.4
 384 bit ecdsa (nistp384)   0.0006s   0.0023s   1756.6    430.1
                              op      op/s
 256 bit ecdh (nistp256)   0.0002s   4893.5
 384 bit ecdh (nistp384)   0.0019s    525.9

Работа с P-384 требует в 7-9 раз больше времени в зависимости от операции.

Если сравнить ECDSA и RSA, то видно что в случае ECDSA вычислительная нагрузка ложится больше на клиента, чем на сервер, что может быть интересным аргументом для перехода на ECDSA сертификаты.

Принцип поиска шифров

Перечисление шифров списком - не лучшая идея потому что уже скоро можно будет штатно, без перекомпиляции и сторонних источников, использовать ChaCha20/Poly1305. С явным заданием шифров сервер не будет использовать любые новые виды шифрования без перенастройки. Настроить и забыть не получится.

Будет лучше выбирать шифры по ключевым словам или тегам, каждое из которых соответствуют какой-то группе шифров. Ключевые слова в разных версиях OpenSSL отличаются, но мы всегда можем их проверить.

Например, тег EECDH соответствует всем шифрам с обменом одноразовыми (эфемерными) ключами по алгориму DH с эллиптическими кривыми.

$ openssl ciphers -v 'EECDH' | column -t
ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(256)  Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(256)  Mac=AEAD
ECDHE-RSA-AES256-SHA384        TLSv1.2  Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA384
ECDHE-ECDSA-AES256-SHA384      TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA384
ECDHE-RSA-AES256-SHA           SSLv3    Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA1
ECDHE-ECDSA-AES256-SHA         SSLv3    Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA1
ECDHE-RSA-AES128-GCM-SHA256    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(128)  Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(128)  Mac=AEAD
ECDHE-RSA-AES128-SHA256        TLSv1.2  Kx=ECDH  Au=RSA    Enc=AES(128)     Mac=SHA256
ECDHE-ECDSA-AES128-SHA256      TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AES(128)     Mac=SHA256
ECDHE-RSA-AES128-SHA           SSLv3    Kx=ECDH  Au=RSA    Enc=AES(128)     Mac=SHA1
ECDHE-ECDSA-AES128-SHA         SSLv3    Kx=ECDH  Au=ECDSA  Enc=AES(128)     Mac=SHA1
ECDHE-RSA-RC4-SHA              SSLv3    Kx=ECDH  Au=RSA    Enc=RC4(128)     Mac=SHA1
ECDHE-ECDSA-RC4-SHA            SSLv3    Kx=ECDH  Au=ECDSA  Enc=RC4(128)     Mac=SHA1
ECDHE-RSA-DES-CBC3-SHA         SSLv3    Kx=ECDH  Au=RSA    Enc=3DES(168)    Mac=SHA1
ECDHE-ECDSA-DES-CBC3-SHA       SSLv3    Kx=ECDH  Au=ECDSA  Enc=3DES(168)    Mac=SHA1
ECDHE-RSA-NULL-SHA             SSLv3    Kx=ECDH  Au=RSA    Enc=None         Mac=SHA1
ECDHE-ECDSA-NULL-SHA           SSLv3    Kx=ECDH  Au=ECDSA  Enc=None         Mac=SHA1

Шифры показываются в списке согласно приоритету: клиент выберет первый подходщящий шифр, просматривая список в указанном порядке.

Если бы у нас была самая последняя версия OpenSSL, то для получения того же списка мы бы использовали понятный тег ECDHE, дающий тот же самый список и соответствующий префиксу шифров. В текущей версии такого тега нет, потому используем какой есть.

В полученном списке бросаются в глаза шифры без, собственно, шифрования, которые идут с отметкой Enc=None. Исключим такие шифры, дающие только аутентификацию. Заодно исключим шифры без аутентификации.

openssl ciphers -v 'EECDH:!aNULL:!eNULL'

Для полного и безвозратного исключения теги групп aNULL и eNULL c неподходящими шифрами упомянуты с отрицанием. В списке не останется шифров с Au=None или с Enc=None.

Теги можно сочетать, получая пересечения множеств шифров. Получим шифры, сочетающие ECDHE, AES-256 и GCM.

$ openssl ciphers -v 'EECDH+AES256+AESGCM' | column -t
ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(256)  Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(256)  Mac=AEAD

Группы шифров можно переместить ниже по приоритету, указав с плюсом в начале. Понизим приоритет у более требовательных шифров с AES-256, не удаляя их.

$ openssl ciphers -v 'EECDH+aRSA+AES:+AES256' | column -t
ECDHE-RSA-AES128-GCM-SHA256  TLSv1.2  Kx=ECDH  Au=RSA  Enc=AESGCM(128)  Mac=AEAD
ECDHE-RSA-AES128-SHA256      TLSv1.2  Kx=ECDH  Au=RSA  Enc=AES(128)     Mac=SHA256
ECDHE-RSA-AES128-SHA         SSLv3    Kx=ECDH  Au=RSA  Enc=AES(128)     Mac=SHA1
ECDHE-RSA-AES256-GCM-SHA384  TLSv1.2  Kx=ECDH  Au=RSA  Enc=AESGCM(256)  Mac=AEAD
ECDHE-RSA-AES256-SHA384      TLSv1.2  Kx=ECDH  Au=RSA  Enc=AES(256)     Mac=SHA384
ECDHE-RSA-AES256-SHA         SSLv3    Kx=ECDH  Au=RSA  Enc=AES(256)     Mac=SHA1

Можно временно убрать какую-то группу шифров, чтобы потом добавить её в другом виде. Исключим из всех ECDHE шифров шифры с 3DES, а затем вернём их обратно, но только в сочетании с обменом ключами RSA. Такие слабые шифры должны оказаться в самом конце списка.

$ openssl ciphers -v 'EECDH:-3DES:RSA+3DES' | tail -1 | column -t
DES-CBC3-SHA  SSLv3  Kx=RSA  Au=RSA  Enc=3DES(168)  Mac=SHA1

Выбираем шифры

Теперь у нас есть всё, чтобы составить список шифров для nginx, которые не будет требовать дополнения при переходе на новые версии OpenSSL, теряя старые шифры и приобретая новые без какого-либо участия с нашей стороны. Мы учём необходимость поддержки старых браузеров и требования к скорости.

Начнём с получения самых сильных шифров из рекомендуемых, но не будем исключать шифры для сертификатов c EC на случай если мы когда-нибудь захотим использовать такой вид сертификатов.

$ openssl ciphers -v 'EECDH+AES256' | column -t
ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(256)  Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(256)  Mac=AEAD
ECDHE-RSA-AES256-SHA384        TLSv1.2  Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA384
ECDHE-ECDSA-AES256-SHA384      TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA384
ECDHE-RSA-AES256-SHA           SSLv3    Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA1
ECDHE-ECDSA-AES256-SHA         SSLv3    Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA1

На деле нам нужнен не столько конкретно AES, а сколько не нужны слабые шифры 3DES и RC4. Последний исключим полностью и навсегда.

$ openssl ciphers -v 'EECDH:-3DES:!NULL:!RC4' | column -t
ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(256)  Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(256)  Mac=AEAD
ECDHE-RSA-AES256-SHA384        TLSv1.2  Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA384
ECDHE-ECDSA-AES256-SHA384      TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA384
ECDHE-RSA-AES256-SHA           SSLv3    Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA1
ECDHE-ECDSA-AES256-SHA         SSLv3    Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA1
ECDHE-RSA-AES128-GCM-SHA256    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(128)  Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(128)  Mac=AEAD
ECDHE-RSA-AES128-SHA256        TLSv1.2  Kx=ECDH  Au=RSA    Enc=AES(128)     Mac=SHA256
ECDHE-ECDSA-AES128-SHA256      TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AES(128)     Mac=SHA256
ECDHE-RSA-AES128-SHA           SSLv3    Kx=ECDH  Au=RSA    Enc=AES(128)     Mac=SHA1
ECDHE-ECDSA-AES128-SHA         SSLv3    Kx=ECDH  Au=ECDSA  Enc=AES(128)     Mac=SHA1

Отдадим приоритет более быстрым шифрам, понизив его для AES-256. Совсем удалять AES-256 не будем на случай если кому-то очень будет нужен именно AES-256.

$ openssl ciphers -v 'EECDH:+AES256:-3DES:!NULL:!RC4' | column -t
ECDHE-RSA-AES128-GCM-SHA256    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(128)  Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(128)  Mac=AEAD
ECDHE-RSA-AES128-SHA256        TLSv1.2  Kx=ECDH  Au=RSA    Enc=AES(128)     Mac=SHA256
ECDHE-ECDSA-AES128-SHA256      TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AES(128)     Mac=SHA256
ECDHE-RSA-AES128-SHA           SSLv3    Kx=ECDH  Au=RSA    Enc=AES(128)     Mac=SHA1
ECDHE-ECDSA-AES128-SHA         SSLv3    Kx=ECDH  Au=ECDSA  Enc=AES(128)     Mac=SHA1
ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(256)  Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(256)  Mac=AEAD
ECDHE-RSA-AES256-SHA384        TLSv1.2  Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA384
ECDHE-ECDSA-AES256-SHA384      TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA384
ECDHE-RSA-AES256-SHA           SSLv3    Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA1
ECDHE-ECDSA-AES256-SHA         SSLv3    Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA1

Такая строка шифров не исключит ChaCha20/Poly1305 в следующей версии OpenSSL.

$ openssl version; openssl ciphers -v 'EECDH:+AES256:-3DES:!NULL:!RC4' | column -t | head -n 6
OpenSSL 1.1.0e  16 Feb 2017
ECDHE-ECDSA-CHACHA20-POLY1305   TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=CHACHA20/POLY1305(256)  Mac=AEAD
ECDHE-RSA-CHACHA20-POLY1305     TLSv1.2  Kx=ECDH  Au=RSA    Enc=CHACHA20/POLY1305(256)  Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256   TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(128)             Mac=AEAD
ECDHE-RSA-AES128-GCM-SHA256     TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(128)             Mac=AEAD
ECDHE-ECDSA-AES128-CCM8         TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESCCM8(128)            Mac=AEAD
ECDHE-ECDSA-AES128-CCM          TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESCCM(128)             Mac=AEAD

Для некоторых устаревших клиентов вернём AES без эфемерных ключей.

$ openssl ciphers -v 'EECDH:+AES256:-3DES:RSA+AES:!NULL:!RC4' | column -t
ECDHE-RSA-AES128-GCM-SHA256    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(128)  Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(128)  Mac=AEAD
ECDHE-RSA-AES128-SHA256        TLSv1.2  Kx=ECDH  Au=RSA    Enc=AES(128)     Mac=SHA256
ECDHE-ECDSA-AES128-SHA256      TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AES(128)     Mac=SHA256
ECDHE-RSA-AES128-SHA           SSLv3    Kx=ECDH  Au=RSA    Enc=AES(128)     Mac=SHA1
ECDHE-ECDSA-AES128-SHA         SSLv3    Kx=ECDH  Au=ECDSA  Enc=AES(128)     Mac=SHA1
ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(256)  Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(256)  Mac=AEAD
ECDHE-RSA-AES256-SHA384        TLSv1.2  Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA384
ECDHE-ECDSA-AES256-SHA384      TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA384
ECDHE-RSA-AES256-SHA           SSLv3    Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA1
ECDHE-ECDSA-AES256-SHA         SSLv3    Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA1
AES256-GCM-SHA384              TLSv1.2  Kx=RSA   Au=RSA    Enc=AESGCM(256)  Mac=AEAD
AES256-SHA256                  TLSv1.2  Kx=RSA   Au=RSA    Enc=AES(256)     Mac=SHA256
AES256-SHA                     SSLv3    Kx=RSA   Au=RSA    Enc=AES(256)     Mac=SHA1
AES128-GCM-SHA256              TLSv1.2  Kx=RSA   Au=RSA    Enc=AESGCM(128)  Mac=AEAD
AES128-SHA256                  TLSv1.2  Kx=RSA   Au=RSA    Enc=AES(128)     Mac=SHA256
AES128-SHA                     SSLv3    Kx=RSA   Au=RSA    Enc=AES(128)     Mac=SHA1

Наконец, вернём 3DES без эфемерных ключей чисто для IE8/XP.

$ openssl ciphers -v 'EECDH:+AES256:-3DES:RSA+AES:RSA+3DES:!NULL:!RC4' | column -t
ECDHE-RSA-AES128-GCM-SHA256    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(128)  Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(128)  Mac=AEAD
ECDHE-RSA-AES128-SHA256        TLSv1.2  Kx=ECDH  Au=RSA    Enc=AES(128)     Mac=SHA256
ECDHE-ECDSA-AES128-SHA256      TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AES(128)     Mac=SHA256
ECDHE-RSA-AES128-SHA           SSLv3    Kx=ECDH  Au=RSA    Enc=AES(128)     Mac=SHA1
ECDHE-ECDSA-AES128-SHA         SSLv3    Kx=ECDH  Au=ECDSA  Enc=AES(128)     Mac=SHA1
ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(256)  Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(256)  Mac=AEAD
ECDHE-RSA-AES256-SHA384        TLSv1.2  Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA384
ECDHE-ECDSA-AES256-SHA384      TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA384
ECDHE-RSA-AES256-SHA           SSLv3    Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA1
ECDHE-ECDSA-AES256-SHA         SSLv3    Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA1
AES256-GCM-SHA384              TLSv1.2  Kx=RSA   Au=RSA    Enc=AESGCM(256)  Mac=AEAD
AES256-SHA256                  TLSv1.2  Kx=RSA   Au=RSA    Enc=AES(256)     Mac=SHA256
AES256-SHA                     SSLv3    Kx=RSA   Au=RSA    Enc=AES(256)     Mac=SHA1
AES128-GCM-SHA256              TLSv1.2  Kx=RSA   Au=RSA    Enc=AESGCM(128)  Mac=AEAD
AES128-SHA256                  TLSv1.2  Kx=RSA   Au=RSA    Enc=AES(128)     Mac=SHA256
AES128-SHA                     SSLv3    Kx=RSA   Au=RSA    Enc=AES(128)     Mac=SHA1
DES-CBC3-SHA                   SSLv3    Kx=RSA   Au=RSA    Enc=3DES(168)    Mac=SHA1

Помочь IE6/XP никак нельзя - он по умолчанию не поддерживает TLS.

Итого

С шифрами закончили. Для оценки с плюсом добавим HSTS.

server {
    server_name example.com www.example.com;
    listen 443 ssl default_server;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

    add_header Strict-Transport-Security "max-age=31536000";
    ssl_ciphers EECDH:+AES256:-3DES:RSA+AES:RSA+3DES:!NULL:!RC4;
}

Проверим, что никакой ошибки в настройке шифров и протоколов нет.

$ nmap --script ssl-enum-ciphers -p 443 $(hostname -d)

Starting Nmap 1.00 ( http://nmap.org ) at 2017-03-01 00:00 UTC
Nmap scan report for example.com (1.2.3.5)
Host is up (0.000052s latency).
PORT    STATE SERVICE
443/tcp open  https
| ssl-enum-ciphers: 
|   SSLv3: No supported ciphers found
|   TLSv1.0: 
|     ciphers: 
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA - strong
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - strong
|       TLS_RSA_WITH_3DES_EDE_CBC_SHA - strong
|       TLS_RSA_WITH_AES_128_CBC_SHA - strong
|       TLS_RSA_WITH_AES_256_CBC_SHA - strong
|     compressors: 
|       NULL
|   TLSv1.1: 
|     ciphers: 
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA - strong
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - strong
|       TLS_RSA_WITH_3DES_EDE_CBC_SHA - strong
|       TLS_RSA_WITH_AES_128_CBC_SHA - strong
|       TLS_RSA_WITH_AES_256_CBC_SHA - strong
|     compressors: 
|       NULL
|   TLSv1.2: 
|     ciphers: 
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA - strong
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 - strong
|       TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - strong
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - strong
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 - strong
|       TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - strong
|       TLS_RSA_WITH_3DES_EDE_CBC_SHA - strong
|       TLS_RSA_WITH_AES_128_CBC_SHA - strong
|       TLS_RSA_WITH_AES_128_CBC_SHA256 - strong
|       TLS_RSA_WITH_AES_128_GCM_SHA256 - strong
|       TLS_RSA_WITH_AES_256_CBC_SHA - strong
|       TLS_RSA_WITH_AES_256_CBC_SHA256 - strong
|       TLS_RSA_WITH_AES_256_GCM_SHA384 - strong
|     compressors: 
|       NULL
|_  least strength: strong

Nmap done: 1 IP address (1 host up) scanned in 1.17 seconds

Шифры на месте, хоть и не в том порядке. Но это ожидаемо. Можно запускать тест.

Тест показывает оценку A+, то есть пять с плюсом по-нашему

С таким конфигом мы получаем ровно такой результат как у Google на начало 2017 года.

При этом мы точно знаем что у всех клиентов сайт открывается настолько быстро, насколько это возможно, и без использования слабых шифров: все современные браузеры используют ECDHE.

Также вам обязательно нужно настроить OCSP stapling и, опционально, кеш TLS сессий.

Возможные проблемы

Что делать если всё сделано по инструкции, но оценка всё равно не A+ или 5 с плюсом если по нашему?

Одной из причин может быть отсутствие приоритета для серверных шифров, без которой некоторые клиенты выбирают субоптимальные шифры. Такая директива есть по умолчанию в nginx.conf в Debian. Если её нет, то это легко исправить.

tee /etc/nginx/conf.d/ssl_prefer_server_ciphers.conf <<EOF
ssl_prefer_server_ciphers on;
EOF