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

OpenSSL调用EVP_PKEY_get_raw_public_key获取原始公钥失败求助

解决EVP_PKEY_get_raw_public_key调用失败及错误信息无效问题

问题根源分析

你的代码存在三个核心问题:

  • 错误码逻辑倒置:RET宏定义中code=1表示成功,但main函数中用if(ret.code)判断失败,导致即使操作成功也会触发错误提示。
  • OpenSSL库未初始化:未加载加密算法和错误字符串表,导致ERR_get_error()无法获取有效错误信息,返回空错误码。
  • 原始公钥处理错误:EC原始公钥是二进制格式,直接用%s输出会因空字节截断,且未正确处理缓冲区大小。

修复步骤

1. 修正错误码逻辑

将成功状态的错误码改为0(符合C语言惯例,0表示成功,非0表示失败):

#define RET (Ret) { .code = 0, .msg = "SUCCESS" }

2. 初始化OpenSSL库

在main函数开头添加库初始化代码,确保错误信息和算法可用:

int main(void) {
    // 初始化OpenSSL库
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    ERR_clear_error(); // 清除初始错误栈

    Ret ret = fn3();
    // ... 后续代码不变
}

3. 正确提取并输出EC原始公钥

EVP_PKEY_get_raw_public_key可以用于EC密钥,但需要注意:

  • 传入的publicKeySize需要初始化为缓冲区的实际大小,函数会返回实际需要的长度。
  • 原始公钥是二进制数据,必须用十六进制格式打印,不能直接用字符串输出。

或者,也可以通过EC_KEY接口直接提取公钥坐标,更直观:

// 从EVP_PKEY中获取EC_KEY指针
EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(pkey);
if (ec_key == NULL) {
    // 错误处理
}
// 获取EC_GROUP
const EC_GROUP *group = EC_KEY_get0_group(ec_key);
// 获取公钥点
const EC_POINT *pub_point = EC_KEY_get0_public_key(ec_key);
// 计算原始公钥所需长度(未压缩格式)
size_t raw_len = EC_POINT_point2oct(group, pub_point, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL);
unsigned char raw_pub[raw_len];
// 提取原始公钥
EC_POINT_point2oct(group, pub_point, POINT_CONVERSION_UNCOMPRESSED, raw_pub, raw_len, NULL);
// 十六进制打印
cout("Raw public key (hex): ");
for (size_t i=0; i<raw_len; i++) {
    printf("%02x", raw_pub[i]);
}
cout("\n");
// 释放EC_KEY(因为用了get1,需要手动释放)
EC_KEY_free(ec_key);

完整修复后的代码示例

#include <openssl/types.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>

#include <openssl/dh.h>
#include <openssl/ec.h>
#include <openssl/evp.h>

// 错误处理结构体
typedef struct {
    int code;
    const char *msg;
} Ret;

// 成功状态:code=0,失败状态code非0
#define RET (Ret) { .code = 0, .msg = "SUCCESS" }

#define cout(...) printf(__VA_ARGS__)
#define err(...) fprintf(stderr, __VA_ARGS__)
#define RED "\x1B[31m"
#define NORM "\033[0m"

#define PUBLIC_KEY_SIZE 1000

Ret fn3() {
    Ret ret = RET;
    const char *curveName = "secp521r1";
    EVP_PKEY *pkey = EVP_EC_gen(curveName);
    if (NULL == pkey) {
        ret = (Ret){1, "Unable to generate key"};
        goto cleanup;
    }

    EVP_PKEY_print_public_fp(stdout, pkey, 1, NULL);

    int keySize = EVP_PKEY_get_size(pkey);
    cout("Key size is %d\n", keySize);

    // 方式1:使用EVP_PKEY_get_raw_public_key提取原始公钥
    size_t publicKeySize = PUBLIC_KEY_SIZE; 
    unsigned char publicKey[PUBLIC_KEY_SIZE];

    if (EVP_PKEY_get_raw_public_key(pkey, publicKey, &publicKeySize) <= 0) {
        unsigned long err_code = ERR_get_error();
        char errMsg[256];
        ERR_error_string_n(err_code, errMsg, sizeof(errMsg));
        ret = (Ret){1, errMsg};
        goto cleanup;
    }

    // 十六进制打印原始公钥(二进制不能用%s输出)
    cout("Public key size: %zu bytes\nRaw public key (hex): ", publicKeySize);
    for (size_t i = 0; i < publicKeySize; i++) {
        printf("%02x", publicKey[i]);
    }
    cout("\n");

    // 方式2:使用EC_KEY接口提取(可选,更直观)
    EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(pkey);
    if (ec_key != NULL) {
        const EC_GROUP *group = EC_KEY_get0_group(ec_key);
        const EC_POINT *pub_point = EC_KEY_get0_public_key(ec_key);
        size_t raw_len = EC_POINT_point2oct(group, pub_point, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL);
        unsigned char raw_pub[raw_len];
        EC_POINT_point2oct(group, pub_point, POINT_CONVERSION_UNCOMPRESSED, raw_pub, raw_len, NULL);
        
        cout("\nAlternative raw public key (hex): ");
        for (size_t i=0; i<raw_len; i++) {
            printf("%02x", raw_pub[i]);
        }
        cout("\n");
        EC_KEY_free(ec_key);
    }

cleanup:
    EVP_PKEY_free(pkey);
    return ret;
}

int main(void) {
    // 初始化OpenSSL库
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    ERR_clear_error();

    Ret ret = fn3();

    if (ret.code != 0) {
        err(RED "[%s:%s:%d] Failed\n\t%s\n" NORM, __FILE__, __func__, __LINE__,
            ret.msg);
    } else {
        cout("%s\n", ret.msg);
    }

    // 清理错误栈
    ERR_free_strings();
    return ret.code;
}

额外说明

  • 对于P2P聊天室的ECDH密钥交换,传输公钥时建议使用DER格式的ASN.1编码(通过i2d_PUBKEY函数生成),因为它包含曲线信息,对等方可以直接解析恢复公钥,无需预先约定曲线。示例代码:
    // 生成DER格式公钥
    unsigned char der_pub[PUBLIC_KEY_SIZE];
    int der_len = i2d_PUBKEY(pkey, &der_pub);
    // 传输der_pub和der_len给对等方
    // 对等方解析:d2i_PUBKEY
    
  • 始终确保在程序退出前清理OpenSSL资源,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 01:37:34