前面实验已经生成了ECC私钥, 现在做从ECC私钥(内容为公私钥对, 里面既有私钥信息, 也有公钥信息)导出ECC公钥.
实验对应的命令行为
openssl ec -in ecc_priv_key.pem -pubout -out ecc_pub_key_s.pem
单步调试, 发现命令行实现用的OSSL_STORE_open_ex(), 这个很难受, 没办法移植. 里面没有openssl API可用.
还好运气不错, 前面实验有从buffer load key的实现, 改了一下, 可以正常从buffer载入到私钥的EVP_PKEY*
然后就可以按照openssl命令行的实验进行移植了, 机智:P
如果没有前面做的那些实验, 直接从命令行实现迁移实现, 那就难了(关键调用. e.g. 官方从文件载入私钥(用的API内部完全看不出载入buffer时的openssl API调用), 我是从buffer载入私钥, 没有交集啊). 只能说运气还不错.
最后程序导出的公钥和命令行导出的公钥, 只有回车的区别, 用起来是一样的.
/*!
* \file exp021_export_pubkey_from_ecc_priv_key.cpp
* \note openssl3.2 - exp - export ecc pubkey from ecc priv key
* 对应的命令 openssl ec -in ecc_priv_key.pem -pubout -out ecc_pub_key_s.pem
* 现在的实现和命令行实现并不一致, 无法从命令行实现中完全移植, 主要是从buffer中载入私钥这块
* 命令行实现是 OSSL_STORE_open_ex(), 里面没用可以参考的openssl API调用
* 换成了前面实验得到的load_priv_key(), 用OSSL_DECODER_from_bio()得到EVP_PKEY*, 后续就一致了
*/
#include "my_openSSL_lib.h"
#include
#include
#include
#include
#include
#include "CMemHookRec.h"
#include "ecc_priv_key_s.h"
#include
#include
#include
#include
void my_openssl_app();
bool export_ecc_pub_key_from_ecc_priv_key(const UCHAR* pBuf, int lenBuf, unsigned char*& pdata, size_t& pdata_len);
EVP_PKEY* load_priv_key(bool isPrivkeyBuffer, const char* key_type, OSSL_LIB_CTX* libctx, const char* pBufPrivKey, int lenPrivKey, const char* passphrase);
int main(int argc, char** argv)
{
setvbuf(stdout, NULL, _IONBF, 0); // 清掉stdout缓存, 防止调用printf时阻塞
mem_hook();
my_openssl_app();
mem_unhook();
return 0;
}
void my_openssl_app()
{
unsigned char* pdata = NULL;
size_t pdata_len = 0;
FILE* fp = NULL;
size_t sz_wr = 0;
do {
if (!export_ecc_pub_key_from_ecc_priv_key((const UCHAR*)ucAry_ecc_priv_key_s, sizeof(ucAry_ecc_priv_key_s), pdata, pdata_len))
{
break;
}
// 实际应用中, 就可以那取到的公钥数据干活了(e.g. 转成公钥的EVP_PKEY*)
// 现在将公钥数据保存成文件, 然后和用命令行从同一个私钥导出的公钥比对一下, 看看是否一样
fp = fopen("ecc_pub_key_export_by_app.pem", "wb");
if (NULL != fp)
{
sz_wr = fwrite(pdata, sizeof(char), pdata_len, fp);
assert(sz_wr == pdata_len);
fclose(fp);
fp = NULL;
// fc /B ecc_pub_key_s.pem ecc_pub_key_export_by_app.pem
// 除了回车, 内容一摸一样
// openssl.exe 导出的公钥.pem 回车是\r\n
// 用 openssl API导出的公钥.pem 回车是 \n
}
} while (false);
if (NULL != pdata)
{
OPENSSL_free(pdata);
pdata = NULL;
}
}
bool export_ecc_pub_key_from_ecc_priv_key(const UCHAR* pBuf, int lenBuf, unsigned char*& pdata, size_t& pdata_len)
{
bool b_rc = false;
int i_rc = 0;
EVP_PKEY* priv_key = NULL;
int selection = 0;
const char* output_structure = NULL;
OSSL_ENCODER_CTX* _ossl_encoder_ctx = NULL;
do {
// 从buffer载入私钥用了 exp018_from_PrivKeyDat_export_PubKeyDat_ecc 的实现
// openssl命令行实现是从文件中拿的, 还不是直接拿, 是用了OSSL_STORE_open_ex(), 实在没办法用, 里面都是内部函数
// 看官方实现, 载入私钥和载入公钥是2个实现
priv_key = load_priv_key(true, "EC", NULL, (const char*)pBuf, lenBuf, NULL);
if (NULL == priv_key)
{
break;
}
i_rc = EVP_PKEY_set_int_param(priv_key, OSSL_PKEY_PARAM_EC_INCLUDE_PUBLIC, 1);
if (1 != i_rc)
{
break;
}
selection = OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS | OSSL_KEYMGMT_SELECT_PUBLIC_KEY;
output_structure = "SubjectPublicKeyInfo";
_ossl_encoder_ctx = OSSL_ENCODER_CTX_new_for_pkey(priv_key, selection,"PEM", output_structure,NULL);
if (NULL == _ossl_encoder_ctx)
{
break;
}
// 从私钥载入公钥成功, _ossl_encoder_ctx中包含公钥
if (1 != OSSL_ENCODER_to_data(_ossl_encoder_ctx, &pdata, &pdata_len))
{
break;
}
b_rc = true;
} while (false);
if (NULL != priv_key)
{
EVP_PKEY_free(priv_key);
priv_key = NULL;
}
if (NULL != _ossl_encoder_ctx)
{
OSSL_ENCODER_CTX_free(_ossl_encoder_ctx);
_ossl_encoder_ctx = NULL;
}
return b_rc;
}
EVP_PKEY* load_priv_key(bool isPrivkeyBuffer, const char* key_type, OSSL_LIB_CTX* libctx, const char* pBufPrivKey, int lenPrivKey, const char* passphrase)
{
int ret = 0;
EVP_PKEY* pkey = NULL;
OSSL_DECODER_CTX* dctx = NULL;
int selection = 0;
int i_tmp = 0;
BIO* bio_privKey = NULL;
if (NULL == key_type)
{
goto cleanup;
}
bio_privKey = BIO_new(BIO_s_mem());
if (NULL == bio_privKey)
{
goto cleanup;
}
i_tmp = BIO_write(bio_privKey, pBufPrivKey, lenPrivKey);
if (i_tmp != lenPrivKey)
{
goto cleanup;
}
/*
* Create PEM decoder context expecting an RSA key.
*
* For raw (non-PEM-encoded) keys, change "PEM" to "DER".
*
* The selection argument here specifies whether we are willing to accept a
* public key, private key, or either. If it is set to zero, either will be
* accepted. If set to EVP_PKEY_KEYPAIR, a private key will be required, and
* if set to EVP_PKEY_PUBLIC_KEY, a public key will be required.
*/
// 在执行 OSSL_DECODER_CTX_new_for_pkey() 之前, 要选择啥要定好, 否则后面从pkey中取不到东西(公私钥对)
// selection = (isPrivkeyBuffer ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY);
// EVP_PKEY_KEY_PARAMETERS
// selection = EVP_PKEY_PUBLIC_KEY; // 如果载入的是纯公钥数据, 好使
// selection = EVP_PKEY_PRIVATE_KEY; // 如果载入的是openssl.exe生成的私钥, 去私钥不好使, OSSL_DECODER_from_bio()就失败
selection = EVP_PKEY_KEYPAIR; //如果载入私钥, 就是一个公私钥对
dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, "PEM", NULL, key_type,
selection,
libctx, NULL);
if (dctx == NULL) {
// fprintf(stderr, "OSSL_DECODER_CTX_new_for_pkey() failed\n");
goto cleanup;
}
/*
* Set passphrase if provided; needed to decrypt encrypted PEM files.
* If the input is not encrypted, any passphrase provided is ignored.
*
* Alternative methods for specifying passphrases exist, such as a callback
* (see OSSL_DECODER_CTX_set_passphrase_cb(3)), which may be more useful for
* interactive applications which do not know if a passphrase should be
* prompted for in advance, or for GUI applications.
*/
if (passphrase != NULL) {
if (OSSL_DECODER_CTX_set_passphrase(dctx,
(const unsigned char*)passphrase,
strlen(passphrase)) == 0) {
// fprintf(stderr, "OSSL_DECODER_CTX_set_passphrase() failed\n");
goto cleanup;
}
}
/* Do the decode, reading from file. */
if (OSSL_DECODER_from_bio(dctx, bio_privKey) == 0) { // 如果f是stdin, 就需要自己输入私钥内容, 所以函数入参的f必须是一个实际文件的FILE*
// fprintf(stderr, "OSSL_DECODER_from_fp() failed\n");
goto cleanup;
}
ret = 1;
cleanup:
if (NULL != dctx)
{
OSSL_DECODER_CTX_free(dctx);
dctx = NULL;
}
/*
* pkey is created by OSSL_DECODER_CTX_new_for_pkey, but we
* might fail subsequently, so ensure it's properly freed
* in this case.
*/
if (ret == 0) {
EVP_PKEY_free(pkey);
pkey = NULL;
}
if (NULL != bio_privKey)
{
BIO_free(bio_privKey);
bio_privKey = NULL;
}
return pkey;
}