Хабрахабр

Стэн Драпкин. Ловушки высокоуровневой криптографии в .NET

Стэн Драпкин — эксперт по безопасности и комплаенсу, имеющий более чем 16 лет опыта работы с .NET Framework (начиная с .NET 1.0-beta в 2001 году). К сожалению, сам он не пишет статьи на русском языке, поэтому мы договорились с ним выпустить перевод его доклада с DotNext Piter. Этот доклад занял первое место на конференции!

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

Приятного прочтения!
Под катом — видео, слайды и перевод.

Слайды

Кроме того, я — автор нескольких опенсорсных библиотек, весьма неплохо принятых сообществом. Меня зовут Стэн Драпкин, я технический директор фирмы, специализирующейся на информационной безопасности и нормативно-правовом соответствии. В этой библиотеке продемонстрирован правильный подход к криптографии в . Кто из вас слышал об Inferno? NET. NET, а в TinyORM реализован микро-ORM для . Одна из них, 2014 года издания — «Security Driven . Кроме того, я написал несколько книг, которые могут иметь отношение к теме сегодняшней статьи. NET, Succinctly». NET», другая, 2017 года — «Application Security in .

Затем последуют две основные темы, в первой речь пойдет о симметричной криптографии, во второй — об асимметричной и гибридной. Вначале мы поговорим о том, что я называю четырьмя стадиями крипто-просветления. Во второй части нас ожидает множество «приключений» с RSA, после чего мы познакомимся с современной эллиптической криптографией. В первой части мы сравним высокоуровневую и низкоуровневую криптографию и взглянем на пример потоковой криптографии (streaming cryptography).

Первая стадия — «XOR такой классный, смотри, мама, как я могу!» Наверняка многие из вас с этой стадией знакомы и знают о чудесах функции XOR. Итак, как выглядят эти стадии крипто-просветления? Большинство разработчиков, не посещающих DotNext, находится именно на этой стадии. Но, я надеюсь, большинство эту стадию переросло и перешло на следующую, то есть научилось выполнять шифрование и дешифрование при помощи AES (Advanced Encryption Standard), широко известного и высоко оцененного алгоритма. Ну, и для полноты картины я упомяну также последнюю стадию — понимание того, что при наилучшем решении проблемы криптография может быть и вовсе не нужна. Но, коль скоро вы следите за DotNext и знакомы с докладами об опасностях низкоуровневых API, вы, скорее всего, находитесь на следующей стадии — «я всё делал(а) неправильно, нужно переходить на высокоуровневые API». В качестве примера можно привести Питера Ньюмана (Peter G. Дойти до этой стадии сложнее всего, и людей на ней находится немного. Neumann), который сказал следующее: «Если вы считаете, что решение вашей проблемы кроется в криптографии, то вы не понимаете, в чем именно состоит ваша проблема».

NET. О том, что низкоуровневая криптография опасна, говорилось уже на многих докладах о . Security. Можно сослаться на доклад Владимира Кочеткова 2015 года, «Подводные камни System. Основная его мысль в том, что на каждом этапе работы с низкоуровневыми криптографическими API мы, сами того не зная, принимаем множество решений, для многих из которых у нас просто нет соответствующих знаний. Cryptography». Это замечательный вывод, но он приводит нас к другой проблеме — знаем ли мы, как именно должна выглядеть высокоуровневая криптография? Основной вывод — в идеале вместо низкоуровневой криптографии следует использовать высокоуровневую. Давайте немного поговорим об этом.

Для начала, такой API не будет производить впечатление родного для . Определим признаки не-высокоуровневого криптографического API. Далее, такой API легко будет использовать неправильно, т.е. NET, скорее он будет похож на низкоуровневую оболочку. Кроме того, он будет заставлять вас генерировать множество странных низкоуровневых вещей — nonce-ов, векторов инициализации и тому подобного. не так, как следует. У него также не будет правильного API для потоков (streaming API) — о том, как последний должен выглядеть, мы еще поговорим. Такой API будет вынуждать вас принимать неприятные решения, к которым вы можете быть не готовы — выбирать алгоритмы, режимы дополнения (padding modes), размеры ключей, nonce-ы и так далее.

