cencryptioncryptography

Replicating hkdf2 key derivation in C


I am trying to create a derived key from my shared secret and shared key. I have done this before, in Java and Python, where it is a fairly straight forward process, however, it seems that in C this requires a few more steps.

Here is my working Java code:

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

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

Where info is:

[-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 is:

[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]

And sharedSecret:

[-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]

And as a result my sharedKey is

-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

My broken C code is:

#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;
}

However, I get the error "HKDF operation failed". Any ideas would be greatly appreciated, I've spent a while trying to crack this 😅


Solution

  • As we were not able to reproduce it appears to be a portability issue or undefined behavior:

    1. On linux you need to #define POSIX_C_SOURCE 200809L and #include <string.h> for strdup() and memcpy().

    2. Don't cast the void * from malloc(). It's unnecessary and may hide a type problem.

    3. EVP_PKEY_derive() expects the 3rd parameter keylen to be a size_t * but you have int sharedKey_len.

    4. Prefer the types in inttypes.h for portable code. Native types may vary in size across platforms.

    5. In extractSharedKey() you haven't initialized sharedKey before you print it (presumably leave this out; and when you do you will see that need to print a newline after the previous field):

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