
В 2019 году департамент информационных технологий Москвы разработал систему дистанционного голосования с использованием блокчейна. Тогда голоса избирателей шифровали с помощью собственной реализации схемы Эль-Гамаля. Эксперты дважды находили в ней разного рода уязвимости.
Теперь в браузере избирателя его голос шифруется с помощью JavaScript-библиотеки TweetNaCl.js. Ее автор Дмитрий Честных узнал от «Медузы» об использовании своей программы разработчиками дистанционного электронного голосования.
Честных портировал на JavaScript программу TweetNaCl — компактную реализацию программы NaCl (Networking and Cryptography library), разработанную известными криптографами Дэниелом Бернштейном (Daniel J. Bernstein), Таней Ланге (Tanja Lange) и Питером Швабе (Peter Schwabe). Все эти программы используют эллиптическую кривую Curve25519 для согласования ключей, а алгоритмы Poly1305 для аутентификации сообщений и Salsa20 для шифрования.
Предыдущая система со схемой Эль-Гамаля обеспечивала вероятностное шифрование голоса избирателя: при использовании одних и тех же параметров на практике всегда получался новый шифротекст. Сообщение нельзя было наверняка расшифровать, не раздобыв секретный ключ получателя (избиркома).
Новая система отличается детерминированным шифрованием: использование одних и тех же параметров приводит к формированию идентичных шифротекстов. Более того, отправитель и получатель формируют общий ключ (shared key), который пригоден как для шифрования сообщения, так и для его расшифровки.
То есть избиратель теперь не только может отправить зашифрованный выбор на сервер избиркома, откуда он попадет в блокчейн, но и успешно расшифровать его. Ему не нужно ждать начала официального подсчета голосов и публикации секретного ключа избирательной комиссии — достаточно сохранить свой секретный ключ, формируемый в браузере на странице электронного бюллетеня.
Мы провели эксперимент, и оказалось, что раздобыть свой секретный ключ сравнительно легко.
Для того чтобы сохранить генерируемый в браузере секретный ключ, мы в Google Chrome получили электронный бюллетень, открыли «Инструменты разработки», выбрали вкладку Sources, отыскали библиотеку election.js, нашли там строку с генерацией секретного и публичного ключей, поставили после нее logpoint (точка, дойдя до которой браузер запишет в консоль требуемую отладочную информацию): 'voter secret key is', encryptor.keyPair.secretKey. И только после этого проголосовали.

Общие данные для всех избирателей
Общедоступные данные о голосовании избирателя
Секретный ключ избирателя из его браузера
Общедоступные данные о голосовании избирателя
Секретный ключ избирателя из его браузера
Вот скрипт, позволяющий расшифровать голоса интернет-избирателей, сохранивших свои секретные ключи.
Если вы не сохранили свой секретный ключ, то вы не сможете расшифровать свой голос самостоятельно — до публикации секретного ключа избирательной комиссии.
Теперь в теории работодатель может не только проверить, что его сотрудник точно проголосовал (потребовав указать конкретную транзакцию с голосом), но и выяснить, каким образом тот распорядился своим голосом (потребовав сохранить секретный ключ). При этом необязательно, чтобы сотрудник был технически подкованным. Его, к примеру, могут попросить установить специальное расширение для браузера, которое подменит сгенерированные ключи на статичные. Таким образом, задача по расшифровке голосов будет сильно упрощена.
Но эту же особенность можно использовать для контроля честности подсчета голосов, когда избирком отказывается обнародовать расшифровку каждого конкретного голоса (как это было в 2019 году). К примеру, сторонники одного из кандидатов могут договориться об установке такого же расширения для браузера. В этом случае прямо по ходу голосования они могут отслеживать минимум голосов, который точно должен получить их кандидат после подсчета.
Разработчики системы интернет-голосования могут запретить избирателям пользоваться одной и той же парой ключей, но это их не спасет. Дмитрий Честных предложил возможные пути обхода такого запрета: «Например, такое расширение может генерировать ключи детерминистически, взяв хэш от статичного секрета и имени (и какого-нибудь номера избирателя) — тогда это обнаружить будет сложно, потому что пары ключей будут разные, но тот, кто владеет секретом, может их заново сгенерировать». То есть для внешнего наблюдателя ключи, сгенерированные расширением, будут выглядеть случайными, но авторы этого расширения смогут восстановить ключи, зная определенные дополнительные параметры. Еще один вариант, предложенный автором TweetNaCl.js: «Расширение может просто писать ключи в центральную базу. Алгоритм Curve25519 очень быстрый, поэтому расшифровать миллионы голосов не составит труда».
«В любом случае это, конечно, полный бред, а не система электронного голосования», — резюмировал Дмитрий Честных.
Чтобы закрыть эту лазейку, департаменту информационных технологий Москвы придется снова полностью менять схему шифрования голосов избирателей. Вряд ли это можно успеть сделать до единого дня голосования в сентябре 2020 года.
На странице электронного бюллетеня в браузере избирателя генерируется не только пара ключей, но и nonce — специальное число, которое должно меняться при создании нового шифротекста (number used only once).
Оно генерируется случайным образом, но теоретически его тоже можно подменить с помощью расширения. Это число достаточно длинное, чтобы персонализировать бюллетень. 24 байта позволяют записать пару слов на русском языке в кодировке UTF-8 (или целую фразу на английском), поставить несколько эмодзи или высказаться каким-то другим образом. На качество шифрования в данном случае это не повлияет.
Правда, чтобы прочитать такое сообщение, скорее всего, тоже нужно будет устанавливать специальное расширение или ждать подборок «расшифрованных» посланий от наблюдателей.
Денис Дмитриев
Автор благодарит за участие в эксперименте Михаила Зеленского, Евгению Давыдову и Дмитрия Карцева