Предпосылки для построения протокола безопасной связи в C

Прежде чем погрузиться в реализацию, убедитесь, что ваша среда разработки включает в себя компилятор C (GCC или Clang), базовые знания программирования сокетов и установленную библиотеку OpenSSL. OpenSSL обеспечивает надежные реализации криптографических алгоритмов, что делает его стандартным выбором для безопасной связи в C. На Linux установите OpenSSL через менеджер пакетов (например, ]. На Windows используйте предварительно скомпилированные двоичные файлы или создайте из источника. Также предполагается знакомство с сокетами TCP/IP и модель клиент-сервер.

Понимание криптографических строительных блоков

Безопасный протокол связи опирается на три столпа: конфиденциальность, целостность и аутентификация. Конфиденциальность достигается за счет шифрования, гарантируя, что только предполагаемый получатель может прочитать сообщение. Целостность гарантирует, что данные не были изменены при передаче. Аутентификация проверяет личности сообщающих сторон. В пользовательском протоколе вы обычно сочетаете симметричное шифрование, хеширование с кодами аутентификации сообщений (HMAC) и механизм обмена ключами, такой как Diffie-Hellman.

Симметричное шифрование с помощью AES

Расширенный стандарт шифрования (AES) является наиболее широко используемым симметричным шифром. Он работает на 128-битных блоках и поддерживает размеры ключей 128, 192 или 256 бит. Для безопасной связи отдавайте предпочтение AES в Galois/Counter Mode (GCM), который обеспечивает как конфиденциальность, так и целостность в одной операции. EVP-интерфейс OpenSSL позволяет легко шифровать и расшифровывать данные с AES-GCM. Избегайте старых режимов, таких как ЕЦБ или CBC, если они не сочетаются с тщательной прокладкой и аутентификацией.

Обмен ключами с Диффи-Хеллманом

Чтобы безопасно договориться о совместном секрете по незащищенному каналу, используйте обмен ключами Diffie-Hellman (DH). Обе стороны генерируют закрытые ключи и обмениваются публичными параметрами, а затем вычисляют общий секрет. Diffie-Hellman уязвим для атак «человек посередине», если не аутентифицирован, поэтому вы можете позже расширить это с помощью цифровых подписей или предварительно разделенных ключей. Для использования в производстве рассмотрите возможность использования эфемерного Diffie-Hellman (DHE) для обеспечения идеальной прямой секретности.

Целостность и аутентификация сообщений с помощью HMAC

Чтобы убедиться, что сообщение не было подделано, добавьте к каждому зашифрованному шифротексту код аутентификации сообщений на основе Hash. HMAC использует общий секретный ключ и криптографическую хеш-функцию (например, SHA-256). Приемник повторно вычисляет HMAC на полученных данных и сравнивает его с передаваемым значением. Этот шаг предотвращает повторение и атаки подделки. Альтернативно, AES-GCM включает в себя тег аутентификации, который служит той же цели, упрощая протокол.

Настройка OpenSSL в вашем C-проекте

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

#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/err.h>
// Initialize OpenSSL
void init_openssl() {
 SSL_load_error_strings();
 OpenSSL_add_all_algorithms();
}

Строительный слой TCP Socket Layer

Основной транспорт для вашего протокола будет TCP, который обеспечивает надежную, упорядоченную доставку. Создайте сервер, который слушает входящие соединения, и клиент, который инициирует рукопожатие. Используйте стандартные разъемы POSIX с , , , на стороне сервера и , на стороне клиента. Не забудьте обрабатывать ошибки изящно и закрывать дескрипторы файлов после использования.

Серверный пример Skeleton

int server_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
bind(server_fd, (struct sockaddr*)&address, sizeof(address));
listen(server_fd, 3);
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);

Пример клиента Skeleton

int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr));

Обмен ключами Диффи-Хеллмана

После установления соединения TCP клиент и сервер выполняют обмен ключами DH. Каждая сторона генерирует пару ключей DH с использованием API OpenSSL . Открытый ключ отправляется через гнездо, и обе стороны получают общий секрет с использованием . Для простоты используйте фиксированную простую группу (например, с параметрами из ). В реальном протоколе вы бы договаривались о группе или использовали предварительно определенные параметры.

// Generate DH parameters
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, NULL);
EVP_PKEY_paramgen_init(pctx);
EVP_PKEY_CTX_set_dh_paramgen_prime_len(pctx, 2048);
EVP_PKEY *params = NULL;
EVP_PKEY_paramgen(pctx, &params);

// Generate key pair
EVP_PKEY_CTX *kctx = EVP_PKEY_CTX_new(params, NULL);
EVP_PKEY_keygen_init(kctx);
EVP_PKEY *my_key = NULL;
EVP_PKEY_keygen(kctx, &my_key);

