Linux C语言调用OpenSSL: 生成 RSA 私钥并提取公钥

  • 调用函数介绍
  • RSA_generate_key
  • RSA_generate_key_ex
  • RSA_set0_key
  • 示例
  • 代码
  • makefile
  • 执行结果


调用函数介绍

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)

执行结果