Pull to refresh

Заметки по выбору шифров для TLS 1.3

Decentralized networks Information Security *Cryptography *Algorithms *IT Standards *
Translation
Original author: klondike

После дискуссии с коллегами о TLS 1.3 в целом и прикладном использовании идущих в комплекте с ним шифров я решил кратко изложить основы, которые не худо было бы знать любому разработчику. Мне хотелось бы показать, на какую нишу направлен каждый из пяти шифров и как конкретно применяются содержащиеся в них хеш-функции.

В этой статье не будут освещаться детали согласования ключей в мере большей, нежели та, что необходима для описания установки безопасного соединения как такового. Я не буду рассуждать о протоколе Диффи-Хеллмана и эллиптических кривых, сертификатах, распределённых ключах и о тому подобном. Если возникнет необходимость, эти вопросы будут рассмотрены в будущем.

Выбор шифра

Первым делом давайте разберёмся, как две стороны, коммуницирующие посредством TLS 1.3, определяют, какой конкретно шифр при этом использовать. Этот процесс известен как процесс согласования шифров и является частью "рукопожатия" или, иначе говоря, первичного взаимодействия двух участников. Помимо этого происходит сразу несколько других вещей, но, как я и говорил выше, они не рассматриваются в данной статье.

Чтобы произвести рукопожатие, сторона, выступающая в качестве клиента, отправляет сообщение "ClientHello", которое помимо прочего содержит список поддерживаемых шифров в порядке от наиболее предпочтительного до наименее предпочтительного.

Как только сторона, выступающая в качестве сервера, получает эту информацию, она должна выбрать, какой именно шифр использовать (или закрыть соединение ввиду того, что не поддерживается ни один из предложенных). Это происходит с помощью сообщения, известного как "ServerHello". За ним следует обмен данными для старта зашифрованного соединения, называемый "ChangeCipherSpec". Весь процесс обычно происходит в чистом тексте и выглядит следующим образом:

  1. Клиент: Привет! Я предпочитаю шифрA, ну или шифрБ или шифрЦ.

  2. Сервер: Годится! Давай-ка возьмём шифрБ. Переключаюсь на него. (Следуют зашифрованные данные).

  3. Клиент: Добро! Тож переключаюсь на него. (Следуют зашифрованные данные).

Собственно, если клиент по какой-то причине ограничен в выборе шифра, он может сообщить это сразу; и если сервер не решит забраковать выбор клиента, шифр будет использован (если поддерживается, конечно). Это полезно не столько с точки зрения безопасности, сколько для производительности, поскольку ни клиент, ни сервер изначально не должны анонсировать шифры, которые они считают небезопасными.

Доступные шифры

RFC 8446 описывает TLS 1.3 в совокупности с 5 доступными шифронаборами. Существует также RFC 8998, который определяет ещё два обязательных в Китае шифра (известных как ShangMi) и ещё два черновика, один из которых добавляет поддержку ГОСТовских шифров, а другой добавляет режимы "только аутентификация". Я опишу работу пяти "оригинальных" шифров, поскольку остальные либо экспериментальные, либо используются редко.

Итак, пять шифров, которые поддерживает TLS 1.3:

TLS_AES_128_GCM_SHA256
Блочный шифр AES в режиме GCM со 128-битным ключом и хеш-функцией SHA-256

TLS_AES_256_GCM_SHA384
Блочный шифр AES в режиме GCM с 256-битным ключом и хеш-функцией SHA-384

TLS_CHACHA20_POLY1305_SHA256
Потоковый шифр ChaCha20 с 256-битным ключом, аутентификатором Poly1305 и хеш-функцией SHA-256.

TLS_AES_128_CCM_SHA256
Блочный шифр AES в режиме CCM со 128-битным ключом и хеш-функцией SHA-256

TLS_AES_128_CCM_8_SHA256
Блочный шифр AES в режиме CCM со 128-битным ключом, ограниченными до 8 байт тэгами аутентификации и хеш-функцией SHA-256

Шифры и хеши

Вы могли заметить, что каждый раз мы упоминаем шифр в совокупности с хешем (а не, например, с механизмом согласования ключей). Это потому, что они связаны, но не так, как может показаться на первый взгляд.

