How to implement HKDF with the Cryptographic library

The CMOX does not provide HMAC-based key derivation function (HKDF) utilities. However HKDF can be constructed based on the available CMOX HMAC services.

1 HKDF theory

Extract from rfc5869

2.  HMAC-based Key Derivation Function (HKDF)


2.1.  Notation


   HMAC-Hash denotes the HMAC function [HMAC] instantiated with hash
   function 'Hash'.  HMAC always has two arguments: the first is a key
   and the second an input (or message).  (Note that in the extract
   step, 'IKM' is used as the HMAC input, not as the HMAC key.)

   When the message is composed of several elements we use concatenation
   (denoted |) in the second argument; for example, HMAC(K, elem1 |
   elem2 | elem3).

   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
   "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
   document are to be interpreted as described in [KEYWORDS].

2.2.  Step 1: Extract


   HKDF-Extract(salt, IKM) -> PRK

   Options:
      Hash     a hash function; HashLen denotes the length of the
               hash function output in octets

   Inputs:
      salt     optional salt value (a non-secret random value);
               if not provided, it is set to a string of HashLen zeros.
      IKM      input keying material

   Output:
      PRK      a pseudorandom key (of HashLen octets)

   The output PRK is calculated as follows:

   PRK = HMAC-Hash(salt, IKM)

2.3.  Step 2: Expand


   HKDF-Expand(PRK, info, L) -> OKM

   Options:
      Hash     a hash function; HashLen denotes the length of the
               hash function output in octets

   Inputs:
      PRK      a pseudorandom key of at least HashLen octets
               (usually, the output from the extract step)
      info     optional context and application specific information
               (can be a zero-length string)
      L        length of output keying material in octets
               (<= 255*HashLen)

   Output:
      OKM      output keying material (of L octets)

   The output OKM is calculated as follows:

   N = ceil(L/HashLen)
   T = T(1) | T(2) | T(3) | ... | T(N)
   OKM = first L octets of T

   where:
   T(0) = empty string (zero length)
   T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
   T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
   T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
   ...

2 HKDF implementation example using HMAC-SHA256 Cryptographic library services

  /* Computed data buffer */
  uint8_t computed_hash[CMOX_SHA256_SIZE];
  uint8_t computed_PRK[CMOX_SHA256_SIZE];
  uint8_t computed_OKM[82];  /* L    = 82 */

  /* HMAC context handle */
  cmox_hmac_handle_t Hmac_Ctx;
  size_t computed_size;
  /* General mac context */
  cmox_mac_handle_t *mac_ctx;
  uint8_t t;
  uint32_t index;

  /* Initialize cryptographic library */
  if (cmox_initialize(NULL) != CMOX_INIT_SUCCESS) Error_Handler();

  /* 
   * Compute PRK = HMAC-Hash(salt, IKM)
   */
  retval = cmox_mac_compute(CMOX_HMAC_SHA256_ALGO,
                            IKM, sizeof(IKM),
                            salt, sizeof(salt),
                            NULL, 0,
                            computed_PRK,
                            CMOX_SHA256_SIZE,
                            &computed_size);

  /* Verify API returned value */
  if (retval != CMOX_MAC_SUCCESS) Error_Handler();

  /*
   * Compute N = ceil(L/HashLen)
   */
  N = (uint32_t)(L / CMOX_SHA256_SIZE);
  if ((N * CMOX_SHA256_SIZE) < L) N++;

  /*
   * Compute OKM = first L octets of T
   * where:
   * T(0) = empty string (zero length)
   * T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
   * T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
   * T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
   ...
   */
  
  mac_ctx = cmox_hmac_construct(&Hmac_Ctx, CMOX_HMAC_SHA256);
  if (mac_ctx == NULL) Error_Handler();

  index = 0;
  for (uint8_t i = 1; i <= N; i++)
  {
    retval = cmox_mac_init(mac_ctx);
    if (retval != CMOX_MAC_SUCCESS) Error_Handler();
    /* For the last iteration, tag length may be reduced to fit requested length L */
    if (i == N)
    {
      retval = cmox_mac_setTagLen(mac_ctx, L - index);
    }
    if (retval != CMOX_MAC_SUCCESS) Error_Handler();
    retval = cmox_mac_setKey(mac_ctx, computed_PRK, sizeof(computed_PRK));
    if (retval != CMOX_MAC_SUCCESS) Error_Handler();
    if (i > 1)
    {
      retval = cmox_mac_append(mac_ctx, computed_hash, CMOX_SHA256_SIZE);
      if (retval != CMOX_MAC_SUCCESS) Error_Handler();
    }
    retval = cmox_mac_append(mac_ctx, info, sizeof(info));
    if (retval != CMOX_MAC_SUCCESS) Error_Handler();
    retval = cmox_mac_append(mac_ctx, &i, 1);
    if (retval != CMOX_MAC_SUCCESS) Error_Handler();
    retval = cmox_mac_generateTag(mac_ctx, computed_hash, NULL);
    if (retval != CMOX_MAC_SUCCESS) Error_Handler();
    if (i == N)
    {
      memcpy(&computed_OKM[index], computed_hash, L - index);
      index = L;
    }
    else
    {
      memcpy(&computed_OKM[index], computed_hash, CMOX_SHA256_SIZE);
      index += CMOX_SHA256_SIZE;
    }
  }
  retval = cmox_mac_cleanup(mac_ctx);
  if (retval != CMOX_MAC_SUCCESS) Error_Handler();