Я считаю, что он в первую очередь должен быть интуитивно понятным и лаконичным как для читающего код, так и для пишущего его. В противоположность этому, как должен выглядеть высокоуровневый криптографический API? Он также должен быть мощным, то есть должен позволять нам достигать нашей цели ценой небольших усилий, небольшого количества кода. Далее, такой API должен быть прост в освоении и использовании, и его должно быть крайне сложно применить неверным образом. Наконец, у такого API не должно быть длинного списка ограничений, предостережений, особых случаев, в целом — должен быть минимум вещей, о которых необходимо помнить при работе с ним, иначе говоря — он должен характеризоваться низким уровнем помех (low-friction), должен просто работать безо всяких оговорок.

NET, как нам его теперь найти? Разобравшись с требованиями к высокоуровневому криптографическому API для . Поэтому мы исследуем эту проблему и опробуем различные альтернативные варианты. Можно попробовать просто загуглить, но это было бы слишком примитивно — мы с вами профессиональные разработчики, и это не наш метод. Они следующие: открытый текст P (plaintext), который мы преобразуем в шифротекст C (ciphertext) той же длины при помощи некоторого секретного ключа K (key). Но для этого нам надо вначале составить для себя правильное представление о том, что такое аутентифицированное шифрование (Authenticated Encryption), а для этого необходимо разобраться с основными понятиями. Кроме того, у нас также есть тег аутентификации T и nonce N. Как видим, пока что мы работаем с очень простой схемой. Как многие из вас, наверное, знают, оно ведет к нарушению конфиденциальности текста, что, очевидно, нежелательно. Важным параметром является N̅, то есть повторное использование nonce-ов с одним ключом. Это необязательные данные, которые аутентифицированы, но не участвуют в шифровании и дешифровании. Другое важное понятие — AD (Associated Data), то есть ассоциированные данные.

NET. Разобравшись с основными понятиями, взглянем на различные варианты криптографических библиотек для . NET. Кто из вас знаком с ней? Начнем с анализа Libsodium. Как вижу, некоторые знакомы.

nonce = SecretAeadAes.GenerateNonce();
c = SecretAeadAes.Encrypt(p, nonce, key, ad);
d = SecretAeadAes.Decrypt(c, nonce, key, ad);

Перед вами код на C#, при помощи которого выполняется шифрование с Libsodium.NET. На первый взгляд он достаточно прост и лаконичен: в первой строке генерируется nonce, который затем используется во второй строке, где происходит собственно шифрование, и в третьей, где текст расшифровывается. Казалось — какие тут могут быть сложности? Для начала, в Libsodium.NET предлагаются не один, а три различных метода симметричного шифрования:

Раз

nonce = SecretAeadAes.GenerateNonce();
c = SecretAeadAes.Encrypt(p, nonce, key, ad);
d = SecretAeadAes.Decrypt(c, nonce, key, ad);

Два

nonce = SecretAead.GenerateNonce();
c = SecretAead.Encrypt(p, nonce, key, ad);
d = SecretAead.Decrypt(c, nonce, key. ad);

Три

nonce = SecretBox.GenerateNonce();
c = SecretBox.Create(p, nonce, key);
d = SecretBox.Open(c, nonce, key);

Очевидно, встает вопрос — который из них лучше в вашей конкретной ситуации? Чтобы на него ответить, необходимо залезть внутрь этих методов, что мы сейчас и сделаем.

Важно, что у него достаточно длинный список ограничений. Первый метод, SecretAeadAes, использует AES-GCM с 96-битным nonce-ом. Причем библиотека не предупреждает о приближении к этим ограничениям, отслеживать их необходимо вам самостоятельно, что создает дополнительную нагрузку на вас как на разработчика. К примеру, при использовании него не следует шифровать больше 550 гигабайт одним ключом, и не должно быть больше 64 гигабайт в одном сообщении при максимуме в 232 сообщений.

Такой маленький nonce делает коллизии крайне вероятными, и уже только на этом основании этим методом не стоит пользоваться — за исключением достаточно редких случаев и при условии, что вы очень хорошо разбираетесь в теме. Второй метод, SecretAead использует другой комплект шифров, ChaCha20/Poly1305 со значительно меньшим, 64-битным nonce-ом.