В TLS 1.3 шифры служат как раз тому, что известно под именованием AEAD (аутентифицированное шифрование с присоединёнными данными). Это означает, что они делают сразу несколько вещей одновременно:

  • Шифруют данные с помощью ключа. Это сохраняет конфиденциальность данных, то есть гарантирует, что никто кроме владельца ключа не сможет их прочесть.

  • Аутентифицируют данные с помощью ключа. Это сохраняет целостность, то есть гарантирует, что в данных не было зафиксировано никаких изменений (добавление или удаление). Аутентификация также гарантирует, что только обладатель ключа шифрования может отправить данные, которые сможет принять вторая сторона.

  • Авторизуют (опять же, с помощью ключа) незашифрованные данные. Это даёт те же гарантии, что и в плане целостности и возможности создавать валидные сообщения для любых данных, какие потребуется валидировать. В частности, это полезно, когда нужно удостовериться, что незашифрованные данные, которые определяют свойства зашифрованных данных, не были изменены.

Следует, однако, помнить, что эти шифры не предоставляют репутационных гарантий (то есть гарантий того, что отправитель и обладатель подписи одно и то же лицо) потому как любой владеющий ключом дешифровки может создать новое сообщение, которое будет определено как валидное. В TLS, это, как правило, реализуется другим способом. Так или иначе, поскольку предоставляется конфиденциальность, гарантируется целостность и достоверность равно для зашифрованных и незашифрованных данных, эти шифры явлются идеальным "строительным материалом" для протоколов вроде TLS, которые в целом на всё вышеперечисленное и направлены.

Но если шифр может удовлетворить все возможные нужды, зачем нужен хеш? У него иное назначение. Во-первых, хеш используется, чтобы создать сводку сообщений, которыми уже был произведён обмен. Это, в частности, полезно, если атакующий изменяет любое сообщение – изменится и результат вычисления. Во-вторых, хеш нужен, чтобы извлечь все применённые протоколом ключи из данных обмена (включая и саму сводку). В-третьих, хеш используется, дабы посредством одного из извлечённых ключей удостоверить обе стороны, что не было произведено подделки при генерации тэга аутентификации из сводки обмена сообщениями и что ключи одинаковы на обоих концах.

Как видите, хеш играет важную роль ещё до того, как будет произведён обмен сообщениями. Коль скоро мы знаем, что общаемся с нужным человеком и что не был устроен подлог, мы отправляем данные, используя AEAD шифр. В результате для длительных соединений, когда отправляются значительные объёмы данных, этот последний шифр будет являться одним их главных факторов производительности, которые необходимо учитывать.

Далее я объясню разницу между алгоритмами хеширования и различными шифрами, а также укажу на факторы, определяющие выбор того или иного из них.

SHA-256

SHA-256 является достаточно старым (2001) алгоритмом хеширования, созданным NIST в качестве альтернативы SHA-1 и MD5 из-за опасений касательно их безопасности. В это же время был объявлен конкурс на создание нового алгоритма (SHA-3).

Данный алгоритм обеспечивает уровень безопасности, эквивалентный 128-битному шифру для большинства задач и, поскольку он оперирует 32 битами даных, достаточно быстр на большинстве архитерктур. Некоторые современные десктопные и серверные процессоры, а также многие криптографические ускорители поддерживают его на аппаратном уровне.

SHA-384

Как и SHA-256, SHA-384 был представлен в том же стандарте и использует похожий подход.

Однако в отличие от собрата SHA-384 обеспечивает уровень безопасности, эквивалентный 192-битному шифру. SHA-384 оперирует 64 битами данных, и это означает, что он может обработать вдвое больше данных за то же время. Расчёт производится дольше, но на 64-битных процессорах всё равно оказывается быстрее решений, основанных на SHA-256. К тому же в отличие от последнего он отбрасывает 128 бит данных от результата хеширования. Это делает атаки удлинением сообщения сложнее, поскольку чтобы обработать эти биты данных, атакующий должен фактически угадать их.

Десктопные и серверные процессоры не имеют специфической поддержки этого алгоритма, а его поддержка среди криптографических ускорителей сильно варьируется по причине необходимости хранить больше информации о состоянии операции и выполняемых действиях.

По итогу SHA-384 более безопасен, нежели SHA-256 (хотя и не в случае применения квантовых компьютеров). Также он быстрее, когда применяется в ПО, исполняемом на 64-битных процессорах, в случае если сообщение достаточно длинное. Но на системах с ускорителями или инструкциями поддержки только для SHA-256 или же без поддержки 64-битных инструкций он будет медленнее последнего.

