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

OpenSSL EC_KEY 公钥重建后校验失败及内存报错问题

基于secp256k1的EC_KEY密钥处理实现问题

项目核心逻辑

开发基于secp256k1椭圆曲线的密钥处理程序,核心流程为:

  • 先生成完整的EC_KEY密钥对
  • 再利用已生成密钥的公钥数据,重建独立的EC_KEY实例

相关代码实现

头文件 hblk_crypto.h

#pragma once
#include <stdint.h>
#include <openssl/sha.h>
#include <openssl/ec.h>
#include <openssl/obj_mac.h>

# define EC_CURVE   NID_secp256k1
# define EC_PUB_LEN 65

uint8_t *sha256(int8_t const *s, size_t len, uint8_t digest[SHA256_DIGEST_LENGTH]);
EC_KEY *ec_create(void);
uint8_t *ec_to_pub(EC_KEY const *key, uint8_t pub[EC_PUB_LEN]);
EC_KEY *ec_from_pub(uint8_t const pub[EC_PUB_LEN]);

密钥生成函数 ec_create

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "hblk_crypto.h"

EC_KEY *ec_create()
{
  EC_KEY *key = EC_KEY_new();
  EC_GROUP *group;
  if(key == NULL)
    return NULL;
  group = EC_GROUP_new_by_curve_name(EC_CURVE);
  if(group == NULL)
    return NULL;
  EC_KEY_set_group(key, group);
  EC_KEY_generate_key(key);
  EC_GROUP_free(group);
  return key;
}

公钥转EC_KEY实例函数 ec_from_pub

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "hblk_crypto.h"

EC_KEY *ec_from_pub(uint8_t const pub[EC_PUB_LEN]){
  EC_KEY *key = NULL;
  EC_POINT *pubKey = NULL;
  const EC_GROUP *group = NULL;
  BN_CTX *ctx;
  char *buf;
  int i;

  buf = malloc(sizeof(char)*(EC_PUB_LEN + 1));
  for(i=0;i<EC_PUB_LEN;i++){
    buf[i] = (char) pub[i];
  }

  key = EC_KEY_new_by_curve_name(EC_CURVE);
  if(key == NULL)
    return NULL;

  group = EC_KEY_get0_group(key);
  if(group == NULL){
    EC_KEY_free(key);
    return NULL;
  }

  pubKey = EC_POINT_new(group);
  ctx = BN_CTX_new();
  EC_POINT_hex2point(group, buf, pubKey, ctx);

  if(pubKey == NULL){
    EC_KEY_free(key);
    BN_CTX_free(ctx);
    return NULL;
  }

  EC_KEY_set_public_key(key, pubKey);
  EC_POINT_free(pubKey);
  BN_CTX_free(ctx);
  free(buf);
  return key;
}

EC_KEY实例转公钥数组函数 ec_to_pub

#include <stdlib.h>
#include <stdio.h>
#include "hblk_crypto.h"

uint8_t *ec_to_pub(EC_KEY const *key, uint8_t pub[EC_PUB_LEN]){
  const EC_POINT *publicKey;
  EC_GROUP *group;
  BN_CTX *ctx;
  point_conversion_form_t form;
  char *pKey;
  int i;

  if(key == NULL)
    return NULL;
  publicKey = EC_KEY_get0_public_key(key);
  if(publicKey == NULL)
    return NULL;
  group = EC_GROUP_new_by_curve_name(EC_CURVE);
  if(group == NULL){
    return NULL;
  }
  form = POINT_CONVERSION_UNCOMPRESSED;
  ctx = BN_CTX_new();
  pKey = EC_POINT_point2hex(group,publicKey,form,ctx);
  if(pKey == NULL){
    EC_GROUP_free(group);
    BN_CTX_free(ctx);
    return NULL;
  }
  for(i=0;i<EC_PUB_LEN;i++){
    pub[i] = (uint8_t)pKey[i];
  }

  EC_GROUP_free(group);
  BN_CTX_free(ctx);
  free(pKey);
  return pub;
}

测试问题描述

测试逻辑:

  1. 调用ec_create生成原始密钥对
  2. 通过ec_to_pub提取原始公钥数组
  3. 将公钥数组传入ec_from_pub重建为key2实例
  4. 调用EC_KEY_check_key(key2)校验时返回失败,程序输出密钥验证失败

