openssl从内存中读取RSA公钥

本文介绍如何正确处理和加载base64编码的RSA公钥,并通过openssl的bio接口从内存中读取公钥。此外,还提供了一个示例代码,展示了如何使用EVP相关函数完成签名验证。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景:近期需要在项目中进行RSA签名验证,厂商会给出pem格式的RSA公钥。在以往项目中使用openssl读取RSA公钥时基本都是从pem文件中读取,基本没什么问题,可最近由于项目需要需要从数据库中读取RSA公钥,经查资料发现openssl提供了bio接口以支持各种形式的秘钥读取。

在使用bio接口从内存中读取pem格式的公钥时,总是读取公钥失败,经不断查找资料,发现在我们得到base64编码的RSA公钥后,从内存中读取这个公钥时要注意以下几点:

(1)公钥字符串开头要加上“-----BEGIN PUBLIC KEY-----\n”,结尾加上“\n-----END PUBLIC KEY-----\n”。否则会出现error:0906D06C:PEM routines:PEM_read_bio:no start line

(2)公钥字符串每隔64个字符要加一个换行,否则会报秘钥格式错误。

c++代码实现举例:

int nPublicKeyLen = strPublicKey.size();      //strPublicKey为base64编码的公钥字符串
for(int i = 64; i < nPublicKeyLen; i+=64)
{
if(strPublicKey[i] != '\n')
{
strPublicKey.insert(i, "\n");
}
i++;
}
strPublicKey.insert(0, "-----BEGIN PUBLIC KEY-----\n");
strPublicKey.append("\n-----END PUBLIC KEY-----\n");

BIO *bio = NULL; 
RSA *rsa = NULL; 
char *chPublicKey = const_cast<char *>(strPublicKey.c_str());
if ((bio = BIO_new_mem_buf(chPublicKey, -1)) == NULL)       //从字符串读取RSA公钥
{     
        cout<<"BIO_new_mem_buf failed!"<<endl;      
}       
rsa = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);   //从bio结构中得到rsa结构
if (!rsa)
 {
        ERR_load_crypto_strings();
        char errBuf[512];
        ERR_error_string_n(ERR_get_error(), errBuf, sizeof(errBuf));
        cout<< "load public key failed["<<errBuf<<"]"<<endl;
BIO_free_all(bio);
 }

由于使用RSA_vefify函数未能成功验证签名,改为使用evp相关函数进行签名验证。下面贴上使用公钥进行签名验证的代码:

string strBase64DecodedSig = base64_decode(strSignature);                //strSignature为签名
char *chInAppDataSignature = const_cast<char *>(strBase64DecodedSig.c_str());   //签名先进行base64解码
int result = 0;
char *chInAppData = const_cast<char *>(strUnsignedData.c_str());         //strUnsignedData为原始数据,即未加密数据
EVP_PKEY *evpKey = NULL;
EVP_MD_CTX ctx;
evpKey = EVP_PKEY_new();
if(evpKey == NULL)
{
        cout<<"error EVP_PEKY_new"<<endl;
        RSA_free(rsa);
BIO_free_all(bio);return;
 }
 if((result = EVP_PKEY_set1_RSA(evpKey,rsa)) != 1)
{
        cout<<"error EVP_PKEY_set1_RSA"<<endl;
        RSA_free(rsa);
        EVP_PKEY_free(evpKey);
BIO_free_all(bio);return;
 }
 EVP_MD_CTX_init(&ctx);
 if(result == 1 && (result = EVP_VerifyInit_ex(&ctx, EVP_sha1(), NULL)) != 1)
{       
        cout<<"error EVP_VerfyInit_ex"<<endl;
 }
 if(result == 1 && (result = EVP_VerifyUpdate(&ctx, chInAppData,strUnsignedData.size())) != 1)
{
cout<<"error EVP_VerifyUpdate"<<endl;
}
 if(result == 1 && (result = EVP_VerifyFinal(&ctx, (unsigned char *)chInAppDataSignature, strBase64DecodedSig.size(), evpKey)) != 1)
{
cout<<"error EVP_VerifyFinal"<<endl;
}
if(result == 1)
{
cout<<"Verify success"<<endl;
}
else
{
ERR_load_crypto_strings(); 
char errBuf[512]; 
ERR_error_string_n(ERR_get_error(), errBuf, sizeof(errBuf)); 
cout<<"verify failed["<<errBuf<<"]"<<endl;
}
EVP_MD_CTX_cleanup(&ctx);
RSA_free(rsa);
EVP_PKEY_free(evpKey);
BIO_free_all(bio);

