如何以与此脚本相同的方式在 C 或 C++ 中生成对称密钥?

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

我正在为基于 ESP32 的固件实施 Azure DPS(设备配置服务)。

目前我使用的bash脚本如下(其中KEY是DPS注册组的主键,REG_ID是给定ESP的注册设备ID 运行s 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

我在platformIO中使用Arduino平台

如何翻译 C/C++ 中的脚本?

[更新] 我不能 运行 openSSL 的原因:我需要从实际设备 MAC 地址生成对称密钥以便从 DPS 获取凭证然后获准连接到 IoT 中心 - 我 运行 在基于 EPS32 的自定义 PCB 上。没有shell。没有OS.

我设法通过使用 bed 库(可从两个 ESP32/Arduino 平台获得)来做到这一点。

这是我在 Arduino 平台上的实现:

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

从 mathematical/algorithmical 的角度来看,您提出了一个非常有趣的问题。因此,为了好玩,决定从头开始实现所有 sub-algorithms,几乎不依赖于标准 C++ 库。

我的所有算法都基于维基百科,并在其文章中描述得很好 SHA-256, HMAC, Base64 (and Whosebug), Hex

我专门从头开始编写整个代码,几乎不依赖标准 C++ 库。现在只有两个 headers <cstdint> 用于实现所有大小的整数 u8, u16, u32, i32, u64, i64.

<string>仅用于实现Heap分配。您也可以在我的 HeapMem class 中轻松实现此堆分配,或者通过删除我代码第一行的 using String = std::string;(和 #include <string>)并使用 built-in heap-allocated String 的 Arduino 如果它有 built-in 个。

Header <iostream> 仅在代码片段的最后几行中使用,仅用于将结果输出到控制台,以便 Whosebug 访问我的 运行 程序而无需外部依赖。当然可以删除此控制台输出。

除了主要算法外,我还必须实现自己的 classes ArrayVectorStrTupleHeapMem 以re-implement标准C++库的基本概念。还必须实现 MemSet()MemCpy()MemCmp()StrLen()Move() 等标准库函数。

您可能也注意到我从未在代码中使用异常,特别是如果您有 disabled/non-supporting 它们。我实现了类似于 Result from Rust 语言的特殊 Result<T> 模板,而不是异常。此模板用于 return/check 更正整个函数堆栈的错误结果。

所有算法(Sha256、Hmac、Base64)都通过简单的测试用例进行测试,参考向量取自互联网。您想要的最终 SignSha256() 功能也通过几个测试用例针对您的参考 bash OpenSSL 脚本进行了测试。

重要!。不要直接在生产代码中使用此代码片段,因为它没有经过很好的测试并且可能包含一些错误。仅用于教育目的或在使用前彻底测试。

代码片段非常大,大约 32 KiB,超过了 Whosebug post 大小的限制(即 30 000 个符号),因此我通过两个外部服务共享代码片段 - GodBolt (点击Try it online! link),这里也可以在线测试,GitHub Gist服务仅供download/view使用。

源代码在这里

Try it online on GodBolt!

GitHub Gist