Valgrind 运行输出

vagrant@vagrant-ubuntu-trusty-64:~/holbertonschool-zero_day/crypto$ valgrind --track-origins=yes ./ec_from_pub-test
==3688== Memcheck,内存错误检测工具
==3688== 版权所有 (C) 2002-2013,由 Julian Seward 等贡献,遵循 GNU GPL 协议发布
==3688== 使用 Valgrind-3.10.1 和 LibVEX,使用 -h 查看版权信息
==3688== 执行命令: ./ec_from_pub-test
==3688==
EC 密钥对创建成功
公钥: 3034383830393442454635453132433639443935353130433146353542333845384146454142353943423333373438433830313230364545364331353531453742
==3688== 条件跳转或移动依赖于未初始化的值
==3688==    调用栈: 0x4F21BA5: ossl_ctype_check (位于 /usr/local/lib/libcrypto.so.1.1)
==3688==    调用栈: 0x4EFE122: BN_hex2bn (位于 /usr/local/lib/libcrypto.so.1.1)
==3688==    调用栈: 0x4F587A0: EC_POINT_hex2point (位于 /usr/local/lib/libcrypto.so.1.1)
==3688==    调用栈: 0x401133: ec_from_pub (位于 /home/vagrant/holbertonschool-zero_day/crypto/ec_from_pub-test)
==3688==    调用栈: 0x400EA0: main (位于 /home/vagrant/holbertonschool-zero_day/crypto/ec_from_pub-test)
==3688==  未初始化值来源: 堆内存分配
==3688==    分配位置: 0x4C2AB80: malloc (位于 /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3688==    分配位置: 0x401080: ec_from_pub (位于 /home/vagrant/holbertonschool-zero_day/crypto/ec_from_pub-test)
==3688==    分配位置: 0x400EA0: main (位于 /home/vagrant/holbertonschool-zero_day/crypto/ec_from_pub-test)
==3688==
==3688== 使用了大小为8字节的未初始化值
==3688==    调用栈: 0x4F21BB1: ossl_ctype_check (位于 /usr/local/lib/libcrypto.so.1.1)
==3688==    调用栈: 0x4EFE122: BN_hex2bn (位于 /usr/local/lib/libcrypto.so.1.1)
==3688==    调用栈: 0x4F587A0: EC_POINT_hex2point (位于 /usr/local/lib/libcrypto.so.1.1)
==3688==    调用栈: 0x401133: ec_from_pub (位于 /home/vagrant/holbertonschool-zero_day/crypto/ec_from_pub-test)
==3688==    调用栈: 0x400EA0: main (位于 /home/vagrant/holbertonschool-zero_day/crypto/ec_from_pub-test)
==3688==  未初始化值来源: 堆内存分配
==3688==    分配位置: 0x4C2AB80: malloc (位于 /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3688==    分配位置: 0x401080: ec_from_pub (位于 /home/vagrant/holbertonschool-zero_day/crypto/ec_from_pub-test)
==3688==    分配位置: 0x400EA0: main (位于 /home/vagrant/holbertonschool-zero_day/crypto/ec_from_pub-test)
==3688==
Key2 校验失败
==3688==
==3688== 堆内存统计:
==3688==     程序退出时仍占用: 2688 字节,共 68 个块
==3688==   总内存操作: 2963 次分配,2895 次释放,共分配 114115 字节
==3688==
==3688== 内存泄漏统计:
==3688==    确定泄漏: 160 字节,共 2 个块
==3688==    间接泄漏: 2528 字节,共 66 个块
==3688==      可能泄漏: 0 字节,共 0 个块
==3688==      仍可访问: 0 字节,共 0 个块
==3688==      已抑制: 0 字节,共 0 个块
==3688== 使用 --leak-check=full 可查看泄漏的详细信息
==3688==
==3688== 使用 -v 参数可查看错误和抑制的统计数量
==3688== 错误汇总: 共 2 个上下文的 2 个错误,抑制了 0 个上下文的 0 个错误

问题原因与修复方案

核心错误点

  1. ec_from_pub中分配的缓冲区未添加字符串终止符
    分配了EC_PUB_LEN + 1字节的缓冲区,但仅填充了前EC_PUB_LEN个字节,最后一个字节未初始化。EC_POINT_hex2point要求输入是以\0结尾的字符串,读取到未初始化的缓冲区末尾时就触发了valgrind报告的未初始化内存错误。
  2. ec_to_pub和ec_from_pub的数据格式完全不匹配
    ec_to_pub调用EC_POINT_point2hex返回的是十六进制字符的ASCII字符串(比如公钥的第一个字节是04,对应ASCII字符串就是'0'和'4'两个字符),但EC_PUB_LEN定义为65,对应的是未压缩公钥的原始二进制长度:1字节前缀04 + 32字节X坐标 + 32字节Y坐标,正好65字节。
    当前ec_to_pub把ASCII字符串的前65个字符的ASCII值存到pub数组里,完全不是原始的公钥二进制数据;反过来ec_from_pub把pub数组的每字节转成字符当十六进制字符串输入,两边逻辑完全错位。
  3. ec_create存在冗余操作
    手动新建EC_GROUP再绑定到EC_KEY上,实际上用EC_KEY_new_by_curve_name(EC_CURVE)一步就能完成,还避免了不必要的内存操作。

修复后的代码

修复后的ec_to_pub

直接导出原始二进制公钥,无需经过十六进制字符串转换:

#include <stdlib.h>
#include <stdio.h>
#include "hblk_crypto.h"

uint8_t *ec_to_pub(EC_KEY const *key, uint8_t pub[EC_PUB_LEN]){
  const EC_POINT *publicKey;
  const EC_GROUP *group;
  BN_CTX *ctx;
  size_t pub_len;

  if(key == NULL)
    return NULL;
  publicKey = EC_KEY_get0_public_key(key);
  if(publicKey == NULL)
    return NULL;
  // 直接复用EC_KEY自带的group,无需新建
  group = EC_KEY_get0_group(key);
  if(group == NULL)
    return NULL;

  ctx = BN_CTX_new();
  if(ctx == NULL)
    return NULL;

  // 直接将公钥点序列化为原始二进制,匹配65字节的长度要求
  pub_len = EC_POINT_point2oct(
    group, publicKey, 
    POINT_CONVERSION_UNCOMPRESSED,
    pub, EC_PUB_LEN, ctx
  );

  BN_CTX_free(ctx);
  if(pub_len != EC_PUB_LEN)
    return NULL;

  return pub;
}

修复后的ec_from_pub

直接读取原始二进制公钥,无需转换为十六进制字符串:

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "hblk_crypto.h"

EC_KEY *ec_from_pub(uint8_t const pub[EC_PUB_LEN]){
  EC_KEY *key = NULL;
  EC_POINT *pubKey = NULL;
  const EC_GROUP *group = NULL;
  BN_CTX *ctx;

  // 一步新建带secp256k1曲线的EC_KEY
  key = EC_KEY_new_by_curve_name(EC_CURVE);
  if(key == NULL)
    return NULL;

  group = EC_KEY_get0_group(key);
  if(group == NULL){
    EC_KEY_free(key);
    return NULL;
  }

  pubKey = EC_POINT_new(group);
  if(pubKey == NULL){
    EC_KEY_free(key);
    return NULL;
  }

  ctx = BN_CTX_new();
  if(ctx == NULL){
    EC_POINT_free(pubKey);
    EC_KEY_free(key);
    return NULL;
  }

  // 直接从原始二进制读取公钥点
  if(!EC_POINT_oct2point(group, pubKey, pub, EC_PUB_LEN, ctx)){
    BN_CTX_free(ctx);
    EC_POINT_free(pubKey);
    EC_KEY_free(key);
    return NULL;
  }

  if(!EC_KEY_set_public_key(key, pubKey)){
    BN_CTX_free(ctx);
    EC_POINT_free(pubKey);
    EC_KEY_free(key);
    return NULL;
  }

  EC_POINT_free(pubKey);
  BN_CTX_free(ctx);
  return key;
}

优化后的ec_create

#include <stdlib.h>
#include "hblk_crypto.h"

EC_KEY *ec_create()
{
  // 直接新建带secp256k1曲线的EC_KEY,无需手动创建group
  EC_KEY *key = EC_KEY_new_by_curve_name(EC_CURVE);
  if(key == NULL)
    return NULL;

  if(!EC_KEY_generate_key(key)){
    EC_KEY_free(key);
    return NULL;
  }
  return key;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.27 08:54:05