Pull to refresh

Криптопереписка для недоверчивых

Reading time8 min
Views50K
Осторожно: данный пост может вызывать непродолжительное обострение паранойи

Привет! Не верите ли вы в популярные продукты для защищённой переписки так, как не верю в них я? Например, в браузерные крипточаты с шифрованием на стороне клиента, или в p2p-криптомессенжеры?

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

image



Вступление


Во всех продуктах защищённой коммуникации, которые я встречал, у меня неизбежно возникал вопрос доверия их разработчикам. Я не понимал, почему, скажем, к автору продукта всё ещё не пришли и не попросили ослабить шифрование или отдавать клиенту с определённым ip-адресом особый javascript-код. Вы можете сказать: «это невозможно». Почитайте, например, недавний пост Почтовый сервис Lavabit вынужден закрыться, и, особенно, продолжение этой истории(англ.), прошедшее мимо хабра.

К сожалению, люди совершают ошибки. Взять, например, сервис, первый по ссылке гугла по запросам «secure chat» и «crypto chat», который называется Cryptocat. Примерно три месяца назад в его коде обнаружилась ошибка — из-за недостаточно хорошей реализации генератора случайных чисел групповые сообщения, которые передавались в период с 17 октября 2011 года по 15 июня 2013 года стало возможно расшифровать, см. Decryptocat. Причём, в начале 2013 года компанией Veracode проводился аудит кода, который присудил ей «Security Quality Score of 100/100», и это был не единственный аудит кода в этот период.

Поэтому возникла идея использовать для коммуникации библиотеки, открытые алгоритмы и протоколы с более чем десятилетней историей поиска уязвимостей. Для меня, как и, надеюсь, что для многих из вас — это алгоритмы и протоколы из openssl и openssh(который, кстати, использует openssl внутри), а именно: TLS, SSH, SHA1, AES256, RSA и D-H. Я буду рассказывать на их примере, но, конечно, вы можете заменить их на те, которым доверяете лично вы, основная идея при этом не изменится.

Об OpenSSL


OpenSSL — библиотека с открытым исходным кодом, реализующая протоколы SSL/TLS. Они используются каждый раз, когда вы заходите на сайт по HTTPS. Про это недавно была статья.

Вот список уязвимостей, которые нашли в OpenSSL за более чем 10 лет анализа.

Неполный список программ, которые используют OpenSSL: apache, nginx, squid, openvpn, openssh, ntp, dhcp, cups, syslog-ng, xorg-server, php, python, ruby, libevent, nodejs, curl, wget, links, lynx, socat, hostapd, wpa_supplicant, virtualbox, vmware-player, libreoffice, ffmpeg.

И совсем немного про TLS. TLS — это криптопротокол, который позволяет устанавливать защищённые соединения. Сейчас существуют 3 версии этого протокола: 1.0, 1.1 и 1.2. Они описаны, соответственно, в rfc 2246, rfc 4346 и rfc 5246. Забавно, что все они заканчиваются на 46. Мы будем использовать версию 1.0 т.к. она вышла в 1999 году и в ней не было найдено серьёзных уязвимостей.

Протокол очень хитрый. Очень упрощённо процесс выглядит так: стороны договариваются о ключе для симметричного шифрования и в дальнейшем его используют. Все сообщения защищаются от изменения/подмены при помощи сочетания хэш функции с секретным ключём(HMAC). Также в протоколе предусмотрена взаимная аутентификация сервера и клиента

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

Примеры использования
1) Тестирование чисел на простоту:
$ openssl prime 997
3E5 is prime

2) Генерация случайных чисел:
$ openssl rand -hex 16
2871002e6f3cff937d066da9d3017197

3) Вычисление контрольной суммы:
$ openssl sha1 /etc/passwd
SHA1(/etc/passwd)= 8b74a9f1ddf496c02bc85e0e120bbb903d922276

4) Шифрование:
$ openssl aes-256-cbc -k pass -in /etc/passwd
<много бинарного вывода>


Именно этот интерфейс мы и будем использовать для нашей задачи. Перейдём к делу.

Простой вариант


Первый собеседник (с «белым» ip, назовём его «сервер») выполняет:
openssl s_server -accept 4433 -nocert -cipher ADH-AES256-SHA -tls1 -no_ticket


Второй собеседник, клиент, выполняет:
openssl s_client -connect <host>:4433 -cipher ADH-AES256-SHA -tls1 -no_ticket


