|
Взаимная проверка подлинности пользователей
Обычно стороны, вступающие в информационный обмен, нуждаются во взаимной аутентификации. Этот процесс выполняется в начале сеанса связи.
Для проверки подлинности применяют следующие способы:
- механизм запроса-ответа;
- механизм отметки времени ("временной штемпель").
Механизм запроса-ответа. Если пользователь A хочет быть уверен, что сообщения, получаемые им от пользователя B, не являются ложными, он включает в посылаемое для B сообщение непредсказуемый элемент - запрос X (например, некоторое случайное число). При ответе пользователь B должен выполнить над этим числом некоторую заранее оговоренную операцию (например, вычислить некоторую функцию f(X)). Это невозможно осуществить заранее, так как пользователю B неизвестно, какое случайное число X придет в запросе. Получив ответ с результатом действий B, пользователь A может быть уверен, что B - подлинный. Недостаток этого метода - возможность установления закономерности между запросом и ответом.
Механизм отметки времени подразумевает регистрацию времени для каждого сообщения. В этом случае каждый пользователь сети может определить насколько "устарело" пришедшее сообщение и не принимать его, поскольку оно может быть ложным.
В обоих случаях для защиты механизма контроля следует применять шифрование, чтобы быть уверенным, что ответ послан не злоумышленником.
При использовании отметок времени возникает проблема допустимого временного интервала задержки для подтверждения подлинности сеанса. Ведь сообщение с "временным штемпелем" в принципе не может быть передано мгновенно. Кроме того, компьютерные часы получателя и отправителя не могут быть абсолютно синхронизированы.
Для взаимной проверки подлинности обычно используют процедуру "рукопожатия", которая базируется на указанных выше механизмах и заключается во взаимной проверке ключей, используемых сторонами. Иначе говоря, стороны признают друг друга законными партнерами, если докажут друг другу, что обладают правильными ключами. Процедуру "рукопожатия" применяют в компьютерных сетях при организации связи между пользователями, пользователем и хост-компьютером, между хост-компьютерами и т.д.
В качестве примера рассмотрим процедуру "рукопожатия" для двух пользователей A и B. Пусть применяется симметричная криптосистема. Пользователи A и B разделяют один и тот же секретный ключ KAB.
- Пользователь A инициирует "рукопожатие", отправляя пользователю B свой идентификатор IDA в открытой форме.
- Пользователь B, получив идентификатор IDA, находит в базе данных секретный ключ KAB и вводит его в свою криптосистему.
- Тем временем пользователь A генерирует случайную последовательность S с помощью псевдослучайного генератора PG и отправляет ее пользователю B в виде криптограммы EKAB(S).
- Пользователь B расшифровывает эту криптограмму и раскрывает исходный вид последовательности S.
- Затем оба пользователя преобразуют последовательность S, используя одностороннюю функцию f.
- Пользователь B шифрует сообщение f(S) и отправляет криптограмму EKAB(f(S)) пользователю A.
- Наконец, пользователь A расшифровывает эту криптограмму и сравнивает полученное сообщение f'(S) с исходным f(S). Если эти сообщения равны, то пользователь A признает подлинность пользователя B.
Пользователь A проверяет подлинность пользователя B таким же способом. Обе эти процедуры образуют процедуру "рукопожатия", которая обычно выполняется в самом начале любого сеанса связи между любыми двумя сторонами в компьютерных сетях.
Достоинством модели "рукопожатия" является то, что ни один из участников связи не получает никакой секретной информации во время процедуры подтверждения подлинности.
Иногда пользователи хотят иметь непрерывную проверку подлинности отправителей в течение всего сеанса связи. Рассмотрим один из простейших способов непрерывной проверки подлинности.
Чтобы отправить сообщение M, пользователь A передает криптограмму EK(IDA, M). Получатель расшифровывает ее и раскрывает пару (IDA, M). Если принятый идентификатор IDA совпадает с хранимым, получатель принимает во внимание это сообщение.
Вместо идентификаторов можно использовать секретные пароли, которые подготовлены заранее и известны обеим сторонам. Продолжение: Протоколы идентификации с нулевой передачей знаний
Литература
- Романец Ю.В., Тимофеев П.А., Шаньгин В.Ф. Защита информации в компьютерных системах и сетях. Под ред. В.Ф. Шаньгина. - 2-е изд., перераб. и доп. - М.:Радио и связь, 2001. - 376 с.: ил.
Защита исходных текстов и двоичного кода
Крупные производители тиражного программного обеспечения отказались от защиты своих продуктов, сделав ставку на их массовое распространение. Пусть лицензионные копии приобретает лишь несколько процентов пользователей, но, если число этих копий измеряется миллионами, даже считанные проценты выливаются в солидную сумму, с лихвой окупающую разработку. Однако для небольших коллективов и индивидуальных программистов такая тактика неприемлема. Им необходимо предотвратить "пиратское" копирование своего продукта.
Предоставление исходных текстов часто является обязательным условием заказчика и закрепляется в контракте. Той же цели добиваются сторонники популярного движения открытых исходных текстов, ратующие за их свободное распространение. Кроме того, исходные тексты могут банальным образом украсть. Поэтому, стоит внимательно отнестись к организации технологической защиты интеллектуальной собственности.
Противодействие изучению исходных текстов
Вообще говоря, открытость исходных текстов - понятие расплывчатое. Вопрос: "Является ли бинарная программа в 1 Kбайт более открытой, чем миллион строк исходников без адекватной инфраструктуры и документации?" В самом деле, мало иметь исходный текст - в нем еще предстоит разобраться. Достаточно удалить все или, по крайней мере, большую часть комментариев, дать переменным и функциям бессмысленные, ничего не говорящие имена, как в программе не разберется и сам автор. А наличие и полнота комментариев к исходному тексту в контракте, как правило, не оговаривается. Получается, что контракт может быть формально выполнен, а предоставленный заказчику исходный текст практически бесполезен. "Практически" не означает "полностью": такой простой прием не позволит обеспечить абсолютную защиту.
Воспрепятствовать анализу можно отделением, абстрагированием алгоритма от языка реализации. Например, реализовать критичные для раскрытия компоненты на машине Тьюринга, а ее поддержку обеспечить на целевом языке. Уровней абстракции может быть несколько - чем их больше, тем труднее осуществлять анализ. Помимо машины Тьюринга для этой цели подходят стрелка Пирса, сети Петри и т.д. Такой подход дает превосходный результат, но требует глубоких математических знаний и значительных накладных расходов - программировать на машине Тьюринга намного сложнее, чем на ассемблере. Отдельный вопрос - эффективность полученной программы. Для многих проектов это неприемлемо, поэтому приходится использовать другие приемы.
Динамическое ветвление
Программу, состоящую из нескольких сотен тысяч строк, невозможно рассматривать как простую совокупность команд. На таком уровне детализации "за деревьями леса не видно". Сначала необходимо проанализировать взаимосвязь отдельных функций друг с другом, выделить интересующие фрагменты и лишь затем изучать реализацию самих функций. Чем выше степень дробления программы, тем труднее анализ. Элементарные функции, состоящие из десятка строк, сами по себе дают мало информации. Для формирования целостной картины необходимо рассмотреть вызывающий их код, поднимаясь по иерархии вызовов до тех пор, пока не удастся реконструировать весь алгоритм целиком или, по крайней мере, охватить его ключевой фрагмент. Чтобы построить дерево вызовов, нужно уметь отслеживать перекрестные ссылки в обоих направлениях: определять, какой функции передается управление данным вызовом, и, наоборот, находить все вызовы, передающие управление данной функции.
Этому легко воспрепятствовать, воспользовавшись динамическим ветвлением - т.е. вычислением адреса перехода непосредственно перед передачей управления. Если функция возвращает не результат своей работы, а указатель на следующую выполняемую функцию (результат работы можно возвратить через аргументы, переданные по ссылке), то статический анализ не позволит определить порядок выполнения программы и построить дерево вызовов станет невозможно. Попутно исчезнет масса дублируемого кода, в частности, станет ненужной проверка результата завершения на корректность - в случае возникновения ошибки функция сама передаст управление нужной ветви программы.
Здесь стоит сделать одну оговорку. Язык Си не позволяет объявить функцию, возвращающую указатель на функции - это объявление рекурсивное. Приходится объявлять функцию, возвращающую бестиповой указатель void *. Аналогично - если функция в ходе своей работы вызывает какие-то другие функции, лучше делать это не напрямую, а передавать указатели на вызываемые функции как аргументы. Такой подход не только препятствует анализу, но еще и увеличивает гибкость программы, облегчая повторное использование старого кода в новых проектах - при должной культуре программирования каждая функция представляет "вещь в себе" и не привязана ко всем остальным.
Контекстная зависимость
Рассмотрим теперь другой прием, когда алгоритм работы большинства функций зависит от флага - глобальной переменной в пределах одного модуля. Если флаги изменяются в зависимости от результата, возвращаемого функцией, автоматически возникает контекстная зависимость (не слишком сильная, но это лучше, чем ничего). Работа одной функции становится зависимой от других, вызванных до нее. Анализ одной, отдельно взятой функции, становится многовариантным. Чтобы определить, что конкретно она делает, необходимо знать значение флагов, а значит - иметь представление о том, какие функции выполнялись до этого и какие именно данные они обрабатывали.
Опять замечание: в многопоточной среде глобальный флаг можно использовать только в том случае, если все потоки явно синхронизованы; в противном случае необходимо предоставить каждому потоку свой собственный экземпляр флага. Глобальный флаг требует особого внимания, малейшая небрежность приводит к трудноуловимым ошибкам. Разобраться в программе с множеством одновременно выполняемых потоков, манипулирующих с одной переменной, невероятно трудно; малейшая невнимательность или излишняя торопливость приводят к ошибкам анализа, затрудняющим понимание сути алгоритма.
Хуки
Хуки ("крючья") - изящный, но ныне практически забытый прием программирования. Его суть заключается в совмещении нескольких разнотипных данных в одном аргументе. Например, если значение аргумента по модулю меньше 0x400000, функция считает его непосредственным значением, в противном случае - указателем на функцию, результат выполнения которой следует поставить на его место. Это увеличивает гибкость программы, но и затрудняет ее анализ, не позволяя быстро определить, происходит ли передача переменной по значению или по указателю. Указатели на переменную, в свою очередь, становятся неотличимы от указателей на функцию. Разумеется, отказ от строгой типизации может приводить к ошибкам, но вероятность их появления в тщательно продуманной программе невелика.
Снова замечание: хуки отрицательно сказываются на переносимости программ, поскольку представление указателей имеет свои особенности на каждой аппаратной платформе. Поэтому использовать их следует только в тех случаях, когда переносимость не требуется, или когда на всех выбранных платформах представление указателей унифицировано.
Существует еще множество других способов запутать того, кто пытается проанализировать исходный текст. Например, можно использовать совершенно корректные с точки зрения языка, но необычные конструкции, ставящие в тупик незнакомого с ними человека. Классический пример - перестановка индекса и имени массива в Си. С точки зрения языка, выражения "buff[666]" и "666[buf]" абсолютно равнозначны, но всякий ли об этом помнит?
Препроцессор Си также имеет свои тонкости. Возможно, самая популярная из них заключается в чувствительности к пробелам при объявлении макроса: "#define x(a,b) a+b" создаст макрос x(a,b), заменяющийся суммой своих аргументов, но "#define x (a,b) a+b" создаст макрос x, заменяющийся последовательностью "(a,b) a+b". Если не обратить внимание на лишний пробел, можно получить совсем не тот результат.
В той или иной степени эти, а также другие приемы используются в большинстве свободно распространяемых "открытых текстов". Последнее словосочетание заключено в кавычки для придания ему ироничного оттенка: одно лишь наличие исходного текста еще не обеспечивает открытости, - требуется, по меньшей мере, грамотно продуманная и качественно составленная документация, а еще лучше - опыт работы с этим текстом. Разобраться с исходными текстами редактора emacs или операционной системы Linux не намного проще, чем написать их "с нуля", - слишком уж скупы их разработчики на комментарии, а документация зачастую и вовсе отсутствует.
В большинстве случаев затраты на анализ чужих исходных текстов сравнимы или даже превышают стоимость заложенных в них алгоритмов (если стоящие алгоритмы там вообще есть), а их модификация просто убийственное занятие: внесенные изменения способны порождать ошибки в непредсказуемых местах и в непредсказуемом количестве. Словом, разумнее было бы говорить о закрытых исходных текстах.
Не нашли, что искали? Воспользуйтесь поиском по сайту:
©2015 - 2024 stydopedia.ru Все материалы защищены законодательством РФ.
|