128-битный AES в режиме GCM

В ходе проведённого NIST конкурса на замену ныне устаревшего DES (Data Encryption Standard) был выбран алгоритм Rijndael, впоследствии ставший известным как AES (Advanced Encryption Standard).

AES это блочный шифр; в качестве входных данных он получает ключ шифрования фиксированного размера (128, 192 или 256 бит) и блок данных фиксированного размера (128 бит), а на выходе возвращает зашифрованный блок данных размером 128 бит. Обратный процесс осуществляется аналогично: чтобы получить оригинальный блок данных, понадобится ключ и блок данных, зашифрованный этим ключом.

Однако нужно определиться, что делать с данными, которые имеют размер больше или меньше одного блока, и это как раз то, для чего нужен режим шифрования. В данном случае режим называется GCM, то есть Galois/Counter Mode. Работает он следующим образом:

  1. Для генерации достаточно длинной строки двоичных данных перед осуществлением шифрования сообщения используется блочный шифр, шифрующий блоки, содержащие счётчик; и 128-битная строка, необходимая для аутентификации данных.

  2. Производится XOR-выборка данных для шифровки вместе с ранее зашифрованными блоками-счётчиками. На этом шаге происходит шифрование сообщения.

  3. Поверх дополнительных данных, зашифрованных данных и размера обоих вводов применяется "быстрый хеш" под названием GMAC, чтобы сгенерировать 128-битную строку для аутентификации данных. На этом шаге формируется уникальный идентификатор данных.

  4. Идентификатор данных шифруется вместе с оставшимся зашифрованным блоком счётчика. Это предотвращает доступ к данным извне.

Дешифровка производится в обратном порядке с учётом сверки хешей (вычисленного и полученного).

Такой подход имеет множество преимуществ. Во-первых, блоки могут быть легко зашифрованы параллельно просто за счёт разного значения счётчиков. Во-вторых, используемый "хеш" может быть сильно упрощён, потому что он уже уникален за счёт использованного ключа. В-третьих, можно применить особый вид хеша (мультипликативный хеш) который тоже может быть запараллелен. Также для больших объёмов данных обычно требуется только один проход (шифрование вообще обычно ресурсоёмкая задача).

В-общем, этот режим шифрования в достаточной степени безопасен и достаточно быстр, особенно если присутствует его аппаратная поддержка. Это актуально для большинства современных десктопных процессоров и некоторых криптографических ускорителей, но не для большинства мобильных устройств или встроенных решений.

256-битный AES в режиме GCM

Этот шифр представляет собой в точности то же самое, что и описанный выше, вплоть до использования таких же 128-битных тэгов аутентификации, но с более длинными (256 бит) ключами, дабы удостовериться в соответствии уровня безопасности хеш-функции уровню безопасности ключей; при этом в отличие от других режимов используется SHA-384. Более "тяжеловесные" ключи делают этот шифр несколько более медленным, чем 128-битный. С другой стороны, ключи этого размера дают преимущество, будучи безопасными, даже если будет применён (а перед этим создан) достаточно мощный квантовый компьютер.

ChaCha20 с аутентификатором Poly1305

В отличие от шифров, описанных ранее, ChaCha20 представляет собой нечто более схожее с хеш-функцией. В частности, ChaCha20 это алгоритм, получающий на вход 512 бит и выводящий 512 бит способом, который делает крайне сложным определение того, каким был ввод, и который гарантирует, что каждый из выведенных битов испытал влияние каждого бита, поданного на вход. Методика: создание блока с 256-битным ключом, 128-битной константой и 128-битной смесью значения счётчика со значением, которое используется только единожды.

В остальном же этот шифр работает схожим с AES GCM образом. Вместо шифрования счётчиков, оные "хешируются" с помощью ChaCha20 функции. Также применяется несколько отличный от GCM хеш с ключом.

  1. Используется ChaCha20, чтобы "хешировать" блоки, содержащие счётчик, чтобы, в свою очередь, сгенерировать достаточно длинную строку двоичных данных, чтобы (в свою очередь) зашифровать сообщение с использованием 128-битной строки для аутентификации данных.

  2. Производится XOR-выборка данных, которые нужно зашифровать вместе с "хешированными" блоками-счётчиками. На этом шаге происходит шифрование сообщения.

  3. Поверх дополнительных данных, зашифрованных данных и размера обоих вводов применяется "быстрая" "хеш-функция" с ключом (отличная от ChaCha20), чтобы сгенерировать 128-битную строку для аутентификации данных. На этом шаге генерируется уникальный идентификатор данных.

  4. Идентификатор данных шифруется вместе с оставшимся зашифрованным блоком счётчика. Это предотвращает доступ к данным извне.