// Export public key to send
unsigned char *pub_key_der = NULL;
int pub_len = i2d_PUBKEY(my_key, &pub_key_der);
send(sock, pub_key_der, pub_len, 0);

На приемной стороне пир импортирует открытый ключ с использованием , а затем получает общий секрет. Полученный секрет может быть хеширован (например, с SHA-256) для получения однородного ключа для AES и HMAC.

Шифрование и дешифрование сообщений с помощью AES-GCM

AES-GCM является предпочтительным режимом, поскольку он обеспечивает как шифрование, так и тег аутентификации в одной операции. Используйте OpenSSL с . Вам нужен 12-байтовый nonce (IV) и 256-битный ключ, полученный из общего секрета DH. Шифротекст производится по частям, и после окончательного обновления вы получаете тег 16 байт. Отправьте нонс, шифротекст и тег вместе. Приемник выполняет , устанавливает тег и дешифрует. Если проверка тега не удается, сообщение отклоняется.

// Encryption
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, nonce);
unsigned char ciphertext[1024];
int outlen;
EVP_EncryptUpdate(ctx, ciphertext, &outlen, plaintext, len);
int tmplen;
EVP_EncryptFinal_ex(ctx, ciphertext + outlen, &tmplen);
unsigned char tag[16];
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag);

Добавление целостности с HMAC (или использование метки GCM)

Если вы решите не использовать AES-GCM, вы можете зашифровать с AES-CBC, а затем вычислить HMAC по шифротексту. Используйте из OpenSSL с SHA-256. Добавьте HMAC после шифротекста. Приемник пересчитывает и сравнивает. Этот подход требует двух ключей: один для шифрования, один для HMAC. Примите оба из общего секрета с использованием функции производного ключа (KDF) как HKDF. Однако AES-GCM устраняет необходимость в отдельном HMAC, уменьшая сложность и потенциальные ошибки.

Соединяем: полный рабочий процесс

  1. Установите TCP-соединение между клиентом и сервером.
  2. Обе стороны генерируют эфемерные пары ключей Диффи-Хеллмана.
  3. Обмен открытыми ключами и вычисление общей тайны.
  4. Подать 256-битный ключ AES и 256-битный ключ HMAC (или использовать тот же ключ для GCM).
  5. Клиент отправляет nonce (12 байтов случайным образом), а затем зашифрованное сообщение AES-GCM плюс тег. Сервер дешифровывает и проверяет.
  6. Сервер отправляет ответ с использованием нового nonce (никогда не используйте nonces с тем же ключом).
  7. Обе стороны могут продолжать обмениваться сообщениями; для длительных сеансов периодически переключайтесь, используя одно и то же рукопожатие DH или механизм рэтчета.

Лучшие практики безопасности

  • Используйте сильные генераторы случайных чисел. Позвоните из OpenSSL для генерации ключей, nonces и DH приватных ключей. или никогда не используйте в криптографических целях.
  • Проверить все полученные данные. Проверить длину, параметры открытого ключа (например, убедиться, что p является простым, g является генератором) и теги HMAC перед обработкой.
  • Избегайте жестких кодов или по умолчанию. Всегда согласовывайте ключи свежими за сеанс, чтобы обеспечить идеальную секретность.
  • Ошибки ручной работы грациозно. Если дешифрование не удается или проверка HMAC не удается, закройте соединение и зарегистрируйте событие. Не раскрывайте, почему произошел сбой.
  • Поддерживайте обновления зависимостей. Регулярно обновляйте OpenSSL для исправления известных уязвимостей. Документация OpenSSL содержит руководство по амортизации и передовой практике.
  • Рассматривайте использование TLS, а не пользовательского протокола. Для производственных систем полагайтесь на хорошо протестированные протоколы, такие как TLS 1.3. Создание пользовательского протокола с нуля подвержено ошибкам и не рекомендуется, если у вас нет глубоких криптографических знаний.

Испытание Протокола

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

Заключение

Создание безопасного протокола связи на C является отличным упражнением для обучения, но оно требует тщательного внимания к деталям. Используя проверенные реализации OpenSSL Diffie-Hellman, AES-GCM и HMAC, вы можете создать систему, которая обеспечивает конфиденциальность, целостность и аутентификацию. Всегда следуйте криптографическим передовым практикам: используйте сильную случайность, извлекайте ключи сеанса с KDF, никогда не повторно и тщательно проверяйте все данные. Для чего-либо за пределами учебного проекта рассмотрите возможность принятия стандартного протокола, такого как TLS 1.3 , который был тщательно рассмотрен и развернут в масштабе. Безопасная связь - это непрерывный процесс улучшения; будьте в курсе возникающих угроз и обновляйте свои реализации соответственно.