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; }
测试问题描述
测试逻辑:
- 调用
ec_create生成原始密钥对 - 通过
ec_to_pub提取原始公钥数组 - 将公钥数组传入
ec_from_pub重建为key2实例 - 调用
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 个错误
问题原因与修复方案
核心错误点
ec_from_pub中分配的缓冲区未添加字符串终止符
分配了EC_PUB_LEN + 1字节的缓冲区,但仅填充了前EC_PUB_LEN个字节,最后一个字节未初始化。EC_POINT_hex2point要求输入是以\0结尾的字符串,读取到未初始化的缓冲区末尾时就触发了valgrind报告的未初始化内存错误。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数组的每字节转成字符当十六进制字符串输入,两边逻辑完全错位。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
相关产品推荐
相关产品推荐