Что мы только что сделали? Первой командой мы запустили tls 1.0-сервер, а второй — подключились к нему. Теперь можно общаться, все сообщения шифруются алгоритмом AES256, ключ для шифрования согласуется с помощью алгоритма Диффи-Хеллмана(D-H). Вот отличное пятиминутное видео, объясняющее его суть. Алгоритм D-H позволяет получить общий секретный ключ, используя незащищённый от прослушивания канал связи.

На этом можно было бы и закончить, но у такого подхода есть три проблемы:
1) Он не защищает от атаки «человек посередине»(MITM, Man In The Middle). Идея атаки тривиальна — становимся между сервером и клиентом, клиент думает, что подключается к серверу, а на самом деле, подключается к нам(к злоумышленнику), а мы транслируем его трафик на настоящий сервер.
2) Требуется наличие белого ip хотя бы у одной стороны.
3) Даже если данные нельзя расшифровать, доступна «метаинформация» о соединении: кто с кем соединялся, когда и сколько данных передано.

Защищаемся от MITM


Для того, чтобы защититься MITM, нужно, чтобы сервер аутентифицировал клиента, а клиент — сервера. Это достигается использованием асимметричного шифрования.

На на сервере и на клиенте нужно сгенерировать закрытый RSA-ключ и публичный сертификат.

На сервере выполняем:
openssl genrsa -out server.key 2048
openssl req -new -key server.key -batch -days 3650 -x509 -out server.crt


На клиенте:
openssl genrsa -out client.key 2048
openssl req -new -key client.key -batch -days 3650 -x509 -out client.crt


Здесь, 2048 — количество бит в модуле закрытого ключа, 3650 — срок действия сертификата в днях.

Затем сервер должен передать свой сертификат(файл server.crt) клиенту, а клиент(файл client.crt) — серверу. Это слабое место, ведь злоумышленник может подменить сертификат в момент передачи(если он его просто прочитает, то ничего страшного). Поэтому, лучше передавать его, используя несколько каналов(pastebin, email, skype, jabber, sms, голосом по телефону, почтой России, стеганографией в фотографиях котят в блоге, и.т.д). Передать сертификат достаточно один раз.

После этого, команда для создания TLS-сервера будет выглядеть так:
openssl s_server -accept 4433 -cert server.crt -key server.key -Verify 0 -CAfile client.crt -cipher DHE-RSA-AES256-SHA -tls1 -no_ticket


Подключение к серверу:
openssl s_client -connect <host>:4433 -cert client.crt -key client.key -verify 0 -CAfile server.crt -cipher DHE-RSA-AES256-SHA -tls1 -no_ticket


Теперь сервер и клиент смогут аутентифицировать друг друга.

Важно: если аутентификация прошла не успешно, то ни сервер, ни клиент не разорвёт соединение! Они лишь напишут об ошибке. К сожалению, нет ключа, который бы менял это поведение, поэтому нужно внимательно смотреть в лог подключения. Ошибка выглядит примерно так:
verify error:num=18:self signed certificate


Будем считать, что проблему MITM мы решили, осталось понять как решить проблемы 2) и 3). Здесь нам поможет протокол SSH.

SSH спешит на помощь


Протокол SSH активно используется при администрировании UNIX-серверов. Он чем-то похож на TLS, передаваемые данные тоже шифруются и подписываются. О ключе для шифрования договариваются обе стороны, например, используя тот же алгоритм D-H.

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

Одна из особенностей протокола — он позволяет создавать защищённые туннели. Этим мы и воспользуемся!

На сервере выполняем:
ssh -c aes256-cbc -m hmac-sha1-96 -o KexAlgorithms=diffie-hellman-group-exchange-sha1 -R 4433:127.0.0.1:4433 user@sshhost
nc -l -p 4433 -v


Здесь, для дополнительного спокойствия, мы явно указываем алгоритмы шифрования, обмена ключами и хэширования. Ключ -R значит, что все подключения на порт 4433 ssh-сервера пойдут в шифрованный ssh-туннель и попадут к ssh-клиенту на тот же самый порт. Если у пользователя установлен в качестве шелла /sbin/nologin, то иногда помогает использование ключа -N, который указывает ssh-клиенту, не исполнять никаких команд на сервере, а лишь только создать туннель.

На клиенте выполняем:
ssh -c aes256-cbc -m hmac-sha1-96 -o KexAlgorithms=diffie-hellman-group-exchange-sha1 -L 4433:127.0.0.1:4433 user@sshhost
nc 127.0.0.1 4433


