Начало работы с OpenSSL: основы криптографии
4
9484
7 сентября 2020 22:45
7/09/2020
Visitors have accessed this post 9484 times.
Перевод статьи — https://opensource.com/article/19/6/cryptography-basics-openssl-part-1
Эта первая из двух статей, посвященных основам криптографии с использованием OpenSSL, библиотеки промышленного уровня и инструментария, популярного в Linux и других операционных системах. Утилиты OpenSSL доступны в командной строке, а программы могут вызывать функции из библиотек OpenSSL. Пример программы для этой статьи написан на C, исходном языке для библиотек OpenSSL.
В двух статьях по данной теме рассматриваются все криптографические хеши, цифровые подписи, шифрование и дешифрование и цифровые сертификаты.
Начнем с обзора SSL в имени OpenSSL.
С чего все началось
Secure Socket Layer (SSL) — это криптографический протокол, который был выпущен компанией Netscape в 1995 году. Этот уровень протокола обозначается буквой S, что означает безопасность, и располагается в конце протокола HTTPS. Протокол SSL предоставляет различные службы безопасности, две из которых являются центральными в HTTPS:
- Аутентификация пользователя (еще называют протоколом согласования ключа): каждая сторона соединения аутентифицирует личность другой стороны. Если, к примеру, Алиса и Боб обмениваются сообщениями по SSL, то каждый сначала аутентифицирует личность другого.
- Конфиденциальность: отправитель шифрует сообщения перед отправкой по каналу. Затем получатель расшифровывает каждое полученное сообщение. Этот процесс защищает сетевые диалоги. Даже если подслушивающая Ева перехватывает зашифрованное сообщение от Алисы Бобу (атака через посредника), ей не удастся расшифровать это сообщение.
Эти две ключевые службы SSL, в свою очередь, связаны с другими, которым уделяется меньше внимания. Например, SSL поддерживает целостность сообщения, что гарантирует, что полученное сообщение совпадает с отправленным. Эта функция реализована с помощью хеш-функций, которые также входят в комплект OpenSSL.
SSL имеет несколько версий (например, SSLv2 и SSLv3), и в 1999 году безопасность транспортного уровня (TLS) появилась в качестве аналогичного протокола на основе SSLv3. TLSv1 и SSLv3 похожи, но их недостаточно для совместной работы. Тем не менее, принято называть SSL/TLS одним и тем же протоколом. Например, функции OpenSSL часто именуют SSL, даже когда используется протокол TLS. К тому же, вызов утилит командной строки OpenSSL начинается с термина openssl.
Документация по OpenSSL из неофициальных справочников зачастую может быть громоздкой и плохо организованной, учитывая большой пакет инструментов OpenSSL. Примеры командной строки и кода – один из способов сосредоточить внимание на основных темах. Давайте начнем со знакомого нам примера, а именно, доступа к веб-сайту с помощью HTTPS, и воспользуемся этим примером, чтобы выделить интересующие нас криптографические элементы.
HTTPS-клиент
В примере ниже, программа-клиент подключается через HTTPS к Google:
/* compilation: gcc -o client client.c -lssl -lcrypto */
#include <stdio.h>
#include <stdlib.h>
#include <openssl/bio.h> /* BasicInput/Output streams */
#include <openssl/err.h> /* errors */
#include <openssl/ssl.h> /* core library */
#define BuffSize 1024
void report_and_exit(const char* msg) {
perror(msg);
ERR_print_errors_fp(stderr);
exit(-1);
}
void init_ssl() {
SSL_load_error_strings();
SSL_library_init();
}
void cleanup(SSL_CTX* ctx, BIO* bio) {
SSL_CTX_free(ctx);
BIO_free_all(bio);
}
void secure_connect(const char* hostname) {
char name[BuffSize];
char request[BuffSize];
char response[BuffSize];
const SSL_METHOD* method = TLSv1_2_client_method();
if (NULL == method) report_and_exit("TLSv1_2_client_method...");
SSL_CTX* ctx = SSL_CTX_new(method);
if (NULL == ctx) report_and_exit("SSL_CTX_new...");
BIO* bio = BIO_new_ssl_connect(ctx);
if (NULL == bio) report_and_exit("BIO_new_ssl_connect...");
SSL* ssl = NULL;
/* link bio channel, SSL session, and server endpoint */
sprintf(name, "%s:%s", hostname, "https");
BIO_get_ssl(bio, &ssl); /* session */
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); /* robustness */
BIO_set_conn_hostname(bio, name); /* prepare to connect */
/* try to connect */
if (BIO_do_connect(bio) <= 0) {
cleanup(ctx, bio);
report_and_exit("BIO_do_connect...");
}
/* verify truststore, check cert */
if (!SSL_CTX_load_verify_locations(ctx,
"/etc/ssl/certs/ca-certificates.crt", /* truststore */
"/etc/ssl/certs/")) /* more truststore */
report_and_exit("SSL_CTX_load_verify_locations...");
long verify_flag = SSL_get_verify_result(ssl);
if (verify_flag != X509_V_OK)
fprintf(stderr,
"##### Certificate verification error (%i) but continuing...\n",
(int) verify_flag);
/* now fetch the homepage as sample data */
sprintf(request,
"GET / HTTP/1.1\x0D\x0AHost: %s\x0D\x0A\x43onnection: Close\x0D\x0A\x0D\x0A",
hostname);
BIO_puts(bio, request);
/* read HTTP response from server and print to stdout */
while (1) {
memset(response, '\0', sizeof(response));
int n = BIO_read(bio, response, BuffSize);
if (n <= 0) break; /* 0 is end-of-stream, < 0 is an error */
puts(response);
}
cleanup(ctx, bio);
}
int main() {
init_ssl();
const char* hostname = "www.google.com:443";
fprintf(stderr, "Trying an HTTPS connection to %s...\n", hostname);
secure_connect(hostname);
return 0;
}
Эта программа может быть скомпилирована и выполнена из командной строки (обратите внимание на строчные буквы L в —lssl и —lcrypto):gcc -o client client.c -lssl -lcrypto
Программа пытается создает безопасное соединение с веб-сайтом www.google.com. Как только протокол TLS соединился с веб-сервером Google, программа-клиент получает один или несколько цифровых сертификатов, которые программа (но в моей системе это не удается) проверяет. Несмотря на это, программа-клиент продолжает загружать домашнюю страницу Google через защищенный канал. Эта программа зависит от артефактов безопасности, упомянутых ранее, хотя в коде выделяется только цифровой сертификат. Другие артефакты будут подробно разъяснены позже.
Обычно программа-клиент на C или C ++, открывающая канал HTTP (незащищенный), будет использовать операторы, такие как дескриптор файла для сетевого сокета, который является конечной точкой в соединении между двумя процессами (например, программой-клиентом и веб-сервером Google). Файловый дескриптор, в свою очередь, является положительным целым числом, которое идентифицирует в программе любую файловую конструкцию, которую открывает программа. Такая программа также будет использовать структуру для указания деталей об адресе веб-сервера.
Ни одна из этих сравнительно низкоуровневых конструкций не встречается в программе-клиенте, поскольку библиотека OpenSSL включает инфраструктуру сокетов и спецификацию адресов в высокоуровневые конструкции безопасности. В результате получается простой API. Ниже рассмотрим детали безопасности на примере программы-клиента.
- Программа начинается с загрузки соответствующих библиотек OpenSSL, и my function init_ssl создает два запроса в OpenSSL:
SSL_library_init(); SSL_load_error_strings()
- Следующим шагом инициализации является попытка получить контекст безопасности, структуру информации, необходимую для установления и поддержки безопасного канала к веб-серверу. В данном примере используется TLS 1.2, как показано в этом вызове функции библиотеки OpenSSL:
const SSL_METHOD* method = TLSv1_2_client_method(); /* TLS 1.2 */
Если вызов завершается успешно, указатель на метод передается библиотечной функции, которая создает контекст типа SSL_CTX:
SSL_CTX* ctx = SSL_CTX_new(method);
Программа-клиент проверяет наличие ошибок при каждом из этих важных вызовов библиотеки, а затем завершает работу в случае сбоя любого из них.
- Теперь начинают работу два других артефакта OpenSSL: сеанс безопасности типа SSL, который управляет безопасным соединением от начала до конца; и защищенный поток типа BIO (базовый ввод/вывод), который используется для связи с веб-сервером. Поток BIO генерируется с помощью следующего вызова:
BIO* bio = BIO_new_ssl_connect(ctx);
Обратите внимание, что крайне важным контекстом является аргумент. Тип BIO – это оболочка OpenSSL для типа FILE в языке программирования C. Эта оболочка защищает потоки ввода и вывода между программой-клиентом и веб-сервером Google.
- При наличии SSL_CTX и BIO, программа связывает их вместе в сеансе SSL. Затем начинают свою работу три вызова библиотеки:
BIO_get_ssl(bio, &ssl); /* get a TLS session */
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); /* for robustness */
BIO_set_conn_hostname(bio, name); /* prepare to connect to Google */
Само безопасное соединение устанавливается с помощью этого запроса:
BIO_do_connect(bio);
Если этот последний запрос не выполняется, программа-клиент завершается; в противном случае соединение будет поддерживать конфиденциальный диалог между программой-клиентом и веб-сервером Google.
Во время соединения с веб-сервером программа-клиент получает один или несколько цифровых сертификатов, которые подтверждают подлинность сервера. Однако программа-клиент не отправляет собственный сертификат, а это означает, что аутентификация является односторонней. (Веб-серверы обычно настроены таким образом, что не ожидают сертификата клиента.) Несмотря на неудачную проверку сертификата веб-сервера, программа-клиент продолжает загрузку домашней страницы Google через безопасный канал к веб-серверу.
Почему попытка подтвердить сертификат Google не удалась? Типичная установка OpenSSL имеет каталог /etc/ssl/certs, который включает в себя файл ca-certificate.crt. Каталог и файл вместе содержат цифровые сертификаты, которым OpenSSL доверяет по умолчанию и, соответственно, составляют хранилище доверенных сертификатов. Хранилище доверенных сертификатов можно обновлять по мере необходимости, в частности, чтобы включить новые доверенные сертификаты и удалить те, которые больше не являются доверенными.
Программа-клиент получает три сертификата от веб-сервера Google, но в хранилище доверенных сертификатов OpenSSL на моем компьютере не нашлось точных совпадений. В настоящее время программа-клиент не проверяет цифровую подпись на сертификате Google (подпись-подтверждение сертификата). Если этой подписи доверяют, то и сертификат, содержащий ее, также должен быть доверенным. Тем не менее, программа-клиент продолжает запрашивать страницу, а затем выводит домашнюю страницу Google. В следующем разделе рассмотрим этот аспект более детально.
Скрытые элементы безопасности в программе-клиенте
Давайте начнем с видимого артефакта безопасности в примере клиента – цифрового сертификата – и рассмотрим, как к нему относятся другие артефакты безопасности. Основной стандарт компоновки для цифрового сертификата – X509, а сертификат производственного уровня выдается центром сертификации (CA), к примеру, Verisign.
Цифровой сертификат содержит различные фрагменты информации (например, даты активации и истечения срока действия, а также доменное имя для владельца), включая идентификационные данные эмитента и цифровую подпись, то есть, зашифрованное криптографическое хеш-значение. Сертификат также имеет незашифрованное хеш-значение, которое служит его идентифицирующим отпечатком.
Значение хеш-функции – это результат отображения произвольного числа битов в дайджесте фиксированной длины. Что конкретно представляют собой биты (бухгалтерский отчет, роман или, возможно, цифровое кино), не имеет значения. Например, алгоритм хеширования версии 5 (MD5) Message Digest отображает входные биты любой длины в 128-битное хеш-значение, тогда как алгоритм SHA1 (алгоритм 1 безопасного хеша) отображает входные биты в 160-битное значение. Разные входные биты приводят к разным – действительно, статистически уникальным значениям хеш-функции. Вторая статья углубляется в детали и делает акцент на том, как хеш-функция становится криптографической.
Цифровые сертификаты различаются по типу (например, сертификаты корневого, промежуточного и конечного объекта) и образуют иерархию, которая отражает эти типы. Как следует из названия, корневой сертификат расположен поверх иерархии, а сертификаты под ним наследуют то доверие, которое имеет корневой сертификат. Библиотеки OpenSSL и большинство современных языков программирования имеют тип X509 вместе с функциями, которые работают с такими сертификатами. Сертификат от Google имеет формат X509, и задача программы-клиента проверить, является ли этот сертификат X509_V_OK.
Сертификаты X509 базируются на инфраструктуре открытых ключей (PKI), которая включает в себя алгоритмы (RSA является доминирующим) для генерации пар ключей: открытый ключ и его парный закрытый ключ. Открытый ключ – это идентификатор: открытый ключ Amazon идентифицирует его, а мой открытый ключ идентифицирует меня. Закрытый ключ не разглашается и должен храниться в секрете его владельцем.
Ключи в паре имеют стандартное использование. Открытый ключ может использоваться для шифрования сообщения, а закрытый ключ из той же пары может затем использоваться для расшифровки сообщения. Закрытый ключ также можно использовать для подписания документа или другого электронного артефакта (например, программы или электронного письма), а открытый ключ из пары можно затем использовать для проверки подписи. Следующие два примера дают нам более детальную картину о вышесказанном.
В первом примере, открытый ключ Алисы виден всем, в том числе и Бобу. Затем Боб шифрует сообщение с помощью открытого ключа Алисы, отправляя зашифрованное сообщение Алисе. Сообщение, зашифрованное открытым ключом Алисы, расшифровывается ее закрытым ключом, который (предположительно) у нее один, вот так:
+------------------+ зашифрованное сообщение +-------------------+
Сообщение Боба --->| Открытый ключ Алисы|--------------->| Закрытый ключ Алисы |---> Сообщение Боба
+------------------+ +-------------------+
Расшифровка сообщения без закрытого ключа Алисы в принципе возможна, но на практике нет, из-за надежной криптографической пары ключей, такой как RSA.Для второго примера рассмотрим подписание документа для подтверждения его подлинности. Алгоритм подписи использует закрытый ключ из пары для обработки криптографического хеша подписываемого документа:
+-------------------+
Хеш документа --->| Закрытый ключ Алисы |---> Цифровая подпись Алисы на документе
+-------------------+
Предположим, что Алиса подписывает контракт в цифровой форме и отправляет Бобу. Затем Боб может использовать открытый ключ Алисы из пары ключей, чтобы проверить подпись:
+------------------+
Цифровая подпись Алисы на документе --->| Открытый ключ Алисы |---> подтвержден или нет
+------------------+
Невозможно подделать подпись Алисы без закрытого ключа Алисы: следовательно, в интересах Алисы не разглашать закрытый ключ.
Ни одна из этих частей безопасности, за исключением цифровых сертификатов, явно не указана в программе-клиенте. В следующей статье подробно описываются примеры, в которых используются утилиты OpenSSL и библиотеки функций.
OpenSSL из командной строки
А пока давайте рассмотрим утилиты командной строки OpenSSL: в частности, утилиту для проверки сертификатов с веб-сервера во время соединения TLS. Вызов утилит OpenSSL начинается с команды openssl, а затем добавляет комбинацию аргументов и флагов для указания желаемой операции.
Рассмотрим следующую команду:
openssl list-cipher-algorithms
Вывод представляет собой список связанных алгоритмов, которые составляют набор шифров. Вот начало списка с комментариями для пояснения акронимов:
AES-128-CBC ## Advanced Encryption Standard, Cipher Block Chaining
AES-128-CBC-HMAC-SHA1 ## Hash-based Message Authentication Code with SHA1 hashes
AES-128-CBC-HMAC-SHA256 ## ditto, but SHA256 rather than SHA1
...
Следующая команда, использующая аргумент s_client, открывает защищенное соединение с www.google.com и выводит на экран полную информацию об этом соединении:
openssl s_client -connect www.google.com:443 -showcerts
Порт 443 — это стандартный порт, используемый веб-серверами для получения HTTPS, а не HTTP-соединений. (Для HTTP используется стандартный порт 80.) Сетевой адрес www.google.com:443 также встречается в коде программы-клиента. Если попытка подключения была успешной, отображаются три цифровых сертификата от Google вместе с информацией о безопасном сеансе, наборе шифров и связанных элементах. Например, вот фрагмент выходных данных с самого начала, который объявляет о предстоящей цепочке сертификатов. Кодировка для сертификатов — base64:
Certificate chain
0 s:/C=US/ST=California/L=Mountain View/O=Google LLC/CN=www.google.com
i:/C=US/O=Google Trust Services/CN=Google Internet Authority G3
-----BEGIN CERTIFICATE-----
MIIEijCCA3KgAwIBAgIQdCea9tmy/T6rK/dDD1isujANBgkqhkiG9w0BAQsFADBU
MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMSUw
...
Крупный веб-сайт, как Google, обычно отправляет несколько сертификатов для аутентификации.
Вывод заканчивается сводной информацией о сеансе TLS, включая особенности набора шифров:
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-GCM-SHA256
Session-ID: A2BBF0E4991E6BBBC318774EEE37CFCB23095CC7640FFC752448D07C7F438573
...
Протокол TLS 1.2 используется в программе-клиенте, а Session-ID идентифицирует соединение между утилитой openssl и веб-сервером Google. Запись шифра может быть проанализирована следующим образом:
- ECDHE (Протокол Диффи — Хеллмана на эллиптических кривых) — эффективный и действенный алгоритм для управления соединением TLS. В частности, ECDHE решает проблему распределения ключей, гарантируя, что обе стороны (например, программа-клиент и веб-сервер Google) используют один и тот же ключ шифрования/ дешифрования, который известен как ключ сеанса. В следующей статье этот алгоритм будет рассмотрен более детально.
- RSA (Rivest Shamir Adleman) является доминирующей криптосистемой с открытым ключом и названа в честь трех ученых, которые впервые описали систему в конце 1970-х годов. Пары ключей генерируются с помощью алгоритма RSA.
- AES128 (Advanced Encryption Standard) — это блочный шифр, который шифрует и дешифрует блоки битов. (Альтернативой является потоковый шифр, который шифрует и дешифрует биты по одному за раз.) Шифр симметричен, потому как один и тот же ключ используется для шифрования и дешифрования, что в первую очередь затрагивает тему распределения ключей. AES поддерживает ключи размером 128 (которые были использованы в примерах), 192 и 256 бит: чем больше ключ, тем лучше защита.
Размеры ключей для симметричных криптосистем, таких как AES, обычно меньше, чем для асимметричных (основанных на паре ключей) систем, таких как RSA. Например, 1024-битный ключ RSA относительно мал, тогда как 256-битный ключ в настоящее время является наибольшим для AES.
- GCM (счетчик с аутентификацией Галуа) обрабатывает многократное применение шифра (в данном случае AES128) во время защищенного диалога. Блоки AES128 имеют размер всего 128 бит, и безопасный диалог, вероятно, будет состоять из нескольких блоков AES128 с одной стороны на другую. GCM эффективен и обычно идет в паре с AES128.
- SHA256 (256-битный алгоритм безопасного хеширования) – это криптографический алгоритм хеширования. Полученные значения хеша имеют размер 256 бит, хотя с алгоритмом SHA возможны и бóльшие значения.
Наборы шифров постоянно развиваются или улучшаются. Например, не так давно Google использовал потоковый шифр RC4 (Ron’s Cipher версии 4 после Ron Rivest из RSA). У RC4 есть известные уязвимости, которые, по-видимому, объясняют переход Google на AES128, по крайней мере частично.
Заключение
Первое знакомство с OpenSSL через защищенный веб-клиент на языке C и обзор нескольких примеров командной строки показали, что некоторые темы не освещены должным образом и нуждаются в дополнительном разъяснении. В следующей статье будет проведен более детальный анализ, начиная с криптографических хешей и заканчивая полным обсуждением того, как цифровые сертификаты решают вопрос распространения ключей.
От редакции
Если вам интересно посещать бесплатные онлайн-мероприятия по DevOps, Kubernetes, Docker, GitlabCI и др. и задавать вопросы в режиме реального времени, подключайтесь к каналу DevOps by REBRAIN.
*Анонсы мероприятий каждую неделю
Практикумы для специалистов по инфраструктуре и разработчиков — https://rebrainme.com.
Наш Youtube-канал — https://www.youtube.com/channel/UC6uIx64IFKMVmj12gKtSgBQ.
Агентство Fevlake, проектируем и поддерживаем IT-инфраструктуры с 2012 года — https://fevlake.com.