Но если AES одобрен NIST и в достаточной мере быстр и безопасен, зачем кому-то может понадобиться ещё один шифр? Причина проста: если AES GCM очень быстр при наличии аппаратной поддержки, он может быть не столь быстр, когда её нет. В противоположность ему, ChaCha20 и Poly-1305 созданы, чтобы быть как можно более быстрыми именно на программном уровне. Они медленнее, но всё же достаточно быстры в сравнении с аппаратно ускоренным AES GCM и ОЧЕНЬ быстры на чисто программном уровне. Иными словами, ChaCha20 в совокупности с Poly1305 лучший выбор при отсутствии поддержки на уровне "железа".

128-битный AES в режиме CCM

Как и GCM, ССM представляет собой ещё один режим функционирования блочного шифра AES; его название является сокращением от "счётчик режима CBC-MAC". В свою очередь, CBC-MAC означает Cipher Block Chaining Message Authentication Code. Этот режим сходен с GCM в том, что он использует блок-счётчик вместе с данными в качестве переменной для операции XOR, но применяемая хеш-функция с ключом – особая. В частности, CCM работает следующим образом:

  1. С помощью блочного шифра создаётся "хеш" дополнительных данных, шифруемых данных (да, перед их шифрованием) и размеров обоих вводов. Это делается путём включения некоторых добавочных блоков с информацией о том, как "хеш" функция будет работать и "наполнения" для соответствия желаемому размеру блока. Для каждого блока производится XOR-выборка между ранее зашифрованным блоком и данными этого блока в виде обычного текста, и результат шифруется. Тэг аутентификации в этом случае представляет собой 128-битный результат последнего шифрования.

  2. Для генерации достаточно длинной строки двоичных данных используется блочный шифр, шифрующий блоки, содержащие счётчик; и 128-битная строка, используемая для аутентификации данных.

  3. Производится XOR-выборка данных, которые нужно зашифровать вместе с зашифрованными блоками-счётчиками. На этом шаге происходит шифрование сообщения.

  4. Идентификатор данных шифруется вместе с оставшимся зашифрованным блоком-счётчиком. Это предотвращает доступ к данным извне.

Режим CCM имеет изъяны в своём дизайне, например, нужно знать размер шифруемых данных до начала самого процесса шифрования. CCM также требует произвести два цикла шифрования каждого блока данных и ещё одного на каждый блок для аутентификации, что может приводить к серьёзному падению производительности в сравнении с GCM. Наконец, эквивалент хеша, который использует CCM, в отличие от GCM или ChaCha20-Poly1305 не может быть распараллелен. В остальном, CCM столь же безопасен, как и 128-битный режим GCM.

Почему тогда этот режим вообще был добавлен? Ответ прост: некоторые встраиваемые устройства могут не иметь достаточно ресурсов для реализации AES и аппаратной поддержки хешей GCM. Для этих устройств проведение двух шифрований оказывается быстрее, нежели одно шифрование и одно хеширование с ключом. Также, в отличие от других режимов, предлагающих решения вроде OCB, использование этого не затрагивает никакие патенты.

Так или иначе, в openssl этот режим по умолчанию выключен.

128-битный AES в режиме CCM и 64-битными тэгами аутентификации

Этот режим схож с описанным выше, но использует только 64 бита "хеша", что является весьма нишевым сценарием, при котором допускается бо́льшая вероятность для атакующего модифицировать пакеты (1 из 264 вместо 1 из 2128) в обмен на отправку на 8 байт меньше данных в каждом пакете. Вообще говоря, это имеет смысл только в очень специфических ситуациях для встраиваемых устройств, где даже эти 8 байт решают. На данный момент этот шифр не рекомендуется к использованию и выключен в openssl по умолчанию. Лично я могу рекомендовать его только в том случае, если Вы знаете, что делаете.