Теперь, если клиент подключится к себе на 127.0.0.1 на порт 4433, то ssh-клиент запроксирует соединение по защищённому туннелю на порт 4433 удалённой машины, а оттуда, данные перешифруются и полетят по другому туннелю на машину-сервер.

Команда nc — это вызов популярной утилиты netcat. Она позволяет слушать и устанавливать tcp-соединения, а так же передавать по ним данные. При выполнении команд выше, стороннему наблюдателю будет казаться, что никакого туннеля нет и что это просто два ssh-подключения к одному и тому же серверу, никак не связанных между собой. Таким образом, получаем защищённое и относительно незаметное соединение.

Что плохо? То, что мы не можем доверять ssh-серверу! Теоретически он может писать расшифрованные данные к себе в лог в момент их перешифровки, когда они попадают из одного туннеля в другой.

SSH + OpenSSL или We have to go deeper


Теперь пустим наш TLS-трафик поверх цепочки SSH-туннелей. Если вы внимательно следили, то итоговая последовательность команд будет такой:

На сервере:
# следующие три строки обязательны только для соединения в первый раз, в дальнейшем - опциональны
openssl genrsa -out server.key 2048
openssl req -new -key server.key -batch -days 3650 -x509 -out server.crt
[передаём server.crt клиенту]

ssh -c aes256-cbc -m hmac-sha1-96 -o KexAlgorithms=diffie-hellman-group-exchange-sha1 -R 4433:127.0.0.1:4433 user@sshhost

openssl s_server -accept 4433 -cert server.crt -key server.key -Verify 0 -CAfile client.crt -cipher DHE-RSA-AES256-SHA -tls1 -no_ticket

[ждём соединения]
[внимательно смотрим на лог подключения на предмет ошибок валидации сертификата]
[общаемся]


На клиенте:
# следующие три строки обязательны только для соединения в первый раз, в дальнейшем - опциональны
openssl genrsa -out client.key 2048
openssl req -new -key client.key -batch -days 3650 -x509 -out client.crt
[передаём client.crt серверу]

ssh -c aes256-cbc -m hmac-sha1-96 -o KexAlgorithms=diffie-hellman-group-exchange-sha1 -L 4433:127.0.0.1:4433 user@sshhost
openssl s_client -connect localhost:4433 -cert client.crt -key client.key -verify 0 -CAfile server.crt -cipher DHE-RSA-AES256-SHA -tls1 -no_ticket

[внимательно смотрим на лог подключения на предмет ошибок валидации сертификата]
[общаемся]



Для того, чтобы ssh-клиент уходил в background после подключения, ему можно указать ключи -Nf.

Итог



Итак, не написав ни одной строчки кода и используя только стандартные инструменты, мы получили возможность переписываться, используя незаметное, способное работать за NAT'ом, соединение, защищённое от прослушивания, MITM-атак и подмены сообщений. К тому же, оно относительно просто для настройки — от каждого собеседника требуется выполнить всего по 4 команды и одну передачу файла для первого соединения, и по две команды для последующих. Да, ещё требуется доступ по SSH к любому серверу.

MiniFAQ


Q: Почему не VPN?
A: VPN — хорошее решение. Но он сложен в настройке, требует рутовых прав на сервере или доверия к серверу.

Q: Почему не GnuPG?
A: GnuPG — тоже хорошее решение. Но при желании переданные ранее данные можно расшифровать, получив закрытый ключ(например, вместе с ноутбуком).

Q: С чего вы взяли, что в OpenSSL нет уязвимостей?
A: Я уверен, что они там есть. OpenSSL лично мне субъективно кажется надёжным, поэтому я рассказывал на его примере. Можно использовать любую другую реализацию, которой доверяете лично вы.

Q: Обещаете ли вы, что команды правильные?
A: Не обещаю! Советую проверить!

Q: Как дела с кроссплатформенностью?
A: OpenSSL — кроссплатформенная. Клиенты SSH существуют под большинство ОС.

Q: Хочу веб-интерфейс, почему консоль?!
A: Для вэб-интерфейса требуется писать код. Одна небольшая XSS-уязвимость может стоить важных данных. Моя логика проста: нет кода — нет ошибок.

Q: Ошибка getaddrinfo: Name or service not known при выполнении команды openssl s_server
A: У вас, наверно, выключен IPv6. Обновите openssl.

Q: Ошибка gethostbyname failure при выполнении команды openssl s_server
A: Проверьте файл /etc/hosts. Первое имя для адреса 127.0.0.1 обязательно должно резолвиться.
Tags:
Hubs:
+55
Comments36

Articles

Change theme settings