Linux C语言调用OpenSSL: 生成 RSA 私钥并提取公钥
调用函数介绍
RSA私钥生成,主要会调用RSA_generate_key,RSA_generate_key和RSA_set0_key函数。
RSA_generate_key
函数作用:生成 RSA 密钥对。
然而,这个函数在新版本的 OpenSSL(如 1.0.0 及以上)中已被废弃,并被更现代的函数如RSA_generate_key_ex 或 RSA_new 与相关函数所取代。
#include <openssl/rsa.h>
//生成一对钥匙
RSA *RSA_generate_key(int bits, unsigned long e, void (*callback)(int, int, void *), void *cb_arg);
//参数说明:
int bits:
含义:指定 RSA 密钥的长度(以位为单位)。
取值范围:通常是 1024, 2048, 3072, 4096 等。不过,出于安全考虑,现在通常推荐使用至少 2048 位的密钥。
unsigned long e:
含义:指定 RSA 加密中的公钥指数 e(也称为“加密指数”或“公共指数”)。
取值范围:e 必须是一个大于 1 的奇数,并且与 (p-1)*(q-1) 互质(其中 p 和 q 是 RSA 密钥对的两个大素数)。在实践中,e 的最常见值是 65537(即 0x10001),因为它是一个常用的素数,且足够大,同时也有良好的性能。
void (*callback)(int, int, void *):
含义:这是一个回调函数指针,用于在密钥生成过程中提供进度信息。
参数:
第一个参数是当前的阶段(通常是 0, 1, 2 或 3,表示密钥生成的不同阶段)。
第二个参数是该阶段中的当前值(具体含义取决于阶段)。
第三个参数是传递给回调函数的任意数据(即 cb_arg)。
如果不需要进度信息,可以将此参数设置为 NULL。
*void cb_arg:
含义:这是一个指向任意数据的指针,该数据将被传递给回调函数。
如果回调函数不需要任何额外的数据,可以将此参数设置为 NULL。
//返回值
返回指向 RSA 结构的指针,如果密钥生成失败,则返回 NULL。
RSA_generate_key_ex
作用:生成 RSA 密钥对。
#include <openssl/rsa.h>
//生成一对钥匙
int RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb);
参数:
RSA *rsa:
这是一个指向 RSA 结构的指针,该结构将用于存储生成的 RSA 密钥对。在调用此函数之前,您应该使用 RSA_new() 初始化此结构。
int bits:
这指定了 RSA 密钥的长度(以位为单位)。常见的值是 1024、2048、4096 等。较长的密钥提供更高级别的安全性,但也需要更多的计算资源。
BIGNUM *e:
这是一个指向 BIGNUM 结构的指针,该结构包含 RSA 公钥的指数 e。在许多情况下,e 被设置为 65537(即 0x10001),因为它是一个常见的选择,并且在加密和解密过程中提供了良好的性能。如果您将此参数设置为 NULL,则 OpenSSL 会自动使用默认值(通常是 65537)。
BN_GENCB *cb:
这是一个指向 BN_GENCB 结构的指针,该结构可以用于为密钥生成过程提供回调功能。这允许您在密钥生成过程中执行某些操作,例如更新进度条或执行其他任务。如果您不需要此功能,可以将此参数设置为 NULL。
返回值:
如果密钥生成成功,则返回 1。
如果发生错误,则返回 0,并设置相应的错误代码(可以使用 OpenSSL 的错误处理函数进行检查)。
注意:为了保持代码的可移植性和兼容性,最好查看您正在使用的 OpenSSL 版本的官方文档,因为函数和参数可能会随着版本的更新而发生变化。
RSA_set0_key
用于设置 RSA 密钥的各个组成部分,而不增加其引用计数。这意味着传递给此函数的 BIGNUM 指针的所有权将被 RSA 结构体接管,且原先的调用者不应再释放这些 BIGNUM 对象,除非它们被明确地从 RSA 结构体中移除或替换。
#include <openssl/rsa.h>
#include <openssl/bn.h>
int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d)
参数:
RSA *r:
这是一个指向 RSA 结构的指针,你想要在其中设置密钥的各个组成部分。
BIGNUM *n:
这是一个指向 BIGNUM 的指针,代表 RSA 模数(也称为“n”)。在 RSA 加密和解密中,n 是公钥和私钥共有的部分,是两个大质数 p 和 q 的乘积。
BIGNUM *e:
这是一个指向 BIGNUM 的指针,代表 RSA 公钥的指数(也称为“e”)。e 是一个与 (p-1)*(q-1) 互质的正整数,通常是一个小的、固定的值,如 65537。
BIGNUM *d:
这是一个指向 BIGNUM 的指针,代表 RSA 私钥的指数(也称为“d”)。d 是私钥的一个核心组成部分,满足 e * d mod ((p-1)*(q-1)) = 1 的条件。这个值在私钥中是保密的,不应该被泄露。
返回值:
一般不返回状态码,因此通常你不会基于这个函数的返回值来判断操作是否成功。但是,你仍然应该确保传入的参数是有效的,以避免潜在的错误或未定义的行为。
注意:由于 RSA_set0_key 采用了“set0”的命名约定,它不会增加传入的 BIGNUM 对象的引用计数。因此,如果原始所有者(在调用 RSA_set0_key 之前拥有这些 BIGNUM 对象的代码)在调用 RSA_set0_key 后释放了这些 BIGNUM 对象,那么 RSA 结构中的指针可能会变成悬垂指针(dangling pointer),这会导致未定义的行为。
为了避免这种情况,通常只有在你确定不再需要这些 BIGNUM 对象,并且希望它们与 RSA 结构共存亡时,才使用 RSA_set0_key。
如果你只是想将新的 BIGNUM 值与 RSA 结构关联起来,但不希望改变它们的所有权,你应该使用 RSA_set_key 函数,它会增加传入 BIGNUM 对象的引用计数。
示例
代码
#include <stdio.h>
#include <stdlib.h>
#include <openssl/rsa.h>
#include <openssl/err.h>
#include <openssl/bn.h>
#define WK_OK 0
#define WK_ERR (-1)
//打印大数
void printf_bn(const BIGNUM* n)
{
unsigned int i = 0;
unsigned char to[256] = {0};
int byte_size = BN_bn2bin(n, to);//大数转换为2进制数,返回值 = 字节数,也可调用BN_num_bytes(n)获取大数长度
for(i=0; i<byte_size; i++)
{
printf("%02x", to[i]);
}
printf("\n");
}
//提取公钥
//prikey:私钥
RSA* my_get_pub_form_pri(RSA* prikey)
{
BIGNUM *n_in_pri = NULL;//私钥中的模数
BIGNUM *e_in_pri = NULL;//私钥中的公钥指数
BIGNUM *n_in_pub = NULL;
BIGNUM *e_in_pub = NULL;
//存放rsa公钥
RSA* pubkey = RSA_new();
if(NULL == pubkey)
{
return NULL;
}
//获取私钥中的N和e(公钥由模数N和公钥指数E决定,所以只需要获取这两个参数)
RSA_get0_key(prikey, &n_in_pri, &e_in_pri, NULL);
//复制大数。注意:(1)不能直接使用memcpy,容易造成内存泄漏;(2)若使用BN_copy,需要提前给目标BINNUM申请空间
n_in_pub = BN_dup(n_in_pri);
e_in_pub = BN_dup(e_in_pri);
//设置公钥(pubkey会继承n_in_pub和e_in_pub空间,所以无需单独释放n_in_pub和e_in_pub空间)
RSA_set0_key(pubkey, n_in_pub, e_in_pub, NULL);
return pubkey;
}
//调用RSA_generate_key_ex生成RSA密钥
//key_size:密钥bit大小
//e_value:公钥指数设置
RSA* my_generate_rsa_key(int key_size, unsigned long e_value)
{
//存放rsa密钥对
RSA* r = RSA_new();
if(NULL == r)
{
return NULL;
}
//公钥指数(大质数)(公钥:由公钥指数N 和 模数N 决定)
BIGNUM* e = BN_new();
//设置公钥指数:一般使用默认值RSA_F4 65537; 也可以使用随机数,性能不可控
BN_set_word(e, e_value);
//生成私钥 (私钥:指数D和模数N)
RSA_generate_key_ex(
r, //输出RSA密钥
key_size, //密钥的bit位(最好2048以上)
e, //公钥指数
NULL //回调函数
);
//释放空间
BN_free(e);
return r;
}
//生成密钥对测试
int test_RSA_generate_key(int test_choice)
{
char err[256];
RSA* key_t = NULL;
RSA* pubkey_t = NULL;
unsigned long e = RSA_F4;//65537,rsa.h中的宏定义,加密指数(或称:公共指数)
if (1 == test_choice)
{
//使用RSA_generate_key生成RSA密钥
key_t = RSA_generate_key(2048, e, NULL, NULL);
}
else
{
//使用RSA_generate_key_ex生成RSA密钥
key_t = my_generate_rsa_key(2048, e);
}
if(NULL == key_t)
{
ERR_error_string(ERR_get_error(), err);
printf("%s\n", err);
return WK_ERR;
}
printf("succ: RSA_generate_key\n");
pubkey_t = my_get_pub_form_pri(key_t);
#if 1//打印看看key_t各项参数
//模数N
BIGNUM* n_in_st = RSA_get0_n(key_t);
BIGNUM* n_in_pub_st = RSA_get0_n(pubkey_t);
//公钥指数E: 明文^E % N = 密文
BIGNUM* e_in_st = RSA_get0_e(key_t);
BIGNUM* e_in_pub_st = RSA_get0_e(pubkey_t);
//私钥指数D: 密文^D % N = 明文
BIGNUM* d_in_st = RSA_get0_d(key_t);
printf("私钥中的模数:\n");
printf_bn(n_in_st);
printf("公钥中的模数:\n");
printf_bn(n_in_pub_st);
printf("私钥中的公钥指数E: \n");
printf_bn(e_in_st);
printf("公钥中的公钥指数E: \n");
printf_bn(e_in_pub_st);
printf("私钥指数D: \n");
printf_bn(d_in_st);
#endif//打印看看key_t各项参数
//释放密钥空间
RSA_free(key_t);
RSA_free(pubkey_t);
return WK_OK;
}
int main()
{
int ret = 0;
int choice = 0;
while(1)
{
printf("\n调用的openssl库版本: num [%lx], text [%s]\r\n", OpenSSL_version_num(), OpenSSL_version(OPENSSL_VERSION));
printf("选择测试项: \n");
printf("0: 使用RSA_generate_key生成RSA密钥 \r\n1:使用RSA_generate_key_ex生成RSA密钥\r\n");
scanf("%d", &choice);
ret = test_RSA_generate_key(choice);
if(WK_OK != ret)
{
printf("err: test_RSA_generate_key\n");
}
}
}
makefile
SRC := $(wildcard ./*.c)
#paramter
CC := gcc
target := app_openssl
#头文件和库路径(修改成安装openssl的路径)
DIR_LIB := -L /xxxxxxxxxxxxxx/openssl/lib64
DIR_INCLUDE := -I ./inc \
-I /xxxxxxxxxxxxxx/openssl/include/
$(target):$(SRC)
$(CC) $(SRC) $(DIR_INCLUDE) $(DIR_LIB) -lssl -lcrypto -o $@
clean:
rm -rf $(target)
执行结果
参考链接:https://www.openssl.org/source/old/index.html