<think>我们正在使用OpenSSL 1.1.1n版本。在OpenSSL 1.1.1中,很多API已经发生了变化,不再推荐直接访问RSA结构体的成员,而是使用一系列的getter和setter函数。 目标:从RSA结构体中导出RSA公钥RSA公钥通常包含模数(n)和指数(e)。 步骤: 1. 获取RSA公钥:我们可以使用`RSA_get0_key`函数来获取RSA公钥的模数(n)、公指数(e)和私指数(d)。因为我们只需要公钥,所以私指数可以忽略。 2. 将公钥导出为PEM格式或者DER格式。这里我们可以使用`PEM_write_bio_RSAPublicKey`或者`i2d_RSAPublicKey`函数。 但是,问题中要求的是从RSA结构体中导出公钥,并没有指定格式。通常,公钥可以导出为PEM格式(文本格式)或DER格式(二进制格式)。这里我们将提供两种方式。 另外,注意OpenSSL 1.1.1中,RSA结构体是不透明的,我们不能直接访问其内部字段,必须使用函数。 导出为PEM格式的步骤: a. 创建一个BIO(OpenSSL的I/O抽象,用于内存或文件)。 b. 使用PEM_write_bio_RSAPublicKeyRSA公钥写入BIO。 c. 从BIO中读取数据并输出。 导出为DER格式的步骤: a. 使用`i2d_RSAPublicKey`函数将RSA公钥转换为DER格式的字节流。 b. 将DER数据写入文件或内存。 但是,用户可能希望得到一个RSA公钥(包含n和e),而不仅仅是模数或指数。所以我们可以导出整个公钥(即RSAPublicKey结构)。 注意:还有一种公钥是SubjectPublicKeyInfo格式(包含算法标识和公钥),但这里要求的是RSA公钥(即PKCS#1定义的RSAPublicKey)。 下面我们分别提供导出为PEM和DER格式的代码示例。 注意:我们假设已经有一个RSA结构体指针(例如从其他地方加载的,或者生成的)。这里我们不会展示如何生成RSA密钥对,而是直接关注导出公钥。 示例代码(导出为PEM格式): ```c #include <openssl/rsa.h> #include <openssl/pem.h> #include <openssl/bio.h> int export_rsa_public_key_to_pem(RSA *rsa, const char *filename) { BIO *bio = NULL; int ret = 0; bio = BIO_new_file(filename, "w"); if (!bio) { // 错误处理 return 0; } // 使用PEM_write_bio_RSAPublicKey写入PEM格式的公钥 if (PEM_write_bio_RSAPublicKey(bio, rsa) != 1) { // 写入失败 ret = 0; } else { ret = 1; } BIO_free_all(bio); return ret; } ``` 示例代码(导出为DER格式): ```c #include <openssl/rsa.h> #include <openssl/asn1.h> int export_rsa_public_key_to_der(RSA *rsa, const char *filename) { unsigned char *der = NULL; int len; FILE *fp = NULL; int ret = 0; // 获取DER编码的长度 len = i2d_RSAPublicKey(rsa, NULL); if (len <= 0) { return 0; } der = (unsigned char *)malloc(len); if (!der) { return 0; } unsigned char *p = der; len = i2d_RSAPublicKey(rsa, &p); // 现在der中存储了DER编码 // 写入文件 fp = fopen(filename, "wb"); if (!fp) { free(der); return 0; } if (fwrite(der, 1, len, fp) != len) { ret = 0; } else { ret = 1; } fclose(fp); free(der); return ret; } ``` 注意:在导出为DER格式时,我们使用了`i2d_RSAPublicKey`,它导出的是PKCS#1定义的RSAPublicKey结构(即SEQUENCE { n, e })。 另外,在OpenSSL 1.1.1中,我们也可以使用EVP_PKEY来保存RSA密钥,但这里用户要求从RSA结构体中导出。 重要:在导出公钥之前,请确保RSA结构体中确实包含公钥(即至少要有n和e)。如果是私钥,那么公钥部分(n和e)通常也会存在。 如果用户想要导出为字符串(内存中)而不是文件,可以稍作修改,使用BIO_s_mem(对于PEM)或者直接使用DER的缓冲区。 另外,还有一种方式是使用`RSA_get0_key`来获取n和e,然后自己构造公钥,但这样比较麻烦,通常我们使用上述现成函数。 参考OpenSSL文档: - `PEM_write_bio_RSAPublicKey` - `i2d_RSAPublicKey` 最后,注意内存管理,确保在不需要时释放RSA结构体(但本函数不负责释放传入的rsa)。 请根据实际需求选择PEM或DER格式。 注意:以上代码没有进行错误处理的完整展示,实际应用中需要更完善的错误处理。 [^1]: 参考站内引用中提到了使用openssl命令行工具进行RSA加密解密,但这里我们关注的是编程API。 [^2]: 引用[2]同样提到了RSA加密解密,但未涉及API使用。</think>在 OpenSSL 1.1.1n 中,从 RSA 结构体导出公钥需要遵循以下步骤(由于 RSA 结构体在 1.1.x 版本中已变为不透明类型,需使用 API 函数访问): ### 导出 RSA 公钥的步骤 1. **获取公钥参数** 使用 `RSA_get0_key()` 获取模数(n)和公钥指数(e): ```c const BIGNUM *n, *e, *d; // d 可忽略 RSA_get0_key(rsa, &n, &e, &d); // 获取公钥参数 ``` 2. **创建新公钥结构** 用获取的参数构建新的 RSA 公钥: ```c RSA *pubkey = RSA_new(); RSA_set0_key(pubkey, BN_dup(n), BN_dup(e), NULL); // 只复制 n 和 e ``` 3. **导出为 PEM 格式** 使用 BIO 将公钥写入内存或文件: ```c BIO *bio = BIO_new(BIO_s_mem()); PEM_write_bio_RSAPublicKey(bio, pubkey); // 写入 PEM 格式 // 获取 PEM 字符串 char *pem_data; long pem_len = BIO_get_mem_data(bio, &pem_data); ``` 4. **清理资源** ```c RSA_free(pubkey); BIO_free_all(bio); ``` ### 完整代码示例 ```c #include <openssl/rsa.h> #include <openssl/pem.h> #include <openssl/bio.h> int export_rsa_pubkey(RSA *rsa, unsigned char **out_pem) { const BIGNUM *n, *e, *d; RSA_get0_key(rsa, &n, &e, &d); // 获取公钥参数 // 创建新公钥结构 RSA *pubkey = RSA_new(); if (!RSA_set0_key(pubkey, BN_dup(n), BN_dup(e), NULL)) { RSA_free(pubkey); return 0; } // 导出为 PEM BIO *bio = BIO_new(BIO_s_mem()); if (!PEM_write_bio_RSAPublicKey(bio, pubkey)) { BIO_free_all(bio); RSA_free(pubkey); return 0; } // 获取 PEM 数据 long len = BIO_get_mem_data(bio, out_pem); *out_pem = OPENSSL_memdup(*out_pem, len + 1); (*out_pem)[len] = '\0'; // 添加终止符 // 清理 BIO_free_all(bio); RSA_free(pubkey); return 1; } ``` ### 使用说明 1. **输入**:包含公钥RSA 结构体(即使初始含私钥,`RSA_get0_key` 也能获取公钥参数) 2. **输出**:PEM 格式的字符串(需用 `OPENSSL_free()` 释放) 3. **验证**:导出的 PEM 可被标准工具识别: ```bash openssl rsa -pubin -in pubkey.pem -text -noout ``` ### 注意事项 - **兼容性**:此方法适用于 OpenSSL 1.1.x(不透明结构体) - **密钥类型**:确保 RSA 结构体已包含公钥参数(n 和 e) - **内存管理**:导出的 PEM 字符串需调用 `OPENSSL_free()` 释放 - **错误处理**:实际代码中需添加返回值检查(示例简化了错误处理) > **重要提示**:若需要 PKCS#8 格式公钥(含算法标识),可改用 `PEM_write_bio_PUBKEY()`,但需先将 RSA 转换为 EVP_PKEY 结构[^1]。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值