'How to generate a symmetric key in C or C++ the same way this script does?

I am implementing Azure DPS (device provisioning service) for my ESP32-based firmware.

The bash script I use so far is as follows (where KEY is the primary key of the DPS enrolment group and REG_ID is the registration device Id for the given ESP it runs on):

#!/bin/sh

KEY=KKKKKKKKK
REG_ID=RRRRRRRRRRR

keybytes=$(echo $KEY | base64 --decode | xxd -p -u -c 1000)

echo -n $REG_ID | openssl sha256 -mac HMAC -macopt hexkey:$keybytes -binary | base64

I use the Arduino platform in platformIO.

How to translate the script in C/C++?

[UPDATE] The reason why I can't run openSSL: I need to generate the symmetric key from the actual device MAC address in order to obtain the credential from DPS and then be granted to connect to IoT Hub - I run on an EPS32-based custom PCB. No shell. No OS.



Solution 1:[1]

I manage to do it by using bed library (which is available from both ESP32/Arduino platforms).

Here is my implementation for the Arduino platform:

#include <mbedtls/md.h>         // mbed tls lib used to sign SHA-256

#include <base64.hpp>           // Densaugeo Base64 version 1.2.0 or 1.2.1


/// Returns the SHA-256 signature of [dataToSign] with the key [enrollmentPrimaryKey]
/// params[in]: dataToSign The data to sign (for our purpose, it is the registration ID (or the device ID if it is different)
/// params[in]: enrollmentPrimaryKey The group enrollment primary key.
/// returns The SHA-256 base-64 signature to present to DPS.
/// Note: I use mbed to SHA-256 sign.
String Sha256Sign(String dataToSign, String enrollmentPrimaryKey){
  /// Length of the dataToSign string
  const unsigned dataToSignLength = dataToSign.length();
  /// Buffer to hold the dataToSign as a char[] buffer from String.
  char dataToSignChar[dataToSignLength + 1];
  /// String to c-style string (char[])
  dataToSign.toCharArray(dataToSignChar, dataToSignLength + 1);

  /// The binary decoded key (from the base 64 definition)
  unsigned char decodedPSK[32];

  /// Encrypted binary signature
  unsigned char encryptedSignature[32];

  /// Base 64 encoded signature
  unsigned char encodedSignature[100];
  
  Serial.printf("Sha256Sign(): Registration Id to sign is: (%d bytes) %s\n", dataToSignLength, dataToSignChar);
  Serial.printf("Sha256Sign(): DPS group enrollment primary key is: (%d bytes) %s\n", enrollmentPrimaryKey.length(), enrollmentPrimaryKey.c_str());


  // Need to base64 decode the Preshared key and the length
  const unsigned base64DecodedDeviceLength = decode_base64((unsigned char*)enrollmentPrimaryKey.c_str(), decodedPSK);
  Serial.printf("Sha256Sign(): Decoded primary key is: (%d bytes) ", base64DecodedDeviceLength);

  for(int i= 0; i<base64DecodedDeviceLength; i++) {
    Serial.printf("%02x ", (int)decodedPSK[i]);
  }
  Serial.println();
  
  // Use mbed to sign
  mbedtls_md_type_t mdType = MBEDTLS_MD_SHA256;
  mbedtls_md_context_t hmacKeyContext;    

  mbedtls_md_init(&hmacKeyContext);
  mbedtls_md_setup(&hmacKeyContext, mbedtls_md_info_from_type(mdType), 1);
  mbedtls_md_hmac_starts(&hmacKeyContext, (const unsigned char *) decodedPSK, base64DecodedDeviceLength);
  mbedtls_md_hmac_update(&hmacKeyContext, (const unsigned char *) dataToSignChar, dataToSignLength);
  mbedtls_md_hmac_finish(&hmacKeyContext, encryptedSignature);
  mbedtls_md_free(&hmacKeyContext);
  
  Serial.print("Sha256Sign(): Computed hash is: ");

  for(int i= 0; i<sizeof(encryptedSignature); i++) {
    Serial.printf("%02x ", (int)encryptedSignature[i]);
  }
  Serial.println();
  // base64 decode the HMAC to a char
  encode_base64(encryptedSignature, sizeof(encryptedSignature), encodedSignature);

  Serial.printf("Sha256Sign(): Computed hash as base64: %s\n", encodedSignature);

  // creating the real SAS Token
  return String((char*)encodedSignature);
}

Solution 2:[2]

You have a very interesting question from mathematical/algorithmical point of view. So just for fun decided to implement ALL sub-algorithms of it from scratch, without almost NO dependacy on standard C++ library.

All algorithms of me are based on Wikipedia and described well in its articles SHA-256, HMAC, Base64 (and StackOverflow), Hex.

I made whole my code specifically from scratch and without almost NO dependency on std C++ library. Only two headers used right now <cstdint> for implementing all sized integers u8, u16, u32, i32, u64, i64.

And <string> is used only to implement Heap allocations. Also you can easily implement this heap allocations inside my HeapMem class, or by removing using String = std::string; (and #include <string>) on first lines of my code and using built-in heap-allocated String of Arduino if it has built-in one.

Header <iostream> is used only in few last lines of code snippet, only to output result to console, so that StackOverflow visitors my run program without external dependencies. This console output may be removed of course.

Besides main algorithms I had to implement my own classes Array, Vector, Str, Tuple, HeapMem to re-implement basic concepts of standard C++ library. Also standard library function like MemSet(), MemCpy(), MemCmp(), StrLen(), Move() had to be implemented.

You may notice too that I never used exceptions in code, specifically if you have disabled/non-supporting them. Instead of exceptions I implemented special Result<T> template that resembles Result from Rust language. This template is used to return/check correct and error results from whole stack of functions.

All algorithms (Sha256, Hmac, Base64) are tested by simple test cases with reference vectors taken from internet. Final SignSha256() function that you desired is also tested by several test cases against your reference bash OpenSSL script.

Important!. Don't use this code snippet directly inside production code, because it is not very well tested and might contain some errors. Use it Only for educational purposes or test it thourughly before using.

Code snippet is very large, around 32 KiB, bigger that limit of StackOverflow post size (which is 30 000 symbols), so I'm sharing code snippet through two external services - GodBolt (click Try it online! link), where you can also test it online, and GitHub Gist service for download/view only.

SOURCE CODE HERE

Try it online on GodBolt!

GitHub Gist

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Stéphane de Luca
Solution 2