Репликация получения ключа hkdf2 в C

Я пытаюсь создать производный ключ из моего общего секретного и общего ключа. Я делал это раньше в Java и Python, где это довольно простой процесс, однако кажется, что в C для этого требуется еще несколько шагов.

Вот мой рабочий Java-код:

    HKDFBytesGenerator hkdf2 = new HKDFBytesGenerator(digest);
    hkdf2.init(new HKDFParameters(sharedSecret, mobileDeviceEphemeralPublicKey, info));

    byte[] sharedKey = new byte[48];
    hkdf2.generateBytes(sharedKey, 0, sharedKey.length);

Где находится информация:

[-63, 20, 124, -15, -40, -14, -53, 23, 15, 24, -38, 39, 34, 28, -43, 47, 46, 37, 107, -15, 4, -92, -117, -45, 75, -7, -35, -80, -82, 78, 109, 95, -38, 40, -5, -123, 85, -79, 33, -96, 17, -86, 106, -121, -17, -52, -7, -111, -77, 11, 105, -117, 110, 12, -87, 19, 44, -74, 58, 95, 87, -98, -90, 19, 2, 13, 76, 41, 3, 44, 54, 4, 76, -11, 27, -82, 70, -107, -1, 36, -114, 41, -46, 122, -60, -111, -111, -14, -105, -85, -113, -74, 118, 7, -127, -100, 115, 37, -13, 71, -113, 48, 68, 2, 32, 83, -33, 54, -35, -77, 4, -95, -55, -128, -66, 126, -46, -23, -43, -63, -41, 66, 92, 104, 85, -19, 113, 85, -9, 95, -97, -4, -58, 89, 88, -76, -36, 2, 32, 119, -73, 86, -9, -45, 21, -66, -56, -101, -107, 118, -78, 51, -48, -118, 91, -14, 51, -40, -115, -7, -5, 58, -60, -75, 22, 29, 40, 13, -110, 94, 36]

mobileDeviceEphemeralPublicKey — это:

[3, -13, 114, -79, -98, 38, 121, -52, -109, -35, 111, 100, 43, 78, -96, 42, -28, -32, -75, -87, 83, 22, -22, -105, 69, -109, 47, -23, 127, -62, -128, -109, -99]

И поделился секретом:

[-27, -24, -42, 64, 23, 20, 36, 24, 46, 45, 57, -60, -53, 5, 106, 82, -128, -126, 66, 62, 42, -60, -72, -75, -90, -38, 54, -97, 98, -67, 29, 59]

И в результате мой общий ключ

-85, 26, -57, -120, -55, -12, 58, 22, -36, -86, 40, -21, -69, -109, -86, 2, 2, 26, 87, 97, 51, -56, -16, -71, 95, -115, 9, -45, -98, 125, 35, -12, 100, 41, -68, -68, 9, -40, 43, 74, 108, 27, -101, -98, -67, 85, 119, -108

Мой сломанный код C:

#include <stdio.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
#include <openssl/ec.h>
#include <openssl/err.h>
#include <openssl/sha.h>
#include <openssl/kdf.h>
#include <openssl/hmac.h>

void set_error(char **error, const char *message) {
   if (error != NULL) {
       if (*error == NULL) {
           *error = strdup(message);
       }
   }
}

void concatenateByteArrays(unsigned char *result, int *result_len, unsigned char *arrays[], const int lengths[], int num_arrays) {
   int pos = 0;
   for (int i = 0; i < num_arrays; i++) {
       memcpy(result + pos, arrays[i], lengths[i]);
       pos += lengths[i];
   }
   *result_len = pos;
}