Здесь нужно сразу заметить, что в аргументах для этого API отсутствуют ассоциированные данные. Наконец, третий метод, SecretBox. Алгоритм шифрования, используемый здесь, называется xSalsa20/Poly1305, nonce достаточно большой — 192 бита. Если вам необходимо аутентифицированное шифрование с AD, этот метод вам не подойдет. Все же, отсутствие AD является существенным ограничением.

NET возникают некоторые вопросы. При использовании Libsodium. Библиотека ничего нам об этом не говорит, разобраться в этом нам предстоит самостоятельно. Например, что именно мы должны сделать с nonce-ом, генерируемым первой строкой кода в приведенных выше примерах? Далее, у нас могло сложиться впечатление, что AD в первых двух методах могут быть любой длины. Скорее всего, мы будем вручную добавлять этот nonce в начало или в конец шифротекста. Идём дальше. Но на самом деле библиотека поддерживает AD длиной не более 16 байт — ведь 16 байт хватит на всех, правда? В данной библиотеке было принято решение в этих случаях бросать исключения. Что происходит при ошибках дешифрования? Что делать, если размер вашего ключа не равен в точности 32 байтам? Если в вашей среде при раcшифровке может быть нарушена целостность данных, то у вас будет возникать множество исключений, которые нужно будет обрабатывать. Другая важная тема — повторное использование массивов байтов с целью уменьшить нагрузку на сборщик мусора в интенсивных сценариях. Библиотека ничего нам об этом не сообщает, это ваши проблемы, которые её не интересуют. Хотелось бы не создавать каждый раз новый буфер, а переиспользовать существующий. Например, в коде мы видели массив который нам вернул генератор nonce-ов. В данной библиотеке это невозможно, массив байтов будет каждый раз генерироваться заново.

NET. Используя уже виденную нами схему, попробуем сравнить различные алгоритмы Libsodium.

Он меньше 128 бит, что создаёт некоторый дискомфорт, но не слишком существенный. Первый алгоритм — AES-GCM, использует nonce длиной в 96 бит (жёлтый столбец на картинке). Вторая синяя цифра, в скобках, означает количество энтропии, или случайности, содержащееся в этом теге — меньше 128 бит. Следующий столбец — синий, это место, занимаемое тегом аутентификации, у AES-GCM оно равно 16 байт или 128 бит. Чем больше шифруется, тем тег становится слабее. Насколько меньше — в данном алгоритме зависит от того, какой объём данных шифруется. Там написано, что повторы (коллизии) nonce-ов приведут к подделке всех шифротекстов, созданных тем же самым ключом. Уже это должно породить у нас сомнения в данном алгоритме, которые только усилятся, если мы взглянем на белый столбец. Это весьма существенное ограничение. Если из, скажем, 100 ваших шифротекстов созданных общим ключом в двух есть коллизия nonce-а, этот nonce приведёт к внутренней утечке ключа аутентификации и позволит злоумышленнику подделать любой другой шифротекст, созданный данным ключом.

NET. Перейдём ко второму методу Libsodium. Тег занимает 128 бит, но содержит только 106 бит энтропии или меньше, иначе говоря, существенно ниже уровня безопасности в 128 бит, которого в большинстве случаев пытаются достичь. Как я уже говорил, здесь для nonce-а используется слишком небольшое пространство, всего 64 бита. Коллизия nonce-ов приводит к подделке шифротекстов, но только для тех блоков, в которых коллизии произошли. Что касается подделывания, то здесь ситуация несколько лучше, чем в случае с AES-GCM. В предыдущем примере у нас оказались бы подделанными 2 шифротекста, а не 100.

Способ аутентификации здесь тот же, что и в прошлом методе, поэтому тег опять-таки занимает 128 бит и имеет 106 бит энтропии или меньше. Наконец, в случае с алгоритмом xSalsa/Poly мы имеем очень большой nonce размером в 192 бита, благодаря чему коллизии оказываются крайне маловероятными.

В ней nonce занимает колоссальное пространство, 320 бит, что делает коллизии практически невозможными. Сравним все эти цифры с соответствующими показателями библиотеки Inferno. Это — пример надёжного и безопасного подхода. Что касается тега, то с ним в ней всё просто: он занимает ровно 128 бит и имеет ровно 128 бит энтропии, не меньше.

