You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用OpenSSL生成HMAC token时出现未知段错误的求助

解决OpenSSL HMAC()调用段错误问题

问题背景

尝试为Mongoose WS添加自定义用户访问令牌,使用OpenSSL库的HMAC函数生成验证哈希,但调用HMAC()时持续出现段错误。已验证密钥、令牌的长度有效性,通过backtrace定位错误未取得明显进展。

相关代码如下:

#include "token.h"

#define TK_SIZE 32
#define HASH_SIZE 32
#define PKEY_PATH "mysecretkey.pem"

int get_pkey(unsigned char **key, size_t *key_len) {
    FILE* pkey_file = fopen(PKEY_PATH, "r");
    if (!pkey_file) return 0;

    fseek(pkey_file, 0, SEEK_END);
    *key_len = ftell(pkey_file);
    fseek(pkey_file, 0, SEEK_SET);

    *key = (unsigned char*) malloc(*key_len);
    if (!(*key)) {
        fclose(pkey_file);
        return 0;
    }

    if (fread(*key, 1, *key_len, pkey_file) != *key_len) {
        free(*key);
        fclose(pkey_file);
        return 0;
    }

    fclose(pkey_file);
    return 1;
}

int get_tk_hash(char** tk_hash) {
    unsigned char tk[TK_SIZE];
    unsigned char hash[HASH_SIZE];
    unsigned char* key = NULL;
    size_t key_len = 0;
    unsigned int hashlen = HASH_SIZE;

    // 获取密钥
    if (!get_pkey(&key, &key_len)) goto err;

    // 生成32字节令牌
    if (!gen_32byte_token(tk)) goto err;

    // 生成令牌的HMAC哈希
    if (!HMAC(EVP_sha256(), key, key_len, tk, TK_SIZE, hash, &hashlen)) goto err;

    // 将令牌和哈希转换为十六进制
    char tk_to_hex[TK_SIZE * 2 + 1];
    uchar_to_hex(tk, TK_SIZE, tk_to_hex);
    char hash_to_hex[HASH_SIZE * 2 + 1];
    uchar_to_hex(hash, HASH_SIZE, hash_to_hex);

    // 组合令牌+哈希,用点分隔
    char hex[sizeof(tk_to_hex) + 1 + sizeof(hash_to_hex)] = "";
    strcat(hex, tk_to_hex);
    strcat(hex, ".");
    strcat(hex, hash_to_hex);

    *tk_hash = (char*) malloc(strlen(hex) + 1);
    if (*tk_hash == NULL) goto err;

    strcpy(*tk_hash, hex);
    free(key);  // 使用后释放密钥
    return 1;

err:
    if (key) free(key);
    return 0;
}

int gen_32byte_token(unsigned char* tk) {
    if (RAND_bytes(tk, TK_SIZE) != 1) {
        return 0;
    }
    return 1;
}

错误原因分析

  • 密钥读取方式错误:HMAC需要的是原始字节流形式的对称密钥,但当前代码直接读取整个PEM格式文件的内容,包含-----BEGIN PRIVATE KEY-----这类文本头、尾以及换行符,这些额外字符会被当作密钥的一部分传入HMAC函数,可能导致内部内存访问错误。
  • OpenSSL未初始化:未调用OpenSSL的初始化函数(如OPENSSL_init_ssl()或OPENSSL_add_all_algorithms()),会导致EVP_sha256()返回空指针,调用HMAC()时使用空指针必然引发段错误。
  • 局部数组未完全初始化:char hex[sizeof(tk_to_hex) + 1 + sizeof(hash_to_hex)] = "";仅将数组第一个元素设为\0,其余元素为未初始化的垃圾值,虽然strcat会从\0开始追加,但存在潜在的内存安全风险,极端情况下可能干扰后续操作。

修复方案

1. 正确读取HMAC密钥

如果使用的是对称密钥,建议直接存储原始字节(而非PEM格式),修改get_pkey函数用二进制模式读取:

int get_pkey(unsigned char **key, size_t *key_len) {
    FILE* pkey_file = fopen(PKEY_PATH, "rb"); // 二进制模式读取避免换行符转换
    if (!pkey_file) return 0;

    fseek(pkey_file, 0, SEEK_END);
    *key_len = ftell(pkey_file);
    fseek(pkey_file, 0, SEEK_SET);

    *key = (unsigned char*) malloc(*key_len);
    if (!(*key)) {
        fclose(pkey_file);
        return 0;
    }

    if (fread(*key, 1, *key_len, pkey_file) != *key_len) {
        free(*key);
        fclose(pkey_file);
        return 0;
    }

    fclose(pkey_file);
    return 1;
}

若必须使用PEM格式,需解析去掉头尾文本和换行,提取原始密钥字节(注意HMAC不需要非对称私钥,建议改用对称密钥存储)。

2. 初始化OpenSSL

在程序入口处添加OpenSSL初始化代码:

#include <openssl/ssl.h>

int main() {
    // 初始化OpenSSL算法和字符串
    OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL);
    OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
    
    // 后续业务代码...
}

3. 修正局部数组初始化

将hex数组完全初始化为0,避免未初始化内存问题:

char hex[sizeof(tk_to_hex) + 1 + sizeof(hash_to_hex)];
memset(hex, 0, sizeof(hex));
strcat(hex, tk_to_hex);
strcat(hex, ".");
strcat(hex, hash_to_hex);

4. 验证EVP_sha256()返回值

在调用HMAC()前检查哈希算法对象是否有效:

const EVP_MD* md = EVP_sha256();
if (!md) goto err;
if (!HMAC(md, key, key_len, tk, TK_SIZE, hash, &hashlen)) goto err;

修正后的关键代码片段

// 程序入口初始化
#include <openssl/ssl.h>

int main() {
    OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL);
    OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
    
    // 调用get_tk_hash生成令牌...
}

// 修正后的get_tk_hash函数
int get_tk_hash(char** tk_hash) {
    unsigned char tk[TK_SIZE];
    unsigned char hash[HASH_SIZE];
    unsigned char* key = NULL;
    size_t key_len = 0;
    unsigned int hashlen = HASH_SIZE;

    if (!get_pkey(&key, &key_len)) goto err;
    if (!gen_32byte_token(tk)) goto err;

    const EVP_MD* md = EVP_sha256();
    if (!md) goto err;
    if (!HMAC(md, key, key_len, tk, TK_SIZE, hash, &hashlen)) goto err;

    char tk_to_hex[TK_SIZE * 2 + 1];
    uchar_to_hex(tk, TK_SIZE, tk_to_hex);
    char hash_to_hex[HASH_SIZE * 2 + 1];
    uchar_to_hex(hash, HASH_SIZE, hash_to_hex);

    char hex[sizeof(tk_to_hex) + 1 + sizeof(hash_to_hex)];
    memset(hex, 0, sizeof(hex));
    strcat(hex, tk_to_hex);
    strcat(hex, ".");
    strcat(hex, hash_to_hex);

    *tk_hash = (char*) malloc(strlen(hex) + 1);
    if (*tk_hash == NULL) goto err;

    strcpy(*tk_hash, hex);
    free(key);
    return 1;

err:
    if (key) free(key);
    return 0;
}

内容的提问来源于stack exchange,提问作者kliam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 05:22:35