Используйте правильные инструменты

Рассмотрим следующую схему:

У меня есть клиентское приложение, которое я продаю. При первом запуске приложения, оно получает «маркер» от пользователя. Строковым маркером может быть все, что угодно: пользователь может выбрать свое имя, имя своей кошки, свой пароль, содержимое некоторого файла на диске, в общем, что угодно. Каким именно образом программа получает этот маркер от пользователя – не очень важно; возможно, он вводится с клавиатуры, возможно, устанавливается значением ключа реестра, но, независимо от способа ввода, пользователь сам выбирает идентифицирующую его информацию.

В любом случае, в момент первого запуска, приложение получает маркер и связывается с моим сервером через интернет. Мой сервер выполняет некий дорогостоящий и сложный процесс, который подтверждает, что пользователь имеет право использовать мое приложение. (Предположим, что такой процесс существует.)

Я шифрую сообщение с маркером своим закрытым RSA-ключом и отправляю его обратно клиенту. Клиент записывает зашифрованное сообщение на диск.

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

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

Что-нибудь из этого кажется вам хорошей идеей?

Потому что мне ничего из этого хорошей идеей не кажется.

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

Но всё, на самом деле, еще хуже. Давайте рассмотрим возможные атаки против такой системы.

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

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

В-третьих, предположим, существует злонамеренный пользователь по имени Ева, который прослушивает канал связи между добропорядочным пользователем (Алиса) и сервером (Бобом) и перехватывает их сообщения. Маркер Алисы отправляется серверу, в ответ отправляется зашифрованный маркер, в результате перехватчик сообщений получил достаточно информации для использования приложения.

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

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

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

Выражение «используйте в работе правильные инструменты» ни к чему не применимо так, как к проблеме решений реальных проблем из области безопасности. Криптосистемы созданы для качественного решения очень узкого круга задач, но как только вы хотя бы немного отклонитесь от предусмотренного сценария использования криптосистемы, вы сходите с накатанной лыжни и несетёсь вниз по склону через лес. Если вы врежетесь в дерево, то по своей вине, – не нужно было покидать лыжню!

Криптосистемы на основе алгоритма RSA чрезвычайно эффективны для решения двух задач:

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

2) Боб хочет отправить собственное сообщение, которое может прийти только от него. Сообщение шифруется закрытым ключом Боба и может быть дешифровано кем угодно с помощью открытого ключа. Тот факт, что сообщение может быть расшифровано с помощью открытого ключа Боба, является доказательством того, что сообщение пришло именно от Боба.

(И, конечно же, два эти сценария могут объединяться: сообщение от Алисы к Бобу может быть зашифровано ее закрытым ключом, чтобы только Боб мог его прочитать, и только Алиса могла его отправить. Конечно, это возможно только при условии, что Алиса и Боб нашли надежный способ обмена открытыми ключами. Мы предполагаем, что такой механизм существует и используется. Хотя в реальном мире это предположение может и не выполняться!)

Попадает ли описанная выше схема под один из этих сценирев? Нет, конечно же, нет. Может показаться, что она попадает под второй сценарий, но это не так. Во втором сценарии говорится о том, что Боб шифрует свое собственное сообщение своим закрытым ключом. Но в описанной схеме, сервер шифрует сообщение, выбранное потенциальным злоумышленником! Криптосистемы на основе RSA не предназначены для защиты от атак, при которых вы позволяете злоумышленнику шифровать произвольное сообщение вашим закрытым ключом. Такой вид атак называется «атакой с выбором шифротекста» (chosen ciphertext attack), потому что злоумышленник выбирает текст, который затем «дешифруется», т.е. шифруется ключом, который будет обычно использоваться Бобом для расшифровки сообщений от Алисы.

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

· Самый простой пример. Злоумышленник может просто создать маркер, представляющий собой документ с таким содержимым, которое Боб никогда бы не сформировал. Существование такой возможности означает, что никто не сможет положиться на тот факт, что сообщение, расшифрованное с помощью открытого ключа Боба, является действительно сообщением Боба. Но, все еще гораздо хуже.

· Предположим, Ева перехватила сообщение с маркером от Алисы к Бобу, зашифрованное с помощью открытого ключа Боба. Поскольку шифрование сообщения закрытым ключом дешифрует сообщения, зашифрованные открытым ключом, Ева просто отправляет это сообщение Бобу в качестве своего маркера. Боб дешифрует маркер Алисы для Евы. Теперь Ева знает маркер Алисы. Конечно же, Боб может обнаружить, что Ева прислала маркер, подозрительно похожий на зашифрованный маркер Алисы, поэтому такая атака может быть не самой хорошей идеей для Евы. Но…

· Злоумышленник может придумать нестандартное сообщение, которое после шифрования закрытым ключом будет содержать достаточно информации для расшифровки ранее перехваченных сообщений, зашифрованных открытым ключом. Например: Алиса шифрует свой маркер с открытым ключом Боба и отправляет его на сервер (Бобу), где оно дешифруется закрытым ключом, результат шифруется закрытым ключом и отправляется обратно. Ева перехватывает оба сообщения. После этого Ева создает особое сообщение, основанное на преобразовании перехваченного содержимого и просит, чтобы Боб зашифровал его своим закрытым ключом. Содержимое результата быть использовано Евой для определения маркера Алисы. У Боба нет разумного способа узнать, что эти сообщения Евы на самом деле являются попыткой расшифровать маркер Алисы.

· Злонамеренный аутентифицированный пользователь может выбрать маркер для шифрования закрытым ключом Боба, но так, чтобы Боб не знал об этом маркере. Существует возможность подобрать такой выглядящий случайным маркер, что вы сможете определить, каким будет результат его шифрования закрытым ключом Боба. Злоумышленник может использовать эту атаку для генерации и распространенения среди других злоумышленников таких пар маркер/шифрованный маркер, по которым не удастся отследить аутентифицированного пользователя.

· И так далее. Мы даже не начали рассматривать вопросы корректного заполнения сообщений для предотвращения атаки повтором (replay attack). Здесь также существует масса других потенциальных проблем.

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

Так что же мы усвоили?

0) Если вы можете, просто не лезьте туда. Шифрование очень трудно правильно реализовать, и зачастую с самого начала оно является неверным решениям. Используйте другие механизмы для решения ваших проблем с безопасностью.

1) Если проблема заключается в ненадежном клиенте, тогда не создавайте систему безопасности, которая требует доверия клиенту.

2) Если возможно, используйте готовые решения.

3) Если вы не можете использовать готовые решения, но вынуждены использовать криптосистему, тогда не используйте криптосистему, которую вы понимаете не до конца.

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

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

6) Если вы позволяете клиенту выбирать маркер, тогда не шифруйте сам маркер. Подписывайте криптографически безопасный хеш маркера. Нападающему будет гораздо сложнее подобрать маркер, у которого будет нужный ему хеш.

7) Не используйте одну и ту же пару ключей для шифрования входящих и исходящих сообщений. Используйте отдельные пары ключей для каждой логической операции.

8) Шифруйте сообщения в обе стороны.

9) Рассмотрите использование механизма аннулирования, чтобы, узнав об атаке Евы, вы могли хотя бы аннулировать ее лицензию. (Или аннулировать скомпрометированную лицензию и т.п.)

*********

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

Оригинал статьи