NET, нам надо понять его предназначение — к сожалению, его осознают далеко не все, кто пользуется этой библиотекой. Прежде, чем более подробно знакомиться с Libsodium. NET является оберткой на С# для libsodium. Для этого необходимо обратиться к её документации, в которой указано, что Libsodium. Что же, обращаемся к документации NaCl, еще одного опенсорсного проекта. Это другой опенсорсный проект, в документации которого написано, что он является форком NaCl с совместимым API. Именно здесь зарыта собака: задачей NaCl и всех его оболочек является предоставить низкоуровневые элементы, из которых затем кто-то другой уже сможет собрать высокоуровневые криптографические API. В ней в качестве цели NaCl постулируется предоставить все необходимые операции для создания высокоуровневых криптографических инструментов. Отсюда мораль: если вам необходим высокоуровневый криптографический API, вам надо найти высокоуровневую библиотеку, а не пользоваться низкоуровневой оберткой и делать вид, что работаете с высокоуровневой. Сами эти оболочки как высокоуровневые библиотеки не задумывались.

Рассмотрим, как работает шифрование в Inferno.

В качестве аргументов используется ключ, текст и необязательные ассоциированные данные. Перед вами пример кода, в котором, как и в случае с Libsodium, каждое шифрование и дешифрование занимает всего одну строчку. Поскольку создание исключений существенно увеличивает нагрузку на сборщик мусора, их отсутствие весьма важно для сценариев с обработкой больших потоков данных. Следует обратить внимание, что здесь нет nonce-ов, нет необходимости принимать какие-либо решения, в случае ошибки дешифрования просто возвращается null, без всякого бросания исключений. Надеюсь, мне удалось вас убедить в том, что такой подход является оптимальным.

Это должен быть наиболее простой сценарий, реализовать который могут все. Из интереса давайте попробуем зашифровать какую-нибудь строку. Предположим, что у нас возможны только два различных значения строки: «LEFT» и «RIGHT».

Мы шифруем две строки одним ключом и получаем два шифротекста, c1 и c2. На картинке вы видите шифрование этих строк при помощи Inferno (хотя для данного примера не важно, какая именно библиотека используется). Является ли он готовым к продакшну? Всё ли в этом коде верно? Я имею в виду нечто иное: при обычных криптографических подходах c1 в нашем примере будет короче c2. Кто-то может сказать, что возможна проблема в коротком ключе, но она далеко не главная, так что будем считать, что ключ используется один и тот же, и он достаточной длины. Это может позволить злоумышленнику понять, какая именно строка представлена данным шифротекстом, «LEFT» или «RIGHT». Это называется length leaking («утечка по длине») — c2 во многих случаях будет на один байт длиннее c1. Проще всего решить эту проблему можно, если сделать так, чтобы обе строки имели одинаковую длину — например, добавить символ в конец строки «LEFT».

Но в январе 2018 года в журнале Wired вышла статья с исследованием, осуществленным израильской компанией Checkmarx, под заголовком «Отсутствие шифрования в Tinder позволяет посторонним отслеживать, когда вы проводите по экрану». На первый взгляд length leaking воспринимается как несколько надуманная проблема, которая в реальных приложениях встретиться не может. Tinder — это приложение, которое получает поток с фотографиями, и затем пользователь проводит по экрану направо или налево, в зависимости от того, нравится ему фото или нет. Я коротко перескажу содержание, но сначала грубое описание функционала Tinder. Это, конечно, уязвимость, но сама по себе она не слишком существенная. Исследователи обнаружили, что, хотя сами команды были правильно зашифрованы с использованием TLS и HTTPS, данные команд для проведения направо занимали иное количество байт, чем данные для проведения налево. Так что злоумышленник мог получить доступ не только к реакциям пользователей на фотографии, но и к самим фотографиям. Более существенным для Tinder был тот факт, что сами потоки с фотографиями они отправляли через обычный HTTP, без всякого шифрования. Так что, как видим, length leaking является вполне реальной проблемой.

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

Он очень простой, здесь мы видим один поток для исходного файла и другой для файла назначения, а также криптопоток, который преобразует первый во второй. Выше вы видите пример, взятый практически без изменений из MSDN. Итак, перед нами простое и в то же время полностью работающее и проверенное решение для шифрования потока. В этом коде Inferno используется только в одной строчке — в той, где происходит преобразование.

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