unsigned char *extractSharedKey(
   unsigned char *mobileDeviceEphemeralPublicKey, int mobileDeviceEphemeralPublicKey_len,
   unsigned char *terminalNonce, int terminalNonce_len,
   unsigned char *mobileDeviceNonce, int mobileDeviceNonce_len,
   unsigned char *collectorId, int collectorId_len,
   unsigned char *terminalEphemeralPublicKeyCompressed, int terminalEphemeralPublicKeyCompressed_len,
   unsigned char *signedData, int signedData_len,
   unsigned char *sharedSecret, int sharedSecret_len,
   int sharedKey_len, // Added parameter to specify the desired key length
   char **error)
{
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
if (!pctx) {
    set_error(error, "Failed to create HKDF context");
    return NULL;
}

   unsigned char info[1024]; // Ensure this buffer is large enough for your data.
   int info_len;
   unsigned char *arrays[] = {
       terminalNonce, mobileDeviceNonce, collectorId,
       terminalEphemeralPublicKeyCompressed, signedData
   };
   int lengths[] = {
       terminalNonce_len, mobileDeviceNonce_len, collectorId_len,
       terminalEphemeralPublicKeyCompressed_len, signedData_len
   };

   // Concatenate the arrays into 'info'
   concatenateByteArrays(info, &info_len, arrays, lengths, 5);

   printf("Info: ");
   for (int i = 0; i < info_len; i++) {
       printf("%02x ", info[i]);
   }
   printf("\n");

   
   // Allocate memory for the shared key
   unsigned char *sharedKey = (unsigned char *)malloc(sharedKey_len);
   if (sharedKey == NULL) {
       set_error(error, "Failed to allocate memory for shared key");
       return NULL;
   }

   printf("mobile key: ");
   for (int i = 0; i < mobileDeviceEphemeralPublicKey_len; i++) {
       printf("%02x ", mobileDeviceEphemeralPublicKey[i]);
   }

   printf("\n");
   printf("shared secret: ");
   for (int i = 0; i < sharedSecret_len; i++) {
       printf("%02x ", sharedSecret[i]);
   }
   if (EVP_PKEY_derive_init(pctx) <= 0) {
       ERR_print_errors_fp(stderr);
       set_error(error, "Failed to initialize HKDF");
       EVP_PKEY_CTX_free(pctx);
       free(sharedKey);
       return NULL;
   }

   if (EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha256()) <= 0) {
       ERR_print_errors_fp(stderr);
       set_error(error, "Failed to set HKDF hash function");
       EVP_PKEY_CTX_free(pctx);
       free(sharedKey);
       return NULL;
   }

   if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, mobileDeviceEphemeralPublicKey, mobileDeviceEphemeralPublicKey_len) <= 0) {
       ERR_print_errors_fp(stderr);
       set_error(error, "Failed to set HKDF salt");
       EVP_PKEY_CTX_free(pctx);
       free(sharedKey);
       return NULL;
   }

   if (EVP_PKEY_CTX_set1_hkdf_key(pctx, sharedSecret, sharedSecret_len) <= 0) {
       ERR_print_errors_fp(stderr);
       set_error(error, "Failed to set HKDF key");
       EVP_PKEY_CTX_free(pctx);
       free(sharedKey);
       return NULL;
   }

   if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) {
       ERR_print_errors_fp(stderr);
       set_error(error, "Failed to add HKDF info");
       EVP_PKEY_CTX_free(pctx);
       free(sharedKey);
       return NULL;
   }

  printf("Derived shared key: ");
   for (int i = 0; i < sharedKey_len; i++) {
       printf("%02x ", sharedKey[i]);
   }
   printf("\n");

   printf("Shared key length: %zu\n", sharedKey_len);

   printf("pctx: %p\n", pctx);
   if (EVP_PKEY_derive(pctx, sharedKey, &sharedKey_len) <= 0) {
       ERR_print_errors_fp(stderr);
       set_error(error, "HKDF operation failed");
       EVP_PKEY_CTX_free(pctx);
       free(sharedKey);
       return NULL;
   }

   EVP_PKEY_CTX_free(pctx);
   return sharedKey; // Return the derived shared key
}

EC_KEY* getPublicKeyFromBytes(const unsigned char* pubKeyBytes, size_t length, char** error) {
   EC_KEY* key = NULL;
   EC_POINT* point = NULL;
   const EC_GROUP* group = NULL;

   // Initialize OpenSSL
   OpenSSL_add_all_algorithms();

   // Create a new EC_KEY structure for the specified curve
   key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
   if (!key) {
       set_error(error, "Error creating EC_KEY structure.");
       return NULL;
   }

   // Get the group used by the curve
   group = EC_KEY_get0_group(key);

   // Create an EC_POINT object from the bytes
   point = EC_POINT_new(group);
   if (!point) {
       set_error(error, "Error creating EC_POINT.");
       EC_KEY_free(key);
       return NULL;
   }

   if (EC_POINT_oct2point(group, point, pubKeyBytes, length, NULL) != 1) {
       set_error(error, "Error converting bytes to EC_POINT.");
       EC_POINT_free(point);
       EC_KEY_free(key);
       return NULL;
   }

   // Set the public key point in the EC_KEY structure
   if (EC_KEY_set_public_key(key, point) != 1) {
       set_error(error, "Error setting public key.");
       EC_POINT_free(point);
       EC_KEY_free(key);
       return NULL;
   }

   // Free the point object
   EC_POINT_free(point);

   // Return the created key
   return key;
}


