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

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

问题分析与解决方案

核心问题

EVP_PKEY_get_raw_public_key 并非适用于所有密钥类型,对于椭圆曲线(EC)密钥,该函数无法直接提取原始公钥,这是调用失败的根本原因。此外,代码未初始化OpenSSL错误字符串库,导致错误信息为空。

修复方案

1. 替换EC公钥提取逻辑

使用EC密钥专用接口提取原始公钥(未压缩格式):

  • 从EVP_PKEY中获取EC_KEY结构
  • 通过EC_GROUP和EC_POINT提取原始公钥字节流
  • 处理二进制公钥的打印(不能直接用%s,需转为十六进制)

2. 初始化OpenSSL错误库

添加错误库初始化代码,确保能获取有效错误信息。

修改后的完整代码

#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>

// My way of error-handling
typedef struct {
  int code;
  const char *msg;
} Ret;

#define RET                                                                    \
  (Ret) { .code = 0, .msg = "SUCCESS" } // 修正SUCCESS的code值,避免成功误判为失败


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

  size_t publicKeySize = PUBLIC_KEY_SIZE; 
  unsigned char publicKey[PUBLIC_KEY_SIZE];

  // 替换原有的EVP_PKEY_get_raw_public_key调用
  EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(pkey);
  if (ec_key == NULL) {
      ret = (Ret){1, "Failed to get EC_KEY from EVP_PKEY"};
      goto cleanup;
  }

  const EC_GROUP *group = EC_KEY_get0_group(ec_key);
  if (group == NULL) {
      ret = (Ret){1, "Failed to get EC_GROUP from EC_KEY"};
      EC_KEY_free(ec_key);
      goto cleanup;
  }

  // 计算未压缩公钥的长度
  size_t pub_len = EC_POINT_point2buf(group, EC_KEY_get0_public_key(ec_key), POINT_CONVERSION_UNCOMPRESSED, NULL, 0);
  if (pub_len == 0) {
      unsigned long err = ERR_get_error();
      char *errMsg = ERR_error_string(err, NULL);
      ret = (Ret){1, errMsg};
      EC_KEY_free(ec_key);
      goto cleanup;
  }

  if (pub_len > PUBLIC_KEY_SIZE) {
      ret = (Ret){1, "Public key buffer too small"};
      EC_KEY_free(ec_key);
      goto cleanup;
  }

  // 提取原始公钥到缓冲区
  if (EC_POINT_point2buf(group, EC_KEY_get0_public_key(ec_key), POINT_CONVERSION_UNCOMPRESSED, publicKey, &pub_len) == NULL) {
      unsigned long err = ERR_get_error();
      char *errMsg = ERR_error_string(err, NULL);
      ret = (Ret){1, errMsg};
      EC_KEY_free(ec_key);
      goto cleanup;
  }

  cout("Public key size: %zu bytes\n", pub_len);
  // 二进制公钥转十六进制打印
  cout("Public key (hex): ");
  for (size_t i = 0; i < pub_len; i++) {
      printf("%02x", publicKey[i]);
  }
  cout("\n");

  EC_KEY_free(ec_key);

cleanup:
    EVP_PKEY_free(pkey);
  return ret;
}

int main(void) {
  // 初始化OpenSSL错误库,确保错误信息正常输出
  OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
  ERR_load_crypto_strings();

  Ret ret = fn3();

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

关键修正点说明

  • 修正RET宏的code值:原代码中SUCCESS的code为1,会导致成功时误判为失败,改为0
  • 使用EC_KEY系列接口提取EC公钥,替代不兼容的EVP_PKEY_get_raw_public_key
  • 二进制公钥转为十六进制打印,避免%s因空字节截断输出
  • 添加OpenSSL错误库初始化,确保能获取详细错误信息

后续ECDH协议实现提示

对于ECDH密钥交换,可使用EVP_PKEY_derive函数:

  1. 接收对方公钥,构造为EVP_PKEY结构
  2. 创建EVP_PKEY_CTX上下文,设置为ECDH模式
  3. 调用EVP_PKEY_derive生成共享密钥

内容的提问来源于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:13