AD могут быть «слабыми»: это значит, что они проходят аутентификацию, но в самом процессе шифрования и дешифрования никак не участвуют. Нам также следует поговорить об аутентификации ассоциированных данных, поскольку это тема, которая освещается не часто. В большинстве известных мне библиотек AD слабые, в то время как в Inferno используется второй подход, там AD используются в самом процессе шифрования/дешифрования… Напротив, «сильные» AD изменяют сам этот процесс.

Если коротко, мой ответ такой: 256-битное шифрование со 128-битным тегом аутентификации. Следует также остановиться на том, к какому уровню безопасности должна стремиться высокоуровневая криптография. Тому есть множество причин, каждая из которых значима сама по себе, но сейчас мне хотелось бы, чтобы вы запомнили одну: нам необходимо предохраниться от возможных предвзятостей (bias) при генерировании криптографических ключей. Почему необходим настолько большой ключ? У генератора случайных битов без предвзятостей для каждого бита вероятности принять значение 0 или 1 равны. Поясню, что имеется ввиду под предвзятостью. На первый взгляд, эта предвзятость небольшая, но на самом деле она значительна: 25%. Но предположим, что у нашего генератора бит будет принимать значение 1 с вероятностью 56%, а не 50%. Попробуем теперь подсчитать, сколько энтропии мы получим при генерировании определённого количества бит нашим генератором.

Важно, что в ней только две переменных: предвзятость, о которой мы уже говорили (bias), и количество бит, создаваемое генератором. На картинке вы видите формулу, по которой будет производиться этот расчёт. Как бы то ни было, при 25% предвзятости и 128-битном ключе мы получим всего 53 бита энтропии. Будем считать, что предвзятость равна 25% — это достаточно экстремальный случай, на практике вы, скорее всего не будете работать в системах с настолько искажённым генератором случайных чисел. Но если вместо 128-битного ключа мы используем 256-битный, то получаем 106 бит энтропии. Во-первых, это существенно меньше 128 бит, которые обычно ожидаются от генератора случайных чисел, во-вторых, при современных технологиях такой ключ можно просто брутфорсить. С современными технологиями взломать такой ключ практически невозможно. Это уже весьма неплохо, хоть и меньше ожидаемых 256.

Я рекомендую всем пользоваться хорошо написанными криптографическими API. В завершение первой части доклада подведу промежуточные итоги. Кроме того, при выборе API следует обращать внимание на наличие поддержки работы с потоками. Найдите тот, который вас устроит, или отправьте петицию в Microsoft, чтобы они вам его написали. Наконец, следует иметь в виду, что высокоуровневая криптография, как и любая другая, не идеальна. По уже объяснённым причинам минимальная длина ключа должна быть 256 бит. Утечки могут происходить, и в большинстве сценариев об их возможности необходимо помнить.

Поставлю вопрос с подвохом: умеете ли вы пользоваться RSA в . Давайте теперь поговорим об ассиметричной, или гибридной криптографии. Не торопитесь отвечать утвердительно, как это делают многие — давайте сначала проверим ваши знания в этой области. NET? Но вначале обратимся к википедии, и вспомним, чем именно является RSA — на случай, если кто-то забыл или давно не пользовался этим алгоритмом. Следующие слайды будут специально предназначены для людей, уже знакомых с этой темой.

Далее, существует некоторый Боб, который хочет зашифровать сообщение для Алисы: «Привет, Алиса!» При помощи её публичного ключа он генерирует шифротекст, который затем отправляет ей. Предположим, существует некоторая Алиса, которая при помощи генератора случайных чисел создает пару ключей, в которую входит один частный и один публичный. Расшифровывает она этот шифротекст с помощью частной части своего ключа.

Попробуем воспроизвести этот сценарий на практике.

