- Вопрос или проблема
- Ответ или решение
- 1. Основы протокола Диффи-Хеллмана
- 2. Обзор вашего кода
- 2.1 Генерация параметров
- 2.2 Обработка публичного ключа
- 2.3 Импортирование публичного ключа сервера
- 3. Вычисление общего ключа (K)
- 4. Отладочный вывод
- 5. Альтернативы и общее улучшение
- 5.1 Используйте автоматическое управление памятью
- 5.2 Чистота кода
- 6. Заключение
Вопрос или проблема
Я пишу простой SSH-клиент на C с использованием библиотеки openSSL и группы Диффи-Хеллмана 14. Я считаю, что мне удалось успешно реализовать своего клиента до шага вывода общей ключевой информации (K) на этапе верификации сервера по Диффи-Хеллману. Функция OSSL, которая вычисляет общий секрет на моем клиенте, генерирует выходные данные, но поскольку верификация сервера не проходит, я хотел увидеть K сервера, чтобы убедиться, что мой K действительно неверен или что-то другое является причиной неудачи верификации. Поэтому я скачал исходный код сервера openSSH и изменил его, чтобы он выводил K после его генерации. Затем я подключил своего клиента к серверу openSSH, и вывод сервера подтверждает, что K, который вычисляет мой клиент, неверен (я предполагаю, что вычисленный K openSSH является правильным).
Ниже приведен код, который обрабатывает обмен DH:
int sendDiffieHellmanExchange(int sock) {
// инициализация всех переменных, которые необходимо освободить
EVP_PKEY_CTX *pctx = NULL, *kctx = NULL, *peerCtx = NULL;
EVP_PKEY *params = NULL, *clientKey = NULL, *peerkey = NULL;
BIO *out = NULL;
BIGNUM *p = NULL, *g = NULL, *eBN = NULL, *fBN = NULL;
OSSL_PARAM dhParams[3];
OSSL_PARAM peerParams[4];
unsigned char *pBin = NULL, *gBin = NULL, *eNetworkOrder = NULL, *buffer = NULL, *serverResponse = NULL;
int pSize = 0, gSize = 0;
RawByteArray *e = NULL, *payload = NULL, *packet = NULL;
// создание параметров группы 14 с использованием bignum
p = BN_new();
BN_get_rfc3526_prime_2048(p);
g = BN_new();
BN_set_word(g, 2);
pSize = BN_num_bytes(p);
gSize = BN_num_bytes(g);
// выделение памяти для pBin и gBin
pBin = (unsigned char*)OPENSSL_malloc(pSize);
gBin = (unsigned char*)OPENSSL_malloc(gSize);
// преобразование p и g в двоичную форму
// нужно дополнить бинарные данные до стандартного размера
BN_bn2binpad(p, pBin, pSize);
BN_bn2binpad(g, gBin, gSize);
// инициализация контекста параметров для генерации ключа DH
pctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL);
// подготовка параметров DH (p и g) с использованием массива OSSL_PARAM
dhParams[0] = OSSL_PARAM_construct_BN("p", pBin, pSize);
dhParams[1] = OSSL_PARAM_construct_BN("g", gBin, gSize);
dhParams[2] = OSSL_PARAM_construct_end();
// использование EVP_PKEY_fromdata для создания EVP_PKEY с использованием параметров DH
EVP_PKEY_fromdata_init(pctx);
EVP_PKEY_fromdata(pctx, ¶ms, EVP_PKEY_KEY_PARAMETERS, dhParams);
// генерация нового ключа DH
kctx = EVP_PKEY_CTX_new(params, NULL);
EVP_PKEY_keygen_init(kctx);
EVP_PKEY_keygen(kctx, &clientKey);
// извлечение открытого ключа BIGNUM
EVP_PKEY_get_bn_param(clientKey, "pub", &eBN);
int eLen = BN_num_bytes(eBN); // получение длины открытого ключа в байтах
eNetworkOrder = (unsigned char *)malloc(eLen);
BN_bn2bin(eBN, eNetworkOrder);
// смена порядка байтов с малой на большую
eNetworkOrder = swapEndianNess(eNetworkOrder, eLen);
e = addTwosComplementBit(eNetworkOrder, eLen);
printf("открытый ключ (e)\n");
printf("длина ключа: %zu\n", e -> size);
for (int i = 0; i < e -> size; i++) {
printf("%02x ", e -> data[i]);
}
printf("\n");
// формирование полезной нагрузки e (включая добавление размера к mpint)
buffer = malloc(e -> size + 1 + 4);
buffer[0] = SSH_MSG_KEXDH_INIT;
uint32_t mpintLenNetworkOrder = htonl(e->size);
memcpy(buffer + 1, &mpintLenNetworkOrder, sizeof(uint32_t));
memcpy(buffer + 5, e -> data, e -> size);
// сохранение глобальной копии e для использования в верификации сообщения
eGlobal = malloc(e -> size + sizeof(uint32_t));
memcpy(eGlobal, &mpintLenNetworkOrder, sizeof(uint32_t));
memcpy(eGlobal + sizeof(uint32_t), e -> data, e -> size);
eGlobalLen = e -> size + sizeof(uint32_t);
/* Опционально: напечатать закрытый ключ */
out = BIO_new_fp(stdout, BIO_NOCLOSE);
if (out && clientKey) {
EVP_PKEY_print_private(out, clientKey, 0, NULL);
}
payload = malloc(sizeof(RawByteArray));
assert(payload != NULL);
payload -> data = buffer;
payload -> size = e -> size + 1 + 4; // +1 за код сообщения, +4 за длину mpint
packet = constructPacket(payload);
int sentBytes = send(sock, packet -> data, packet -> size, 0);
if (sentBytes != -1) {
printf("Успешная отправка инициализации DH! Количество отправленных байтов: %i\n", sentBytes);
} else {
printf("Отправка не завершилась успешно.\n");
}
serverResponse = malloc(BUFFER_SIZE);
memset(serverResponse, 0, BUFFER_SIZE); // очищение буфера
ssize_t bytesReceived = recv(sock, serverResponse, BUFFER_SIZE, 0);
if (bytesReceived > 0) {
// printf("ответ сервера на инициализацию DH:\n");
// for (int i = 0; i < bytesReceived; i++) {
// printf("%02x ", (unsigned char)serverResponse[i]);
// }
// printf("\n");
} else {
printf("Не получен ответ сервера DH :(\n");
// случайный код ошибки сообщения
exit(1);
}
ServerDHResponse *dhResponse = extractServerDHResponse(serverResponse);
// нужно сохранить f от сервера в глобальной переменной, потому что OSSL не принимает ведущий двоичный бит, поэтому мы удаляем его для использования в OSSL
fGlobal = malloc(dhResponse -> fLen + sizeof(uint32_t));
mpintLenNetworkOrder = htonl(dhResponse -> fLen);
memcpy(fGlobal, &mpintLenNetworkOrder, sizeof(uint32_t));
memcpy(fGlobal + sizeof(uint32_t), dhResponse -> f, dhResponse -> fLen);
fGlobalLen = dhResponse -> fLen + sizeof(uint32_t);
// импортирование f в PKEY:
// Если первый байт f равен 0x00 и длина равна 257, удаляем ведущий байт.
// OSSL становится недовольным при согласовании ключей, если есть ведущий байт
if (dhResponse->fLen == 257 && dhResponse->f[0] == 0x00) {
// создание нового буфера для хранения скорректированного открытого ключа
unsigned char *adjustedF = malloc(dhResponse->fLen - 1);
assert(adjustedF != NULL); // проверка на ошибку выделения памяти
// копирование данных без ведущего нуля
memcpy(adjustedF, dhResponse -> f + 1, dhResponse -> fLen - 1);
// обновление указателя и длины
free(dhResponse -> f); // освобождение старого буфера, если выделялся динамически
dhResponse -> f = adjustedF; // указание на новый буфер
dhResponse -> fLen -= 1; // изменение длины
}
fBN = BN_new();
BN_bin2bn(dhResponse -> f, dhResponse -> fLen, fBN);
peerCtx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL);
// подготовка параметров для открытого ключа пира
peerParams[0] = OSSL_PARAM_construct_BN("p", pBin, pSize);
peerParams[1] = OSSL_PARAM_construct_BN("g", gBin, gSize);
peerParams[2] = OSSL_PARAM_construct_BN("pub", dhResponse -> f, dhResponse -> fLen);
peerParams[3] = OSSL_PARAM_construct_end();
EVP_PKEY_fromdata_init(peerCtx);
EVP_PKEY_fromdata(peerCtx, &peerkey, EVP_PKEY_PUBLIC_KEY, peerParams);
printf("FFFFF:\n");
for (int i = 0; i < dhResponse -> fLen; i++) {
printf("%02x ", dhResponse -> f[i]);
}
printf("\n");
// ОТЛИЧНОСТЬ ДЛЯ ОТЛАДКИ
EVP_PKEY_print_public(out, peerkey, 0, NULL);
RawByteArray *k = generateSharedKey(clientKey, peerkey);
RawByteArray *twosK = addTwosComplementBit(k -> data, k -> size);
RawByteArray *mpintK = malloc(sizeof(RawByteArray));
unsigned char *mpintKdata = malloc(twosK -> size + sizeof(uint32_t));
mpintLenNetworkOrder = htonl(twosK -> size);
memcpy(mpintKdata, &mpintLenNetworkOrder, sizeof(uint32_t));
memcpy(mpintKdata + sizeof(uint32_t), twosK -> data, twosK -> size);
mpintK -> data = mpintKdata;
mpintK -> size = twosK -> size + sizeof(uint32_t);
RawByteArray *verificationMessage = concatenateVerificationMessage(dhResponse -> hostKeyType, dhResponse -> hostKeyTypeLen, dhResponse -> publicKey, dhResponse -> publicKeyLen, mpintK -> data, mpintK -> size);
verifyServerSignature(dhResponse, verificationMessage);
// очистка
return 0;
}
Вот код, который выводит K:
RawByteArray *generateSharedKey(EVP_PKEY *pkey, EVP_PKEY *peerkey) {
RawByteArray *sharedKey = malloc(sizeof(RawByteArray));
assert(sharedKey != NULL);
EVP_PKEY_CTX *ctx;
unsigned char *skey;
size_t skeylen;
// Инициализация контекста
ctx = EVP_PKEY_CTX_new(pkey, NULL);
// Инициализация деривации
EVP_PKEY_derive_init(ctx);
// Установка открытого ключа пира
EVP_PKEY_derive_set_peer(ctx, peerkey);
// Определение длины буфера для общего секрета
EVP_PKEY_derive(ctx, NULL, &skeylen);
// Выделение памяти для общего секрета
skey = OPENSSL_malloc(skeylen);
assert(skey != NULL);
// Деривация общего секрета
EVP_PKEY_derive(ctx, skey, &skeylen);
// Отладочный вывод
printf("skeylen: %zu\n", skeylen);
printf("ОБЩИЙ КЛЮЧ!!\n");
for (int i = 0; i < skeylen; i++) {
printf("%02x ", skey[i]);
}
printf("\n");
// Сохранение общего секрета
sharedKey->data = skey;
sharedKey->size = skeylen;
// Очистка
EVP_PKEY_CTX_free(ctx);
// нужно использовать OPENSSL free для sharedKey -> data, так как для его создания использовалась OSSL malloc
return sharedKey;
}
Ниже приведен вывод от sendDiffieHellmanExchange()
:
открытый ключ (e)
длина ключа: 256
3f 94 6e 9f 44 c6 0c 3e ac 78 c4 06 e7 37 ce ab 50 f2 a6 4e e5 72 a7 5f 0d 64 50 47 81 77 9c 3b df 8b e6 1a 73 0a e5 4e e2 15 9e cb 50 ab 3f f3 c0 61 12 75 0d 13 67 aa c0 f7 b2 d6 04 0d e7 fb 88 1b d8 0d be 67 da a6 bb fc 48 b7 fd 1a 91 5b 9d 7d aa 18 21 8b f1 07 6a 41 12 88 e9 da 39 a9 76 6b c3 c0 4a 38 67 9c 34 e6 60 1e 90 7e 9c 2d a1 ca fe 01 0d 05 70 53 19 d8 8d 4f ec a5 bd 14 84 c9 0f cb 8a 0c cf 12 a5 51 7b 29 c6 b8 7d de 44 a2 4d 20 01 b5 b7 54 97 ce 6e 6f 2d ce 3e 26 9f 24 82 95 fd 13 af 5c 79 df e5 b2 f8 dd 6c aa ac 95 e6 c4 fe 58 d9 09 91 f0 25 2e 65 82 59 b8 de 2c 49 e7 e8 3f 46 b3 33 91 02 aa 46 8a fb 58 bb 09 b1 fa 67 d5 b6 32 6c 71 66 a3 0a fb 92 52 f1 7e 0b d8 e3 d5 6a 69 ff 2c 13 f6 e2 a0 46 17 7c c3 44 21 20 5b 31 47 7c b6 2b d2 75 5d 90 a0
Закрытый ключ DH: (2048 бит)
закрытый ключ:
4d:5a:58:8a:47:15:c8:25:47:95:4f:3a:62:50:a4:
78:cc:5f:81:c1:f2:00:99:7c:15:f8:da:f7:09:f7:
dc:aa:45:dd:6a:12:f4:a3:7a:5d:24:ec:bc:de:fc:
51:db:ec:41:eb:2f:6d:24:b4:8f:30:54:91:ea:97:
73:10:63:2b:9f:ad:96:20:94:ad:54:d5:22:c7:40:
3f:23:6b:7f:c4:99:63:33:4d:6f:ab:1c:16:4c:9b:
93:a7:8b:64:0d:11:1f:26:11:54:1b:7c:aa:aa:6e:
7b:13:8c:26:1f:b6:95:eb:12:3b:19:b3:d5:c1:07:
1b:97:79:82:16:ee:31:9c:8d:22:60:2f:bf:b2:77:
ef:fa:87:08:52:f8:0c:20:9f:7c:94:45:61:02:09:
7a:0c:ee:14:be:01:04:21:b7:14:00:14:40:22:43:
fc:11:8f:ab:b2:37:85:59:7b:b0:a4:f3:8f:53:ee:
d3:72:5d:14:b9:31:0d:0d:ac:55:ff:bb:04:14:4d:
2a:c3:41:70:e3:71:6d:5d:5f:8c:69:45:26:e9:84:
6e:43:e8:6e:57:23:6a:91:52:1b:0e:41:83:7f:d5:
81:06:84:d7:55:df:80:20:7a:af:21:3b:a3:ba:fb:
62:23:93:14:e2:78:39:29:42:d3:b1:c6:dd:b5:e8:
58
открытый ключ:
00:a0:90:5d:75:d2:2b:b6:7c:47:31:5b:20:21:44:
c3:7c:17:46:a0:e2:f6:13:2c:ff:69:6a:d5:e3:d8:
0b:7e:f1:52:92:fb:0a:a3:66:71:6c:32:b6:d5:67:
fa:b1:09:bb:58:fb:8a:46:aa:02:91:33:b3:46:3f:
e8:e7:49:2c:de:b8:59:82:65:2e:25:f0:91:09:d9:
58:fe:c4:e6:95:ac:aa:6c:dd:f8:b2:e5:df:79:5c:
af:13:fd:95:82:24:9f:26:3e:ce:2d:6f:6e:ce:97:
54:b7:b5:01:20:4d:a2:44:de:7d:b8:c6:29:7b:51:
a5:12:cf:0c:8a:cb:0f:c9:84:14:bd:a5:ec:4f:8d:
d8:19:53:70:05:0d:01:fe:ca:a1:2d:9c:7e:90:1e:
60:e6:34:9c:67:38:4a:c0:c3:6b:76:a9:39:da:e9:
88:12:41:6a:07:f1:8b:21:18:aa:7d:9d:5b:91:1a:
fd:b7:48:fc:bb:a6:da:67:be:0d:d8:1b:88:fb:e7:
0d:04:d6:b2:f7:c0:aa:67:13:0d:75:12:61:c0:f3:
3f:ab:50:cb:9e:15:e2:4e:e5:0a:73:1a:e6:8b:df:
3b:9c:77:81:47:50:64:0d:5f:a7:72:e5:4e:a6:f2:
50:ab:ce:37:e7:06:c4:78:ac:3e:0c:c6:44:9f:6e:
94:3f
P:
00:ff:ff:ff:ff:ff:ff:ff:ff:68:aa:ac:8a:5a:8e:
72:15:10:05:fa:98:18:26:d2:15:e5:6a:95:ea:7c:
49:95:39:18:17:58:95:f6:cb:2b:de:c9:52:4c:6f:
f0:5d:c5:b5:8f:a2:07:ec:a2:83:27:9b:03:86:0e:
18:2c:77:9e:e3:3b:ce:36:2e:46:5e:90:32:7c:21:
18:ca:08:6c:74:f1:04:98:bc:4a:4e:35:0c:67:6d:
96:96:70:07:29:d5:9e:bb:52:85:20:56:f3:62:1c:
96:ad:a3:dc:23:5d:65:83:5f:cf:24:fd:a8:3f:16:
69:9a:d3:55:1c:36:48:da:98:05:bf:63:a1:b8:7c:
00:c2:3d:5b:e4:ec:51:66:28:49:e6:1f:4b:7c:11:
24:9f:ae:a5:9f:89:5a:fb:6b:38:ee:ed:b7:06:f4:
b6:5c:ff:0b:6b:ed:37:a6:e9:42:4c:f4:c6:7e:5e:
62:76:b5:85:e4:45:c2:51:6d:6d:35:e1:4f:37:14:
5f:f2:6d:0a:2b:30:1b:43:3a:cd:b3:19:95:ef:dd:
04:34:8e:79:08:4a:51:22:9b:13:3b:a6:be:0b:02:
74:cc:67:8a:08:4e:02:29:d1:1c:dc:80:8b:62:c6:
c4:34:c2:68:21:a2:da:0f:c9:ff:ff:ff:ff:ff:ff:
ff:ff
G: 2 (0x2)
Успешная отправка инициализации DH! Количество отправленных байтов: 272
FFFFF:
19 35 18 1d 9c 70 62 0e 55 4d 5b 2b 8f e2 e1 18 5b 22 23 68 d4 d2 5a 12 04 68 e3 49 a5 6c a2 a4 dc ce 58 ff a0 80 90 59 9b ce 25 55 21 9b 8a 68 c7 f0 37 56 5c c4 4b 72 02 80 d2 7c 20 31 2f 6d 04 fb 43 a2 ea 0b c7 e6 aa 2a ef 94 77 9a 89 e0 64 97 80 64 e2 6b cd 67 aa 23 0f 8f 90 41 15 b3 26 fa b4 d7 b1 53 2e 42 30 e3 b4 40 ae 23 f2 94 36 93 10 10 fb 32 f3 3c 49 25 3f 80 e2 44 00 dc 95 f4 9d fb 27 8f 24 3b 18 dc 2a 83 59 f2 31 8d 05 4a 3d 0c 40 f5 be 88 d7 c4 7c 1d 0a 3b 6c 11 8b f1 04 a0 66 a8 e2 17 f6 5c 94 0c 90 04 78 39 89 7e 4a 65 69 db d2 db 3e 59 85 62 a0 a8 b4 46 3b 31 00 ca 8b b9 9e 32 49 2d c5 b6 f9 19 c4 ca f7 ff bb 5b c5 1f 3d 99 0a cd f8 6f 3f 89 d3 af 5f fb 10 98 76 28 a1 6c a6 f5 fa 75 53 1f 44 ee 9c 71 a8 ab 53 ea 57 89 bc 56 5d 8e d6 d7 b2 f0
Открытый ключ DH: (2048 бит)
открытый ключ:
00:f0:b2:d7:d6:8e:5d:56:bc:89:57:ea:53:ab:a8:
71:9c:ee:44:1f:53:75:fa:f5:a6:6c:a1:28:76:98:
10:fb:5f:af:d3:89:3f:6f:f8:cd:0a:99:3d:1f:c5:
5b:bb:ff:f7:ca:c4:19:f9:b6:c5:2d:49:32:9e:b9:
8b:ca:00:31:3b:46:b4:a8:a0:62:85:59:3e:db:d2:
db:69:65:4a:7e:89:39:78:04:90:0c:94:5c:f6:17:
e2:a8:66:a0:04:f1:8b:11:6c:3b:0a:1d:7c:c4:d7:
88:be:f5:40:0c:3d:4a:05:8d:31:f2:59:83:2a:dc:
18:3b:24:8f:27:fb:9d:f4:95:dc:00:44:e2:80:3f:
25:49:3c:f3:32:fb:10:10:93:36:94:f2:23:ae:40:
b4:e3:30:42:2e:53:b1:d7:b4:fa:26:b3:15:41:90:
8f:0f:23:aa:67:cd:6b:e2:64:80:97:64:e0:89:9a:
77:94:ef:2a:aa:e6:c7:0b:ea:a2:43:fb:04:6d:2f:
31:20:7c:d2:80:02:72:4b:c4:5c:56:37:f0:c7:68:
8a:9b:21:55:25:ce:9b:59:90:80:a0:ff:58:ce:dc:
a4:a2:6c:a5:49:e3:68:04:12:5a:d2:d4:68:23:22:
5b:18:e1:e2:8f:2b:5b:4d:55:0e:62:70:9c:1d:18:
35:19
P:
00:ff:ff:ff:ff:ff:ff:ff:ff:68:aa:ac:8a:5a:8e:
72:15:10:05:fa:98:18:26:d2:15:e5:6a:95:ea:7c:
49:95:39:18:17:58:95:f6:cb:2b:de:c9:52:4c:6f:
f0:5d:c5:b5:8f:a2:07:ec:a2:83:27:9b:03:86:0e:
18:2c:77:9e:e3:3b:ce:36:2e:46:5e:90:32:7c:21:
18:ca:08:6c:74:f1:04:98:bc:4a:4e:35:0c:67:6d:
96:96:70:07:29:d5:9e:bb:52:85:20:56:f3:62:1c:
96:ad:a3:dc:23:5d:65:83:5f:cf:24:fd:a8:3f:16:
69:9a:d3:55:1c:36:48:da:98:05:bf:63:a1:b8:7c:
00:c2:3d:5b:e4:ec:51:66:28:49:e6:1f:4b:7c:11:
24:9f:ae:a5:9f:89:5a:fb:6b:38:ee:ed:b7:06:f4:
b6:5c:ff:0b:6b:ed:37:a6:e9:42:4c:f4:c6:7e:5e:
62:76:b5:85:e4:45:c2:51:6d:6d:35:e1:4f:37:14:
5f:f2:6d:0a:2b:30:1b:43:3a:cd:b3:19:95:ef:dd:
04:34:8e:79:08:4a:51:22:9b:13:3b:a6:be:0b:02:
74:cc:67:8a:08:4e:02:29:d1:1c:dc:80:8b:62:c6:
c4:34:c2:68:21:a2:da:0f:c9:ff:ff:ff:ff:ff:ff:
ff:ff
G: 2 (0x2)
skeylen: 256
ОБЩИЙ КЛЮЧ!!
59 e4 b4 05 4d 6a 0d 69 76 a2 57 66 8b 05 24 cf 99 cd a2 8c 2b b6 52 75 8c 0c 52 bc 0b 68 d7 18 a1 3a 15 3e 19 46 75 d6 27 27 38 fd ae c7 4a 15 ec 40 20 6b d9 e6 c4 80 01 2b d5 84 10 e5 72 1d 45 7e 66 9a 9b 13 e0 52 fb c5 e8 c5 14 82 48 4d 52 f4 1c e5 b1 81 06 eb fb a4 3e 0e c8 1f 5d 92 70 92 8a cb 73 f4 1f 4c a8 34 c6 7e 76 7d 6f 9f d3 13 fd 12 bd 2e 95 d7 bb 08 c7 fc 63 00 9a dd 5d 52 2c fb f8 30 e5 8f 8b 92 72 da 16 e6 b0 89 31 56 fc 61 a9 d6 29 5a 68 d1 af 7f 06 4d e4 54 f3 0a 4e 2a fa 7a cb 67 dc 74 e9 17 48 bd a2 8d 37 4b ce 04 11 5c e9 b2 e0 7c 3e 89 eb e5 2f ba f7 a2 1e 69 bf 4b 3c cc 6d 07 5a a8 28 c3 2f fe e8 dd 82 c7 f4 06 91 f2 b2 7f 32 bf c9 15 9b 2a d8 26 7b 62 ed fe fc 50 dd b7 68 5c 80 82 ac a6 de e2 bc e6 70 a4 3a 49 4d 24 a9 e1 9e 39 f8 bc
HASHED MESSAGE:
0b 89 fe 62 3b fc da e2 78 41 31 25 5f e9 fc 44 2f 57 90 17 05 73 3d 96 45 ad db 98 1a ab 6d 7e
Ошибка: проверка подписи сервера не прошла
Заметно, что K, который я здесь печатаю, не совпадает с K, который вычисляет сервер.
Есть ли что-то, что кажется неправильным в моей реализации?
Стоит ли мне отказаться от использования нового EVP API и вместо этого использовать более старую версию, используемую в файле kexdh.c из openSSH, поскольку она работает (я конкретно смотрю на функцию kex_dh_compute_key
)?
Если я пропустил какой-либо важный код, вот мой файл client.c.
Любые советы по улучшению чистоты кода/лучшего использования функций OSSL также будут оценены. Заранее спасибо.
Ответ или решение
Для корректного выполнения обмена ключами по методу Диффи-Хеллмана (DH) и в частности для вычисления общего секретного ключа (K) в своем клиенте SSH на C с использованием библиотеки OpenSSL, необходимо учитывать много тонкостей и деталей.
1. Основы протокола Диффи-Хеллмана
Протокол Диффи-Хеллмана позволяет двум сторонам генерировать общий секретный ключ по открытым данным. В вашем случае используется группа 14, основанная на 2048-битном простом числе, что предлагает хорошую степень безопасности.
2. Обзор вашего кода
Ваш код на первый взгляд выглядит хорошо, но есть несколько областей, в которых может происходить ошибка. Обратите внимание на следующие моменты:
2.1 Генерация параметров
Вы используете функции BN_get_rfc3526_prime_2048
и BN_set_word
для создания публичных параметров (p и g). Убедитесь, что эти параметры соответствуют тем, что ожидает сервер.
2.2 Обработка публичного ключа
Проверьте, что вы правильно преобразуете ваш публичный ключ eBN
в формат big-endian, и что он включает в себя дополнительные биты (например, биты знака), если это необходимо.
2.3 Импортирование публичного ключа сервера
Вы очищаете первый байт публичного ключа сервера f
, если он равен 0x00 и длина равна 257. Убедитесь, что это действительно соответствует спецификациям. Возможно, сервер вместо этого использует другую кодировку или формат.
3. Вычисление общего ключа (K)
Функция generateSharedKey
выглядит почти правильно, но проверьте следующее:
- Убедитесь, что контекст (
ctx
) инициализируется корректно и не содержит ошибок. - Проверьте, что
EVP_PKEY_derive_init
вызывается и возвращает успех. - Убедитесь, что передаете корректный публичный ключ сервера в
EVP_PKEY_derive_set_peer
.
4. Отладочный вывод
Сравните ваш вывод с выходными данными сервера. Убедитесь, что каждый этап обмена приводил к ожидаемым изменениям. В частности, у вас должно быть совпадение в длине общего ключа и самом массиве байтов.
5. Альтернативы и общее улучшение
Если вы по-прежнему испытываете трудности с API EVP, вернуться к более старой версии библиотеки как kexdh.c из OpenSSH может быть хорошей идеей. Однако, лучше всего приложить усилия для понимания текущей реализации, так как она предоставляет более актуальные подходы к безопасному программированию.
5.1 Используйте автоматическое управление памятью
Избегайте использования malloc
и free
везде, где это возможно; используйте функции управления памятью OpenSSL.
5.2 Чистота кода
Четко комментируйте каждую важную часть своего кода, особенно в тех местах, где происходит обработка различных форматов данных (например, преобразование между big-endian и little-endian) и работа с контекстом.
6. Заключение
Ошибки в вычислении общего ключа (K) или в процессе верификации сервера могут быть результатом неправильной обработки данных. Тщательная проверка каждого шага, правильная обработка форматов данных и учёт требований спецификации помогут в решении вашей проблемы. Не стесняйтесь задавать вопросы на форумах или в сообществе OpenSSL, если столкнётесь с мирными областями.
Конечная цель — убедиться, что обе стороны имеют одинаковый ключ K для дальнейшей использования в протоколе, а вас сопровождает уверенность в том, что реализация отвечает современным стандартам безопасности.