int main() {
unsigned char mobileDeviceEphemeralPublicKey[] = {3, 243, 114, 177, 158, 38, 121, 204, 147, 221, 111, 100, 43, 78, 160, 42, 228, 224, 181, 169, 83, 22, 230, 151, 69, 147, 47, 233, 127, 194, 128, 147, 157};
unsigned char terminalNonce[] = {193, 20, 124, 241, 216, 242, 203, 23, 15, 24, 218, 39, 34, 28, 213, 47, 46, 37, 107, 241, 4, 164, 139, 211, 75, 249, 221, 176, 174, 78, 109, 95};
unsigned char mobileDeviceNonce[] = {218, 40, 251, 133, 85, 177, 33, 160, 17, 170, 106, 135, 239, 204, 249, 145, 179, 11, 105, 139, 110, 12, 169, 19, 44, 182, 58, 95, 87, 158, 166, 19};
unsigned char collectorId[] = {2, 13, 76, 41};
unsigned char terminalEphemeralPublicKeyCompressed[] = {3, 44, 54, 4, 76, 245, 27, 174, 70, 149, 255, 36, 142, 41, 210, 122, 196, 145, 145, 242, 151, 171, 143, 182, 118, 7, 129, 156, 115, 37, 243, 71, 143};
unsigned char signedData[] = {48, 68, 2, 32, 83, 223, 54, 221, 179, 4, 161, 201, 128, 190, 126, 210, 233, 213, 193, 215, 66, 92, 104, 85, 237, 113, 85, 247, 95, 159, 252, 198, 89, 88, 180, 220, 2, 32, 119, 183, 86, 247, 211, 21, 190, 200, 155, 149, 118, 178, 51, 208, 138, 91, 242, 51, 216, 141, 249, 251, 58, 196, 181, 22, 29, 40, 13, 146, 94, 36};
unsigned char sharedSecret[] = {229, 232, 214, 64, 23, 20, 36, 24, 46, 45, 57, 196, 203, 5, 106, 82, 128, 130, 66, 62, 42, 196, 184, 181, 166, 218, 54, 159, 98, 189, 29, 59};
   char *error = NULL;
   int sharedKeyLen = 48; // Desired length of the shared key
   unsigned char *sharedKey = extractSharedKey(
       mobileDeviceEphemeralPublicKey, sizeof(mobileDeviceEphemeralPublicKey),
       terminalNonce, sizeof(terminalNonce),
       mobileDeviceNonce, sizeof(mobileDeviceNonce),
       collectorId, sizeof(collectorId),
       terminalEphemeralPublicKeyCompressed, sizeof(terminalEphemeralPublicKeyCompressed),
       signedData, sizeof(signedData),
       sharedSecret, sizeof(sharedSecret),
       sharedKeyLen, &error);

   if (sharedKey) {
       printf("Shared Key: ");
       for (int i = 0; i < sharedKeyLen; i++) {
           printf("%02X", sharedKey[i]);
       }
       printf("\n");
       free(sharedKey); // Free the allocated memory for the shared key
   } else {
       printf("Error extracting shared key: %s\n", error);
       free(error); // Free the allocated memory for the error message
   }

   return 0;
}

Однако я получаю сообщение об ошибке «Операция HKDF не удалась». Будем очень признательны за любые идеи, я потратил некоторое время, пытаясь разобраться в этом 😅

Я не могу воспроизвести проблему. На моей машине я получаю тот же результат, что и код Java (при идентичных входных параметрах). Однако опубликованный код C не является полным, поэтому мне пришлось добавить недостающие части. Вполне возможно, что ошибка именно в этих частях. Поэтому, пожалуйста, опубликуйте полный исполняемый код, включая инициализацию с тестовыми данными (mobileDeviceEphemeralPublicKey, sharedSecret, info), см. MCVE.

Topaco 17.06.2024 16:41

Например, как вы создали pctx? Например. EVP_PKEY_CTX_new_id() в сочетании с неправильным идентификатором может привести к сбою операции HKDF.

Topaco 17.06.2024 16:52

Привет @Topaco, я обновил свой код, и теперь ты можешь увидеть его целиком. Надеюсь, это поможет. Можете ли вы показать код, который у вас работает?

Tom Rowbotham 17.06.2024 18:19

Недавно опубликованный код работает и на моей машине, я не могу воспроизвести ваше сообщение об ошибке. Однако он дает другой результат, чем исходный код. Это связано с несоответствием mobileDeviceEphemeralPublicKey. Старый код имел значение -22 (или 234) по индексу 22, а новый код имел значение 230 (или -26). Если это исправлено, возвращается результат старого кода. Помимо этого несоответствия данных, я не могу найти никакой проблемы.