Идеальный случай

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

Например, клиент без специфической аппаратной поддержки на первое место поставил бы ChaCha20-Poly1305 и сервер наверняка учёл бы это. С другой стороны, современный десктопный компьютер может избрать и 128-битный AES в режиме GCM, а сервер, не имеющий "железной" поддержки для него всё равно мог бы выбрать ChaCha20-Poly1305. Тот же первый клиент, соединяясь с сервером, имеющим на аппаратном уровне поддержку 128-битного AES в режиме GCM, был бы вынужден использовать именно его.

Иными словами, в случае файлообменной сети было бы так:

Клиент-параноик:
Только TLS_AES_256_GCM_SHA384 и, возможно,TLS_CHACHA20_POLY1305_SHA256 в порядке предпочтения скорости работы.

Клиент без аппаратной поддержки:
TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256 TLS_AES_256_GCM_SHA384 и, возможно,TLS_AES_128_CCM_SHA256именно в таком порядке. В соответствии с требованиями безопасности 256-битный AES может идти в этом списке раньше.

Клиент с аппаратной поддержкой AES:
TLS_AES_128_CCM_SHA256 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256 и TLS_AES_256_GCM_SHA384именно в таком порядке. В соответствии с требованиями безопасности 256-битный AES может идти в этом списке раньше.

Клиент с аппаратной поддержкой AES GCM (например, AES-NI and PCLMULQDQ):
TLS_AES_128_GCM_SHA256 TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 и, возможно,TLS_AES_128_CCM_SHA256именно в таком порядке. В соответствии с требованиями безопасности 256-битный AES может идти в этом списке раньше.

Сервер-параноик:
Только TLS_AES_256_GCM_SHA384 и, возможно,TLS_CHACHA20_POLY1305_SHA256. ChaCha20 выбирается на основании предпочтений клиента или скорости работы сервера.

Сервер без аппаратной поддержки: TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256 TLS_AES_256_GCM_SHA384 и, возможно,TLS_AES_128_CCM_SHA256именно в таком порядке. В соответствии с требованиями безопасности 256-битный AES может идти в этом списке раньше.

Сервер с аппаратной поддержкой только AES:
TLS_CHACHA20_POLY1305_SHA256 в случае предпочтения клиента, или же TLS_AES_128_CCM_SHA256если поддерживается клиентом. Ну или TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256 TLS_AES_256_GCM_SHA384именно в таком порядке. В соответствии с требованиями безопасности 256-битный AES может идти в этом списке раньше.

Сервер с аппаратной поддержкой AES GCM: основывается на предпочтениях клиента. 256-битный AES может иметь приоритет.

Обратите внимание, я намеренно избегаю упоминаний шифра TLS_AES_128_CCM_8_SHA256поскольку если бы его использование было необходимо, это было бы определено заранее.

Итоги представим в виде таблицы:

Клиент\Сервер

Без аппаратной поддержки

Только AES

AES GCM

Без аппаратной поддержки

ChaCha20

ChaCha20

ChaCha20

Только AES

ChaCha20

CCM

CCM

AES GCM

ChaCha20

CCM

GCM

Как видно, выбор всегда останавливается на опции, обеспечивающей лучшую производительность для стороны, испытывавшей бы бо́льшую нагрузку в случае, если бы был выбран более производительный (для одной из сторон) вариант.

В сценарии "клиент-сервер" приоритет производительности сервера может быть критичен. И хотя выгоды GCM могут быть не столь уж существенны (поскольку скорость работы CCM варьируется от реализации к реализации) они могут давать разный результат в зависимости от самого сервиса. Тогда таблица выглядела бы так:

Клиент\Сервер

Без аппаратной поддержки

Только AES

AES GCM

Без аппаратной поддержки

ChaCha20

CCM

GCM

Только AES

ChaCha20

CCM

GCM

AES GCM

ChaCha20

CCM

GCM

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

Выводы

Как было показано, хотя при использовании TLS 1.3 за сервером всегда будет последнее слово в выборе конкретного алгоритма, клиент имеет возможность выразить свои предпочтения. Он должен реализовать эту возможность, чтобы указать на ожидаемый уровень безопасности и производительности, дабы сервер мог произвести информированный выбор.

Tags:
Hubs:
Total votes 7: ↑7 and ↓0 +7
Views 5.6K
Comments Comments 14