Сразу же обращаем внимание, что . Как видно выше, мы создаём экземпляр RSA и зашифровываем некоторый текст. Их существует пять, все с непонятными именами. NET заставляет нас выбрать режим дополнения (padding mode). Воспользуемся одним из двух оставшихся — OaepSHA1. Если мы попробуем их все по очереди, то выясним, что последние три попросту бросают исключение и не работают. Поэтому нам придётся выставить размер ключа вручную. Здесь ключ будет размером в 1 килобит, что слишком мало для RSA, это практически взломанный ключ. KeySize, которое получает или задаёт размер ключа. Из документации мы узнаём, что есть специальное свойство .

KeySize = 3072. На первый взгляд это ровно то, что нам необходимо, поэтому мы пишем: rsa. Не важно, будем мы проверять этот параметр при помощи метода WriteLine(rsa. Но если, руководствуясь смутным подозрением, мы после этого проверим, чему теперь равен размер ключа, то узнаем, что он по-прежнему занимает 1 килобит. ExportParameters(false). KeySize), или rsa. Length * 8 — в последнем случае экспортируется публичный компонент ключа RSA, для этого нужен аргумент «false». Modulus. Как видим, этот алгоритм отправлять в продакшн ещё рановато. Modulus этого ключа является массивом, который мы умножаем на 8 и получаем размер в битах — который опять-таки будет 1 килобит.

NET 4. Не будем тратить время на выяснение того, почему этот API не работает, вместо этого попробуем другую реализацию RSA, предоставленную Microsoft в . Она называется RSACng, причём Cng расшифровывается как Cryptography next generation («следующее поколение криптографии»). 6, то есть совсем новую. Наверняка здесь мы найдем волшебное решение всех наших проблем. Великолепно, кто же не хочет работать с инструментами следующего поколения?