Topaco 17.06.2024 19:49

Какая версия openssl?

Allan Wind 18.06.2024 01:06

Интересно, тогда я не понимаю, почему с моей стороны это не работает. Я использую I/opt/homebrew/Cellar/openssl@3/3.3.0/include -L/opt/homebrew/Cellar/openssl@3/3.3.0/lib -lcrypto @Allan Wind

Tom Rowbotham 18.06.2024 10:08

@Topaco, какую версию openSSL ты использовал?

Tom Rowbotham 18.06.2024 10:13

Я тестировал OpenSSL 3.2.1 [30 января 2024 г.].

Topaco 18.06.2024 12:07

Что вы порекомендуете, если эта проблема не исчезнет? Я понял, что не могу понизить версию OpenSSL, используя доморощенный вариант.

Tom Rowbotham 18.06.2024 14:51

У меня этот код также работает с OpenSSL 3.3.1 [4 июня 2024 г.]. Я не уверен, что это общая проблема с 3.3.0. Возможно, у вас неправильно установлен OpenSSL или в вашей среде возникла проблема несовместимости.

Topaco 18.06.2024 16:05

Спасибо за всю помощь, я очень ценю это! Чтобы внести ясность: вы вообще не меняли код, чтобы получить этот результат, а выполнили команду типа: gcc -o extractsharedkeys extractsharedkeys.c -I/opt/homebrew/Cellar/openssl@3/3.3.1/include - L/opt/homebrew/Cellar/openssl@3/3.3.1/lib -lcrypto? Я посмотрю на свое окружение

Tom Rowbotham 18.06.2024 16:30

Я работаю в Windows и использую компилятор MS C/C++, а не компилятор GNU/GCC. Опубликованный код запускается без изменений (кроме getPublicKeyFromBytes(), который для теста не нужен).

Topaco 18.06.2024 17:17

Я предлагаю вам сравнить рабочее решение с нерабочим и попутно проверить частичные данные. Рассмотрите возможность использования inttypes.h вместо собственных типов. Вам не хватает #include <string.h> и define_POSIX_C_SOURCE 200809L. Вы используете %zu для печати общего ключа_len, но это %ul. В моей системе EC_KEY_new_by_curve_name, EC_KEY_get0_group, EC_KEY_free выдает предупреждения об устаревании.

Allan Wind 18.06.2024 18:40

... ‘EVP_PKEY_derive' expects a size_t *` но вы передаете unsigned long *.

Allan Wind 18.06.2024 18:47

Не используйте void * (из malloc()) в C. Это требуется в C++.

Allan Wind 18.06.2024 18:51

В extractSharedKey() я вижу, что вы выполняете malloc sharedKey, но не видите, что он инициализируется перед печатью «Производного общего ключа». Что мне не хватает?

Allan Wind 18.06.2024 18:53

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

Allan Wind 18.06.2024 18:58

Я внес все предложенные вами изменения, и теперь все работает! Большое спасибо @AllanWind

Tom Rowbotham 19.06.2024 11:05

Я написал предложения в качестве ответа. Если бы это был я, я бы в следующий раз выяснил, какой из них является основной причиной. Я буду рад пересмотреть свой ответ с этой информацией. Пожалуйста, примите ответ, нажав галочку рядом с ним, чтобы мы знали, что все готово.

Allan Wind 19.06.2024 16:37
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
19
114
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Поскольку нам не удалось воспроизвести это, похоже, проблема с переносимостью или неопределенное поведение:

  1. В Linux вам нужны #define POSIX_C_SOURCE 200809L и #include <string.h> для strdup() и memcpy().

  2. Не используйте void * из malloc(). Это необязательно и может скрыть проблему с типом.

  3. EVP_PKEY_derive() ожидает, что третий параметр keylen будет size_t *, но у вас есть int sharedKey_len.

  4. Для переносимого кода отдавайте предпочтение типам в inttypes.h. Нативные типы могут различаться по размеру на разных платформах.

  5. В extractSharedKey() вы не инициализировали sharedKey перед печатью (вероятно, оставьте это; и когда вы это сделаете, вы увидите, что после предыдущего поля необходимо напечатать новую строку):

     printf("Derived shared key: ");
     for (int i = 0; i < sharedKey_len; i++) {
         printf("%02x ", sharedKey[i]);
     }
     printf("\n");
    

Другие вопросы по теме