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
相关产品推荐
相关产品推荐

