Это происходит со мной, что корабль проектирования и написания программного обеспечения cryptographc так, чтобы она не попадала конфиденциальной информации является, в лучшем случае,
черный искусство, и большинство необходимых методов не широко известно, задокументировано, или совместно.
Держу пари, есть тысячи советов и трюки, которые большинство из нас никогда не писали, потому что они являются безобразными деталями, которые не очень
интересно математически. Я хотел бы услышать методы кодирования, что и другие, разработанные для контроля боковых каналов и программное обеспечение для записи, которая является одновременно надежным и не утечки информации.
Вот некоторые из моих.
Во-первых, я пишу модульные тесты для всего. Часто даже прежде чем писать вещи. Удивительно, как часто криптографический код может выглядеть, как это
делать правильные вещи в то время как на самом деле делать что-то очень тонко другое, что вы, вероятно, не заметите было иначе. Как иметь неправильный период для вашей реализации некоторой LFSG на основе вещи, как на Вихрь Мерсенна - это неправильно на один бит, а выход по-прежнему выглядит хорошо, но это будет небезопасно и иметь период путь короче, чем вы ожидали, что дает ваше программное обеспечение годная для использования ошибки. Или проверка отзыва сертификатов, а затем принимать сертификат, который был аннулирован в любом случае.
Или получать шифровать-Расшифровать, который воспроизводит открытый текст, но не производит такой же вывод на тестовых векторов в качестве стандартной реализации вы пытаетесь быть совместимы с (и, если он не производит такой же вывод, весьма вероятно, что это стандартная реализация, что это более безопасный, чем ваш ....) Или найти ключ сервер недоступен, а затем падает обратно на прием сертификата без отображения ошибки он должен был показать, не зная, есть ли хороший сертификат. Во всяком случае, писать много модульных тестов. Мало того, что они делают, что вы знаете, что ваши процедуры делают, они также сказать вам, когда вы что-нибудь сломать.
Сборки отладки вызывают модульные тесты, и они пишут файл результатов тестирования. Файл результаты испытаний, а не исполняемый файл, обычный
Makefile цели, и инструкция Makefile для построения результатов испытаний конца с «кошкой results.txt», который присоединяет вывод результата теста (т.е. пустая строка следует списки любых ошибок модульного тестирования) непосредственно к выходу компиляции, так же как и любым другим ошибки, которые требуют фиксации.
Если я получу выбор об этом, я стараюсь делать всю криптографию в однопоточном, выделенном исполняемом файле, который делает очень мало еще. Это
гораздо легче анализировать и отлаживать однопоточное приложение.
Знайте свои параметры компилятора. Использование диагностических и стандартных по принуждению них тяжело. Используйте те, которые позволяют расширения системы безопасности, характерные для конкретного компилятора, если это помогает (например, канареек стека или опции к нулю всю память, выделенную для вашей программы на выходе), но сделать все возможное, чтобы написать код, который не полагается для своей безопасности только на тех, расширения, потому что рано или поздно кому-то нужно будет скомпилировать его на другой компилятор. Отбросьте любое стандартное непосильным расширение, которые не будут работать (особенно, если они также будут вызывать ошибки) в различных средах.
Я пишу в C, используя стандартные библиотеки и библиотеки bignum GMP. GMP имеет некоторые не очень широко известных примитивы, которые специально для криптографического использования и не оставлять ничего в стеке или в буферах. Они немного медленнее, чем "нормальный" набор вызовов, но это нормально. Стандартная C библиотека в большинстве случаев может доверять, чтобы делать то, что они говорят, и больше ничего. Это своего рода позор, потому что другие языки имеют хорошие средства, меньше "не определено" поведение, и значительно более высокий уровень защиты от программиста ошибок, но нет никакого способа обойти это, потому что AFAIK нет другого языка позволяет мне абсолютно контролировать, когда и будут ли сделаны копии, когда и будет ли запись в переменные на самом деле произошло, и т.д., а также. Который не высокая похвала для C, потому что она по-прежнему трудно и безобразной и ошибок. Но, по крайней мере, это возможно.
В Runtimes, шаблоны и библиотеки, которые приходят с большинством языков (и здесь я включаю C ++) можно доверять, чтобы делать то, что они говорят, но не заходя более миллион строк трудно, тяжело # ifdef'd шаблон код с тонким гребнем Я не могу верить, что они не делают ничего больше. Поэтому я не знаю, как писать безопасное программное обеспечение на этих языках, и быть уверенным, что она не будет течь информации. Они выполняют свои смысловые цели хорошо, но сделать это, оставив копии, а также фрагменты копий, все, что они соприкасаются в своих объектах, в неиспользуемых областях их выделенных буферов, пока эти буферы фактически не используются для чего-то еще, и в стеке.
Много криптографического программного обеспечения делает широкое использование глобальных переменных для чувствительных значений. Они быстро, никогда не получить освобождаться или (случайно) копируются во время выполнения, новые значения всегда переписывают предыдущие значения вместо посадку на новых местах в памяти, возможно, оставив копии старых ценностей где-то, и они избегают indirections, которые могли бы оставить указатели на них валяются , Единственное, что хоть немного тонкий убедившись, что они стираются, как только программа не нуждается в них больше, и снова перед выходом из программы, и это не трудно. Шаблон появляется с выделенной «ластиком» рутиной, которая устанавливает их все байты, считанных из / разработчика / случайному перед выходом из программы. Чтения из / разработчика / случайного не могут быть опущены компилятором, поскольку она является побочным эффектом системного уровня. Но если вы читаете из / разработчика / случайному в буфер, а затем скопировать из буфера чувствительных переменных, компилятор может не учитывать, что, поскольку те записи на мертвые переменных. Что необходимо, чтобы прочитать байты из / разработчика / случайный * непосредственно * в глобальные переменные, по одному за раз, если это необходимо. Это помогает, если вы определить тип одноточечно записи, которая держит их всех; Таким образом, вы можете просто переписать запись вместо того, чтобы делать по одному за раз.
Эта модель прекрасно подходит для глобалов, что вы ясно один или два раза в выполнении программы и снова на выходе из программы, но I / O, даже из / разработчика / случайное, является слишком медленным для использования по возвращении из каждой подпрограммы, которая обрабатывает чувствительные переменные. Я вроде не люблю использовать глобальные переменные. Мне не нравится идея, что каждая часть программы имеет доступ к ним. Но я, конечно, использовал их.
C также дает переменные с «файла» сферы, что является еще одним способом, чтобы иметь что-то, что вы не можете DEALLOCATE или потерять след и позволяет ограничить доступ к конфиденциальным переменных JUST подпрограммы, определенные в одном файле. Это, наверное, лучше, чем картина глобал.
Я использую «летучий» ключевое слово много, для обозначения всех локальных переменных, так что запись в эти переменные никогда не будут пропущены, даже если запись на них последнее, что происходит перед процедурой они выделяемой в прибыли. Я не знаю ни одного другого языка, кроме C, что позволяет это. «Летучий» позволяет мне легко использовать локальные переменные и не оставлять ничего чувствительную на стеке, но не использовать его для чего-нибудь, что это не чувствительны. Например, если вы используете летучий переменную индекса цикла, цикл будет работать медленно, потому что код должен написать эту переменную в кэш каждой итерации, а не только отображение его в регистр. Лучше всего иметь правильное переменное авто, который вы используете для этого.
Очень хорошее решение проблемы состоит в выделении большого буфера «безопасности» в качестве локальной переменной в основной (), и есть программа управления свой собственный стек для чувствительных переменных. Путь, который работает в том, что, когда основной () вызывает ничего, это дает ему указатель в буфер безопасности, и размер буфера. Вызываемая процедура проверяет, что буфер достаточно велик, и использует столько же буфера, как это необходимо для его собственных чувствительных местных литьем указатель на буфер в указатель на тип записи, который содержит его чувствительные местные жители. Если он называет что-нибудь еще, что есть чувствительные локальные переменные, он делает это с указателем просто мимо конца его записи, и размером он получил для буфера безопасности минус размера собственной чувствительных местных жителей записи. Как глобал, перед выходом из программы вы называете «ластик», который переписывает весь буфер байт из / разработчика / случайного.
Преимуществом этого является то, что основная () сохраняет контроль над буфером. Он не делает систему медленнее путь «летучий» делает. И если несколько процедур чтение и запись в буфере, компилятор никогда не может Elide пишет в него - так подпрограммы могут легко и надежно удалить любой чувствительный ВАР, что они * не * передавая обратно к их вызывающему, прежде чем они вернутся. Это, вероятно, безопаснее, чем использование «летучие» локальные переменных, потому что можно забыть очистить чувствительные «летучий», прежде чем вернуться, но это никогда не возможно забыть очистить буфер безопасности перед выходом - это, безусловно, сломать модульные тесты. Недостатком этого является то, что обработка буфера, как правило, боль в @ $$, поэтому я предпочитаю использовать «летучий» вместо этого. Я использую назначенный буфер для очень чувствительных переменных, таких как пароли. Абсолютно не рутина, в любом месте, не получает, чтобы сделать свою собственную копию пароля, который живет за пределами буфера, и основные () пишет более, что, как только ключ установлен.
Я видел криптографическое программное обеспечение, где чувствительные местные жители были выделенными с помощью «статического» ключевого слова, чтобы гарантировать, что локальные переменные с чувствительным
информация не высвобождены, когда функция возвращает. Это предотвращает утечку во время выполнения, и статическими переменных, компилятор не может ОБЫЧНО игнорировать пишет, так что вызываемая подпрограмма обычно может очистить значение своих чувствительных местных жителей, прежде чем вернуться. Но это не техника, которую я доверяю, потому что компиляторы ЗАПРЕЩАЕТСЯ игнорировать конечные операции записи в статических переменных, если он может доказать, что начальные значения статических переменных не имеет значения к подпрограмме, сфера они в, и статические местные жители строго хуже, чем глобал для предотвращения утечки, так как основные () не имеют никакого способа, чтобы перезаписать все те, прежде чем он выйдет. Каждый из них освобождается при выходе из программы, и вы просто не знаете, что это будет следующая программа выделить блок памяти, где они содержались.
Я всегда использую целые числа без знака. На самом деле это то, что я узнал сравнительно недавно, в связи с вопросом, поднятым в списке криптографии. Основные математические операторы (сложение, вычитание, умножение) может переполниться, и переполнение на целых чисел (с обычной сервоприводом семантики, которые могут дать отрицательный результат сложения двух положительных чисел) не определено поведение. Если необходимо использовать целые числа и проверить позже для перелива / опоясывающего, вы должны добавить их, как если бы они были целыми числами без знака, как это:
(Беззнаковое целое) г = (беззнаковое целое) х + (беззнаковое целое) у;
потому что переполнение целых чисел без знака * это * определяется поведение.
Вы не можете даже проверить, если неопределенное поведение произошло, потому что компилятор будет идти, "О, это не может произойти за неопределенное поведение, за исключением, и я могу делать все, что я хочу с неопределенным поведением. Я хочу, чтобы игнорировать его."
Затем он перерезал чеки и все, что зависит от них из вашей программы, как «мертвый код». Таким образом, вы можете иметь целое число, которое на самом деле отрицательный из-за опоясывающим, произошедшими при сложении двух положительных чисел, и программа будет прыгать прямо мимо чек для отрицательного значения, не вызывая его.
Crypto кодирование научил меня использовать несколько другие необычные биты стиля кодирования. В шифровании, мы склонны выделять буферы, перестановки и т.д., которые являются «круглые» числа, как 0x100 байт или 0x10000 16-битовых значений. Поскольку мы используем целые числа без знака в любом случае, индексирование в эти буферы с помощью uint8_t или uint16_t переменных дает нам автоматическую проверку безопасности диапазона. Но это трудно писать «для» петли, что выход, если мы итерация всего буфера, так что вместо «для» петли я, как правило, использовать делать / в то время как петли. Если я хочу сделать что-то вроде инициализации перестановки с каждым значением в uint16_t, например, моя обычная идиома, чтобы написать что-то вроде
кол = 0; делать {
перестановка [число] = кол;
} В то время как (++ кол = 0!);
Это также пример привычке, которая мне подходит, когда в полной на режим паранойя никогда не покидать любую локальную переменную (например, счет) с ненулевым значением, если я могу помочь ему, будь то * я * думаю, что переменная чувствительна или нет , Если я что-то оставить с ненулевым значением на выходе рутинного, я стараюсь, чтобы оставить ту же постоянную величину в нем на каждом выходе.
Так вот несколько бит о написании не герметичный криптографический код. Я был больше озабочен предотвращением утечки данных памяти на основе, очевидно, чем контроль другие побочные каналов, такие как сроки или использование мощности.
Кто-нибудь еще здесь хотели бы поделиться некоторыми из методов, которые они используют?