预处理器宏将十六进制字符串转换为字节数组

Mot*_*tla 3 c macros gcc preprocessor c-preprocessor

我已经在我的IDE中定义了一个AES-128密钥作为构建符号,因此它像这样调用GCC:

arm-none-eabi-gcc -D"AES_KEY=3B7116E69E222295163FF1CAA1681FAC" ...
Run Code Online (Sandbox Code Playgroud)

(相当于#define AES_KEY 3B7116E69E222295163FF1CAA1681FAC)

优点是相同的符号也可以作为参数自动传递给构建后的CLI脚本,该脚本使用此密钥加密编译的代码(例如,用于安全的固件更新)...

但是如何在代码中将此键存储为字节数组?我想定义一个执行转换的预处理器宏:

uint8_t aes_key[] = { SPLIT_MACRO(AES_KEY) };
Run Code Online (Sandbox Code Playgroud)

uint8_t aes_key[] = {0x3B, 0x71, 0x16, 0xE6, 0x9E, 0x22, 0x22, 0x95, ...};
Run Code Online (Sandbox Code Playgroud)

换句话说,GCC预处理器是否可以在2-char块中拆分密钥字符串并, 0x在它们之间添加" "?

Ste*_*ner 5

有点笨拙,但如果您事先知道密钥的长度,可以按如下方式处理:

  1. 定义一个HEXTONIBBLE将十六进制数字转换为数字的宏
  2. 定义一个HEXTOBYTE用于HEXTONIBBLE从十六进制中获取字节的宏
  3. 使用HEXTOBYTE参数化的correctrly 初始化您的数组

如果你KEY不是一个字符串的形式,即用双引号括起来,那么使用stringify-operator #(使用一个变量宏的技巧,以便在用作参数或另一个时扩展宏):

//           01234567890123456789012345678901
#define K    3B7116E69E222295163FF1CAA1681FAC

#define STRINGIFY_HELPER(A) #A
#define STRINGIFY(...) STRINGIFY_HELPER(__VA_ARGS__)

#define KEY  STRINGIFY(K)

#define HEXTONIBBLE(c) (*(c) >= 'A' ? (*(c) - 'A')+10 : (*(c)-'0'))

#define HEXTOBYTE(c) (HEXTONIBBLE(c)*16 + HEXTONIBBLE(c+1))

uint8_t aes_key[] = {
    HEXTOBYTE(KEY+0),
    HEXTOBYTE(KEY+2),
    HEXTOBYTE(KEY+4),
    HEXTOBYTE(KEY+6),
    HEXTOBYTE(KEY+8),
    HEXTOBYTE(KEY+10),
    HEXTOBYTE(KEY+12),
    HEXTOBYTE(KEY+14),
    HEXTOBYTE(KEY+16),
    HEXTOBYTE(KEY+18),
    HEXTOBYTE(KEY+20),
    HEXTOBYTE(KEY+22),
    HEXTOBYTE(KEY+24),
    HEXTOBYTE(KEY+26),
    HEXTOBYTE(KEY+28),
    HEXTOBYTE(KEY+30)
};

int main() {

    for (int i=0; i<sizeof(aes_key); i++) {
        printf("%02X ", aes_key[i]);
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出:

3B 71 16 E6 9E 22 22 95 16 3F F1 CA A1 68 1F AC 
Run Code Online (Sandbox Code Playgroud)

  • 这适用于C++(虽然我不确定编译器是否需要在编译时对其进行评估),但不能在C:"test.c:13:22:error:initializer元素不是常量". (2认同)
  • 您可以使其在函数内移动数组,以解决此处违反的规则(*具有静态存储持续时间的对象的初始值设定项中的所有表达式应为常量表达式或字符串文字。*,C99 §6.7。 8¶4); 再次注意,*理论上*这可能会延迟运行时的计算(尽管实际上 gcc 在编译时完全解决了它)。 (2认同)
  • 好的,所以,检查 gcc 和 clang 的标准是否正确;C99 §6.7.8 ¶4 要求使用常量表达式来初始化静态存储持续时间对象(其想法是它们的值应直接烘焙到可执行文件中);在 §6.6 ¶7 中解释了初始化器实际上需要*算术常量表达式*(或 NULL 或地址常量 +/- 整数常量表达式);算术常量表达式又只能包含整数/字符/fp/枚举常量和 sizeof 表达式作为操作数,因此没有字符串文字(可以在地址常量中使用,但是... (2认同)
  • ......只是为了得到一个地址; 不允许*实际*解除引用).所以,gcc在技术上是正确的; clang的行为也被允许,因为10说"实现可能接受其他形式的常量表达式".但是,依赖于此当然会使代码不可移植. (2认同)