KeySize) — и вновь узнаём, что размер ключа по-прежнему равен одному килобиту. Мы запрашиваем экземпляр RSACng, вновь задаём размер ключа в 3 килобита, вновь проверяем размер ключа через WriteLine(rsa. Я просто хочу поделиться здесь моим личным ощущением отчаяния, и завопить: «За что, Microsoft?!». Вдобавок, если мы запросим тип объекта, сгенерировавшего ключ — как помним, мы запросили экземпляр RSACng — то узнаем, что это RSACryptoServiceProvider.

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

Что ещё лучше — здесь нам удаётся, наконец, задать размер ключа в 3 килобита. Здесь значение размера ключа по умолчанию 2048 бит, что уже значительно лучше. Как говорится, achievement unlocked.

По-прежнему остаются вопросы, на которые нам предварительно нужно ответить. Напомню, что все наши старания до сих пор сводились только к созданию RSA, к шифрованию мы ещё даже не приступали. Реализация фабрики RSA может быть переопределена в machine.config, следовательно, она может измениться без вашего ведома (например, её может поменять системный администратор). Для начала, в какой степени можно полагаться на размеры ключей по умолчанию? Таким образом, вы никогда не должны доверять значениям, предоставляемым по умолчанию, размер ключа всегда нужно выставлять самостоятельно. А это значит, что может поменяться и размер ключа по умолчанию. В . Далее, насколько хороши предоставляемые по умолчанию размеры ключей RSA? В первой размер по умолчанию 1 килобит, во второй два. NET существует две реализации RSA, одна на основе RSACryptoServiceProvider, другая на основе RSACng. Я заранее извиняюсь за поднятие больной темы, но мы не будем обсуждать Bitcoin или криптовалюту, речь пойдёт только о самой сети. Давайте из интереса сравним эти значения с имеющимися в сети Bitcoin (BCN). Это эквивалентно 290 хэшам в год. У неё есть публикуемый hashrate, который растёт с каждым месяцем и на сегодняшний день равен 264 хэшам в секунду. Если вы читаете книги по криптографии, написанные действительными профессионалами, а не людьми вроде меня, то вы знаете, что 270 операций (то есть одной минуты BCN) достаточно, чтобы взломать 1-килобитный ключ RSA, а 290 (одного года BCN) — чтобы взломать 2-килобитный ключ. Предположим для простоты, что хэш является эквивалентом базовой операции — хоть это и не вполне верно, он более сложен. Именно поэтому я настоятельно рекомендую всегда самостоятельно задавать размер ключа, и делать его по меньшей мере размером в 3 килобита, а если вам позволяет производительность — то и 4. Оба значения должны вызывать у нас тревогу — это то, чего можно достичь при уже существующих технологиях.

NET не так просто разобраться, как экспортировать публичные и частные ключи. В .

Ниже показан код, при помощи которого извлекается публичный и частный ключ из обоих экземпляров. В верхней части слайда вы видите два экземпляра ключа RSA, первый от RSACryptoServiceProvider, второй от RSACng, каждый по 4 килобита. Далее, если мы сравним размеры публичных ключей у первого и второго экземпляра, то увидим, что они сравнимы, примерно по полкилобайта каждый. Здесь следует обратить внимание на то, что оба API достаточно сильно отличаются друг от друга — разный код, разные методы, разные параметры. Это необходимо иметь ввиду и соблюдать единообразие, не мешать эти два API друг с другом. Но вот частный ключ у новой реализации RSA значительно меньше, чем у старой.

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

Но на этот раз исключение у нас возникнет. Создадим массив байтов, который будет нашим открытым текстом (data), а затем зашифруем его при помощи одного из тех режимов дополнения, которые не бросали исключение. Я понятия не имею — и Microsoft, скорее всего, тоже. Это исключение неверного параметра; но о каком параметре идёт речь? Так что дело не в режиме дополнения. Если мы попробуем запустить тот же метод с другими режимами дополнения, то в каждом случае получим то же исключение. Сложно сказать, что именно с ним неверно, так что попробуем на всякий случай урезать его в два раза. Значит, проблема в самом исходном тексте. Мы остаёмся в недоумении. На этот раз шифрование проходит успешно.

SHA-1, как мы знаем, уже не является криптографически-сильной функцией, поэтому наши аудиторы и отдел нормативно-правового соответствия (compliance department) настаивают на том, чтобы мы от неё избавились. Возможно, всё дело в том, что мы использовали дополнение SHA-1? Заменим OaepSHA1 на OaepSHA256, по меньшей мере это успокоит аудиторов.

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

Она должна находиться в методе int GetMaxDataSizeForEnc(RSAEncryptionPadding pad), который рассчитывает этот объём, получив на вход режим дополнения. Попытаемся выяснить, как именно выглядит та волшебная формула, которая определяет максимальный объём шифруемых данных. Я пытаюсь донести мысль о том, что даже самая основная информация, необходимая разработчику для правильного использования RSA, нам недоступна. Главный недостаток этого метода заключается в том, что он не существует, я его выдумал. Спасибо, Microsoft.

Как, я надеюсь, мне удалось показать, API для RSA в . Приведу причины, по которым RSA следует избегать, даже для подписи. Вас заставляют принимать множество решений относительно режима дополнения, размера данных и тому подобного, что нежелательно. NET крайне неудовлетворительны. Он даст вам частный ключ размером в килобайт, публичный ключ размером в пол-килобайта, и сигнатуру размером в пол-килобайта. Далее, для 128-битного уровня безопасности вам понадобится по меньшей мере весьма громоздкий 4-килобитный ключ. А если вы попытаетесь достичь 256-битного уровня безопасности, вам понадобится и вовсе огромный ключ — 15360 бит. Для многих сценариев такие значения могут быть нежелательны. На моём ноутбуке один такой ключ генерируется полторы минуты. В RSA использование подобного ключа практически невозможно. Почему нам важна скорость подписи? В дополнение к этому RSA на фундаментальном уровне, как алгоритм, весьма медленно осуществляет подпись, независимо от реализации. А нас, как разработчиков, в наибольшей степени касается именно то, что происходит на сервере, мы за него ответственны, нам важна его пропускная способность. Если вы используете TLS с сертификатами RSA, то подпись осуществляется на сервере. Резюмируя, я хочу ещё раз порекомендовать не пользоваться RSA.

Я хотел бы познакомить вас с современными эллиптическими криптографическими примитивами. В таком случае, чем заменить RSA? В этой и следующих аббревиатурах EC — общий префикс, который расшифровывается как Elliptic-Curve («эллиптический»). В первую очередь вам следует иметь ввиду алгоритм ECDSA (Digital Signature Algorithm, «алгоритм цифровой подписи»), который можно использовать вместо RSA для подписей. NET. По ссылке securitydriven.net/inferno/#DSA Signatures можно найти пример кода ECDSA, который, кстати говоря, является родным для . Этот алгоритм может выполнять гибридное шифрование вместо RSA, то есть такое, где вы генерируете симметричный ключ, шифруете ими данные и затем шифруете сам ключ. Другой важный алгоритм — ECIES (Integrated Encryption Scheme, «эллиптическая интегрированная схема шифрования»). Наконец, ещё один очень важный алгоритм — ECDH (Diffie-Hellman key exchange, «обмен ключей Диффи-Хеллмана»). Пример кода доступен по ссылке securitydriven.net/inferno/#ECIES example. В некоторых ситуациях и способах использования он позволяет добиться прямой секретности (forward secrecy). Он позволяет создавать ключи для симметричного шифрования между двумя сторонами с известными публичными ключами. По ссылке securitydriven.net/inferno/#DHM Key Exchange доступен пример кода.

Всегда следует пользоваться высокоуровневыми API, не заставляющими вас принимать решения, к которым вы не готовы. Подведём итоги разговору об асимметричном шифровании. Конечно, это проще сказать, чем сделать, поскольку мы все работаем с большими уже созданными приложениями, провести полный рефакторинг которых может быть невозможно. Я также рекомендовал бы перестать пользоваться RSA. Далее, я советую познакомиться с современными эллиптическими криптографическими алгоритмами (ECDSA, ECDH, ECIES). В этом случае по меньшей мере необходимо научиться правильно использовать RSA. Приведу цитату со StackOverflow, с которой я полностью согласен: «Криптография сама по себе не решает проблем. Наконец, важно, что высокоуровневая криптография не решает волшебным образом все проблемы, поэтому необходимо помнить о целях, которые вы преследуете. Симметричное шифрование только превращает проблему конфиденциальности данных в проблему управления ключами».

Существует относительно приемлемая высокоуровневая библиотека SecurityDriven. Скажу пару слов о ресурсах, которые, возможно, будут вам полезны. Есть прекрасная книга «Серьёзная криптография» Жана-Филиппа Омассана (Jean-Philippe Aumasson, Serious Cryptography). Inferno с неплохой документацией. Кроме того, мною была написана уже упомянутая книга Application Security in . В ней сделан обзор современного состояния криптографии с учётом последних новшеств. В ней есть ещё больше информации о ловушках безопасности в . NET, Succinctly, которая находится в открытом доступе. Наконец, на Slideshare есть прекрасная презентация Владимира Кочеткова, которая несколько упрощённо, но очень добротно излагает основы теории безопасности приложений и объясняет различные источники опасностей. NET.

В самом начале я говорил про четвёртую стадию крипто-просветления, в которой приходит осознание, что наилучшее решение может вовсе не нуждаться в криптографии. В качестве заключения давайте посмотрим несколько дополнительных примеров, которые я подготовил. Взглянем на классический механизм . Давайте рассмотрим пример такого решения. В данной модели у нас есть агент пользователя — как правило, браузер. NET — CSRF (Cross-Site Request Forgery, «межсайтовая подделка запросов»), предназначенный для защиты от класса атак, в том числе от межсайтовой подделки запросов. В ответ сервер присылает токен CSRF, который скрывается в поле HTML «hidden». Он пытается наладить связь с сервером, отправляя запрос GET. Пользователь обрабатывает некоторую форму и выполняет POST, который возвращается на сервер с обоими токенами. Кроме того, тот же токен прикрепляется к ответу в качестве cookie-файла, в качестве заголовка. Именно это сравнение на идентичность позволяет серверу защититься от злоумышленника. Сервер проверяет, во-первых, присланы ли оба токена, и, во-вторых, совпадают ли они. NET и ASP. Это классический механизм, встроенный в ASP. Михаил Щербаков сделал прекрасный доклад, в котором работа CSRF была исследована во всех подробностях. NET Core.

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

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

Вы прочитали перевод доклада с конференции DotNext. Минутка рекламы. Меньше месяца осталось до следующего DotNext 2018 Moscow — он пройдёт 22-23 ноября 2018 в Конгресс-парке «Рэдиссон Ройал Москва».

В четверг, первого ноября, произойдёт последнее обновление цен. Очень важная новость. Сделать это можно на официальном сайте конференции.
У вас ещё есть возможность купить билеты по октябрьским ценам!

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть