使用C语言openssl库实现 RSA加密 和 消息验证

news2024/7/30 18:45:14

Q:什么是RSA?

A:RSA(Rivest-Shamir-Adleman)是一种非对称加密算法,是最早的一种用于公开密钥加密和数字签名的算法。它使用一对公钥(public key)和私钥(private key)。公钥用于加密,私钥用于解密。由于使用不同的密钥进行加密和解密,因此被称为非对称加密算法。RSA算法的安全性基于大数因子分解的难度,即大整数分解问题。目前尚未找到一种高效算法可以在合理时间内解决大整数的分解问题,这使得RSA在当前计算机性能下是安全的。


Q:什么是RSA消息验证?

A:RSA消息验证指的是使用RSA算法对消息进行签名和验证,以确保消息的完整性和真实性。


RSA密钥对生成

  • 选择两个大素数 p 和 q
  • 计算 𝑛=𝑝×𝑞,这是模数
  • 计算 𝜙(𝑛)=(𝑝−1)×(𝑞−1)
  • 选择一个整数 𝑒作为公用指数,使 1<𝑒<𝜙(𝑛)并且 𝑒 与 𝜙(𝑛)互质
  • 计算私钥指数 𝑑,使得 𝑑×𝑒≡1 (mod 𝜙(𝑛))

公钥是 (𝑒,𝑛),私钥是 (𝑑,𝑛)

RSA加密/解密步骤

加密

  • 将明文 𝑀 转换为整数 𝑚,其中 0≤𝑚<𝑛
  • 使用公钥 (𝑒,𝑛)进行加密,得到密文 𝑐=m^{e} (mod 𝑛)

解密

  • 使用私钥 (𝑑,𝑛) 进行解密,恢复明文 𝑚=c^{d}(mod 𝑛)

RSA消息验证步骤

签名

  • 发送方使用其私钥对消息进行签名。具体做法是对消息进行哈希处理,得到一个消息摘要,然后使用私钥对这个摘要进行加密,生成签名
  • 签名与原始消息一起发送给接收方

验证

  • 接收方使用发送方的公钥对签名进行验证。具体做法是对收到的签名使用公钥解密,得到解密后的摘要
  • 接收方同时对收到的原始消息进行哈希处理,得到本地计算的消息摘要
  • 比较解密后的摘要和本地计算的摘要,如果两者相同,则验证成功,消息没有被篡改且确实来自发送方

举个例子:

假设Alice要给Bob发送一条消息,并且需要确保Bob知道这条消息确实来自Alice且没有被篡改,Alice和Bob可以使用RSA进行消息签名和验证。

  1. Alice生成密钥对

    • 公钥:(𝑒,𝑛)
    • 私钥:(𝑑,𝑛)
  2. Alice签名消息

    • 对消息进行哈希处理,得到消息摘要 𝐻(𝑀)
    • 使用私钥对消息摘要进行加密,生成签名 𝑆=𝐻(𝑀)𝑑 (mod 𝑛)
    • 将消息 𝑀 和签名 𝑆 发送给Bob。
  3. Bob验证签名

    • 使用公钥对签名进行解密,得到解密后的摘要 𝐻 ′(𝑀)=𝑆𝑒 (mod 𝑛)
    • 对收到的消息 𝑀 进行哈希处理,得到本地计算的消息摘要 𝐻(𝑀)
    • 比较解密后的摘要 𝐻 ′(𝑀)和本地计算的摘要 𝐻(𝑀),如果两者相同,则验证成功。

通过上述过程,Bob可以确认消息确实来自Alice且未被篡改。

RSA加密/解密 & RSA消息验证

在刚刚的消息验证 例子中,公钥用于解密,而私钥用于加密,这似乎违背了在开头所说的“公钥加密,私钥解密”,其实并没有,这是因为RSA加/解密 和 RSA消息验证是两个不同的概念

他们可以各自单独使用,也可以结合使用,以下是三个不同的场景帮助理解:


1. 单独使用RSA加密

场景1:确保消息的机密性

步骤

  1. 密钥生成接收方生成一对RSA密钥对(公钥和私钥)。
  2. 公钥分发:接收方将公钥分发给发送方。
  3. 消息加密
    • 发送方使用接收方的公钥对消息进行加密。
    • 发送方将加密后的消息发送给接收方。
  4. 消息解密
    • 接收方使用自己的私钥对加密的消息进行解密,恢复原始消息


2. 单独使用RSA消息验证(数字签名)

场景2:确保消息的完整性和真实性

步骤

  1. 密钥生成发送方生成一对RSA密钥对(公钥和私钥)。
  2. 公钥分发:发送方将公钥分发给接收方。
  3. 消息签名
    • 发送方对消息进行哈希处理,生成消息摘要。
    • 发送方使用自己的私钥对消息摘要进行加密,生成签名。
    • 发送方将原始消息和签名一起发送给接收方。
  4. 签名验证
    • 接收方使用发送方的公钥对签名进行解密,得到解密后的消息摘要。
    • 接收方对收到的原始消息进行哈希处理,生成本地计算的消息摘要。
    • 接收方比较解密后的消息摘要和本地计算的消息摘要,如果两者相同,则验证成功。


3. 结合使用RSA加密和消息验证

场景3:确保消息的机密性、完整性和真实性

步骤

  1. 密钥生成
    • 发送方和接收方各自生成一对RSA密钥对(各自的公钥和私钥)。
  2. 公钥分发
    • 发送方和接收方互相交换公钥。
  3. 消息签名和加密
    • 发送方对消息进行哈希处理,生成消息摘要。
    • 发送方使用自己的私钥对消息摘要进行加密,生成签名。
    • 发送方将签名附加到原始消息上。 
    • 发送方使用接收方的公钥对包含签名的消息进行加密。
    • 发送方将加密后的消息发送给接收方。
  4. 消息解密和验证
    • 接收方使用自己的私钥对加密的消息进行解密,得到原始消息和签名。
    • 接收方使用发送方的公钥对签名进行解密,得到解密后的消息摘要。
    • 接收方对收到的原始消息进行哈希处理,生成本地计算的消息摘要。
    • 接收方比较解密后的消息摘要和本地计算的消息摘要,如果两者相同,则验证成功。

相关函数认识

密钥对生成相关API

  • OpenSSL_add_all_algorithms(void):用于注册所有可用的加密算法、摘要算法和公钥算法。这个函数通常在程序初始化时调用,以确保在后续的加密操作中可以使用所有 OpenSSL 提供的算法
  • RSA_new(void):创建一个新的 RSA 结构,该结构用于存储 RSA 密钥对。RSA 结构包含了生成和使用 RSA 密钥对所需的信息

返回值

  • 成功时返回一个指向新分配的 RSA 结构的指针
  • 失败时返回 NULL
  • RSA_free(RSA *rsa): 用于释放 RSA 结构体占用的内存的函数。它用于清理和释放与 RSA 结构相关的所有资源,防止内存泄漏。 

参数

  • RSA *rsa:指向要释放的RSA结构体的指针
  • BN_new(void):创建一个新的 BIGNUM 结构,该结构用于存储任意大小的整数。BIGNUM 是 OpenSSL 中表示大整数的基本数据类型,用于各种密码学计算

返回值

  • 成功时返回一个指向新分配的 BIGNUM 结构的指针
  • 失败时返回 NULL
  • BN_set_word(BIGNUM *a,unsigned long w) : 用于将一个无符号长整数 (unsigned long) 设置为 BIGNUM 结构的值。这个函数在初始化 BIGNUM 结构时特别有用,例如设置 RSA 公钥的指数

参数

  • BIGNUM *a:指向 BIGNUM 结构的指针
  • unsigned long w:要设置的无符号长整数值

返回值

  • 成功时返回 1
  • 失败时返回 0
  •  BN_free(BIGNUM *a) : 函数用于释放由 BN_new() 分配的 BIGNUM 结构。BIGNUM 是 OpenSSL 中用于表示大整数的结构,当它们不再需要时,需要调用 BN_free() 来释放分配的内存,以避免内存泄漏

参数

  • BIGNUM *a:指向 BIGNUM 结构的指针,需要被释放的 BIGNUM 结构
  •  RSA_generate_key_ex(RSA *rsa,int bits,BIGNUM *e,BN_GENCB *cb): 函数是 OpenSSL 库中用于生成 RSA 密钥对的函数

参数解释

  • RSA *rsa:一个指向 RSA 结构的指针,这个结构将保存生成的密钥对。你需要预先分配并初始化这个结构。
  • int bits:指定生成的 RSA 密钥的长度,以位为单位。常见的长度有 1024、2048、4096 等,长度越大,安全性越高,但生成和使用的时间也越长。
  • BIGNUM *e:指定公钥指数的 BIGNUM 结构。通常使用 65537 作为公钥指数,因为它是一个常用且有效率的选择。
  • BN_GENCB *cb:一个可选的回调函数,用于在密钥生成的过程中提供反馈。如果不需要反馈,可以传递 NULL。

返回值

  • 成功时返回 1,失败时返回 0。

加密/解密API 

RSA_public_encrypt RSA_private_decrypt 是 OpenSSL 中用于 RSA 加密和解密操作的函数。它们分别使用公钥进行加密和私钥进行解密:

  • RSA_public_encrypt(int flen,const unsigned char *from,unsigned char *to,RSA *rsa,int padding):
  • RSA_private_decrypt(int flen,const unsigned char *from,unsigned char *to,RSA *rsa,int padding):

参数解释

  • int flen:输入数据的长度
  • const unsigned char *from:指向输入数据的指针
  • unsigned char *to:指向输出数据的缓冲区的指针
  • RSA *rsa:指向 RSA 密钥的指针
  • int padding:填充方式。常见的填充方式有:
    • RSA_PKCS1_PADDING:PKCS #1 v1.5 填充
    • RSA_NO_PADDING:无填充
    • RSA_PKCS1_OAEP_PADDING:PKCS #1 OAEP 填充

返回值

  • 成功时,返回加密或解密的数据长度
  • 失败时,返回 -1

 

签名/认证API

  • SHA256(const unsigned char *d,size_t n,unsigned char *md): OpenSSL 库中用于计算消息的 SHA-256 哈希值的函数

参数说明

  • const unsigned char *d:指向要进行哈希运算的数据的指针
  • size_t n:数据的长度,以字节为单位
  • unsigned char *md:指向保存哈希值的输出缓冲区的指针。这个缓冲区需要有足够的空间来存储 SHA-256 哈希值(32 字节)

返回值

  • 返回指向哈希值的指针(通常与 md 相同),如果 md 为 NULL,则该函数会使用静态分配的内存并返回该内存的指针

 RSA_sign RSA_verify 是 OpenSSL 库中用于 RSA 签名和验证的函数:

  • RSA_sign(int type,const unsigned char *m,unsigned int m_len,unsigned char *sigret,unsigned int *siglen,RSA *rsa):
  • RSA_verify(int type,const unsigned char *m,unsigned int m_len,const unsigned char *sigbuf,unsigned int *siglen,RSA *rsa):

参数说明

  • int type:要使用的哈希类型的 NID。例如,NID_sha256 表示 SHA-256
  • const unsigned char *m:指向要签名/验证的消息的哈希值
  • unsigned int m_len:哈希值的长度,以字节为单位
  • unsigned char *sigret:指向存储生成的签名的缓冲区的指针
  • unsigned int *siglen:指向存储生成的签名长度的变量的指针
  • const unsigned char *sigbuf:指向存储签名的缓冲区的指针
  • unsigned int siglen:签名的长度,以字节为单位
  • RSA *rsa:指向 RSA 密钥的指针

返回值

  • 成功返回 1,失败返回 0

BIO的概念和相关API

BIO 结构是 OpenSSL 中的一个核心概念,它用于处理各种 I/O 操作。BIO 是 Basic Input/Output 的缩写,代表了一个抽象的 I/O 流。BIO 可以用于多种不同的 I/O 操作,包括文件 I/O、内存 I/O、网络 I/O 等。

在 OpenSSL 中,BIO 提供了一种统一的接口来处理这些不同类型的 I/O 操作,使得程序员可以更方便地进行加密和解密操作,而不需要关心底层的 I/O 细节。

创建和释放 BIO 结构
  • BIO_new_file(const char *filename, const char *mode): 创建一个文件 BIO,用于文件读写。

参数解释

  • const char *filename:要打开的文件的名称(文件路径)。
  • const char *mode:文件打开模式,与标准 C 库中的 fopen 函数的模式参数相同。例如:
    • "r":以只读模式打开文件。
    • "w":以写入模式打开文件(如果文件不存在则创建文件)。
    • "a":以追加模式打开文件(数据写入到文件末尾)。

返回值

  • 成功时返回一个指向新 BIO 对象的指针。
  • 失败时返回 NULL。
  • BIO_new_mem_buf(const void *buf, int len): 创建一个内存 BIO,用于内存读写。
  • BIO_free_all(BIO *a): 释放一个 BIO 结构。
  • BIO_new_fp(FILE *stream,int close_flag):用于创建一个新的 BIO(Basic Input/Output)对象,并将其与一个文件指针关联起来的函数

参数解释

  • FILE *stream:一个标准 C 的文件指针,指向你希望 BIO 对象读取或写入的文件
  • int close_flag:一个标志,指定当 BIO 对象被释放时,是否关闭关联的文件。如果设置为 1,当 BIO 释放时文件也会被关闭;如果设置为 0,文件将保持打开状态

返回值

  • 成功时返回一个指向新 BIO 对象的指针,失败时返回 NULL
读写操作
  • BIO_read(BIO *b, void *buf, int len): 从 BIO 读取数据。
  • BIO_write(BIO *b, const void *buf, int len): 向 BIO 写入数据。

公钥/私钥的读取/写入(打印)

PEM_write_bio_RSAPublicKey PEM_write_bio_RSAPrivateKey 用于将 RSA 公钥和私钥以 PEM 格式写入 BIO 结构中。PEM(Privacy Enhanced Mail)格式是一种用于存储和传输加密密钥、证书和其他数据的标准格式,通常以 ASCII 编码表示。

  • PEM_write_bio_RSAPublicKey(Bio *bp, RSA *x)
  • PEM_write_bio_RSAPrivateKey(Bio *bp, RSA *x,const EVP_CIPHER *enc, unsigned char *kstr, int klen, pem_password_cb *cb, void *u)

参数说明

  • BIO *bp: 指向 BIO 结构的指针,用于指定输出位置,如文件或内存
  • RSA *x: 指向 RSA 结构的指针,包含要写入的公钥或私钥
  • const EVP_CIPHER *enc: 指向加密算法的指针,如果为 NULL,则不加密私钥
  • unsigned char *kstr: 密钥字符串,用于加密私钥
  • int klen: 密钥字符串的长度
  • pem_password_cb *cb: 回调函数,用于获取加密私钥所需的密码
  • void *u: 传递给回调函数的用户数据

返回值

  • 成功时返回 1,失败时返回 0

PEM_read_bio_RSAPrivateKeyPEM_read_bio_RSAPublicKey 用于从 BIO 对象中读取 PEM 格式的 RSA 私钥和公钥。这两个函数分别适用于私钥和公钥的读取操作,可以用来替代直接从文件读取密钥的方法。

  • PEM_read_bio_RSAPrivateKey(BIO *bio, RSA **x, pem_password_cb *cb, void *u)
  • PEM_read_bio_RSAPublicKey(BIO *bio, RSA **x, pem_password_cb *cb, void *u)

参数

  • bio:BIO 对象。
  • x:指向一个 RSA 结构的指针,通常传递 NULL,表示由函数内部创建新的 RSA 结构。
  • cb:用于提供私钥解密的回调函数,通常传递 NULL
  • u:用于私钥解密的用户数据,通常传递 NULL

返回值

  • 成功时返回指向 RSA 结构的指针,失败时返回 NULL

实践仿真

接下来就使用C语言的openssl库来分别实现刚刚所述的三个场景:

场景1实现

单独使用RSA加密/解密,确保消息的机密性

rsa_jiajiemi.c:

#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <stdio.h>
#include <string.h>

// 定义密钥长度和公钥指数
#define KEY_LENGTH 2048
#define PUB_EXP 3

// 如果定义了 PRINT_KEYS 则打印密钥
#define PRINT_KEYS

// 如果定义了 WRITE_TO_FILE 则写密钥到文件
#define WRITE_TO_FILE

// 错误处理函数,打印错误信息并终止程序
void handle_errors() {
    ERR_print_errors_fp(stderr);
    abort();
}

// 生成RSA密钥对的函数
RSA *generate_RSA_keypair() {
    RSA *rsa = RSA_new(); // 创建新的RSA对象
    BIGNUM *bn = BN_new(); // 创建新的大数对象

    // 设置公钥指数
    if (!BN_set_word(bn, PUB_EXP)) {
        handle_errors(); // 如果设置失败,则处理错误
    }

    // 生成密钥对
    if (!RSA_generate_key_ex(rsa, KEY_LENGTH, bn, NULL)) {
        handle_errors(); // 如果生成失败,则处理错误
    }

    BN_free(bn); // 释放大数对象
    return rsa; // 返回生成的RSA对象
}

// 将密钥写入文件的函数
void write_key_to_file(RSA *rsa) {
    // 写公钥到文件
    BIO *bp_public = BIO_new_file("public.pem", "w+");
    PEM_write_bio_RSAPublicKey(bp_public, rsa);
    BIO_free_all(bp_public);

    // 写私钥到文件
    BIO *bp_private = BIO_new_file("private.pem", "w+");
    PEM_write_bio_RSAPrivateKey(bp_private, rsa, NULL, NULL, 0, NULL, NULL);
    BIO_free_all(bp_private);
}

// 打印密钥的函数
void print_keys(RSA *rsa) {
    // 打印公钥到控制台
    BIO *bp_public = BIO_new_fp(stdout, BIO_NOCLOSE);
    PEM_write_bio_RSAPublicKey(bp_public, rsa);
    BIO_free_all(bp_public);

    // 打印私钥到控制台
    BIO *bp_private = BIO_new_fp(stdout, BIO_NOCLOSE);
    PEM_write_bio_RSAPrivateKey(bp_private, rsa, NULL, NULL, 0, NULL, NULL);
    BIO_free_all(bp_private);
}

// 执行加密和解密操作的函数
void rsa_encrypt_decrypt() {
    RSA *rsa = generate_RSA_keypair(); // 生成RSA密钥对

    #ifdef PRINT_KEYS
    print_keys(rsa); // 如果定义了 PRINT_KEYS,则打印密钥
    #endif

    #ifdef WRITE_TO_FILE
    write_key_to_file(rsa); // 如果定义了 WRITE_TO_FILE,则写密钥到文件
    #endif

    const char *message = "Hello, this is a test message."; // 要加密的消息
    unsigned char encrypted[KEY_LENGTH/8]; // 加密后的消息
    unsigned char decrypted[KEY_LENGTH/8]; // 解密后的消息
    
    // 使用公钥加密消息
    int encrypted_length = RSA_public_encrypt(strlen(message)+1, (unsigned char*)message, encrypted, rsa, RSA_PKCS1_OAEP_PADDING);
    if(encrypted_length == -1) {
        handle_errors(); // 如果加密失败,则处理错误
    }
    
    // 打印加密后的消息
    printf("Encrypted message: ");
    for(int i = 0; i < encrypted_length; i++) {
        printf("%02x", encrypted[i]);
    }
    printf("\n");

    // 使用私钥解密消息
    int decrypted_length = RSA_private_decrypt(encrypted_length, encrypted, decrypted, rsa, RSA_PKCS1_OAEP_PADDING);
    if(decrypted_length == -1) {
        handle_errors(); // 如果解密失败,则处理错误
    }

    // 打印解密后的消息
    printf("Decrypted message: %s\n", decrypted);

    RSA_free(rsa); // 释放RSA对象
}

int main() {
    // 初始化OpenSSL库
    OpenSSL_add_all_algorithms();
    ERR_load_BIO_strings();
    ERR_load_crypto_strings();

    // 执行加密和解密操作
    rsa_encrypt_decrypt();

    return 0;
}

场景2实现

单独使用RSA消息验证,确保消息的完整性和真实性

rsa_xxyz.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/sha.h>

// 生成RSA密钥对并保存到文件
void generate_keys(const char *private_key_file, const char *public_key_file) {
    RSA *rsa = RSA_new();
    BIGNUM *bn = BN_new();
    BN_set_word(bn, RSA_F4); // 设置密钥的公用指数

    if (RSA_generate_key_ex(rsa, 2048, bn, NULL) != 1) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }

    // 写公钥到文件
    BIO *bp_public = BIO_new_file(public_key_file, "wb");
    PEM_write_bio_RSAPublicKey(bp_public, rsa);
    BIO_free_all(bp_public);

    // 写私钥到文件
    BIO *bp_private = BIO_new_file(private_key_file, "wb");
    PEM_write_bio_RSAPrivateKey(bp_private, rsa, NULL, NULL, 0, NULL, NULL);
    BIO_free_all(bp_private);

    RSA_free(rsa);
    BN_free(bn);
}

// 使用私钥对消息进行签名
unsigned char* sign_message(const char *private_key_file, const char *message, size_t *sig_len) {
    BIO *priv_bio = BIO_new_file(private_key_file, "rb"); //rb 具体代表“读取二进制”模式
    if (!priv_bio) {
        perror("无法打开私钥文件");
        exit(EXIT_FAILURE);
    }

    RSA *rsa = PEM_read_bio_RSAPrivateKey(priv_bio, NULL, NULL, NULL);
    BIO_free_all(priv_bio);

    if (!rsa) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }

    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256((unsigned char*)message, strlen(message), hash); // 计算消息的SHA256哈希

    unsigned char *signature = (unsigned char*)malloc(RSA_size(rsa));
    if (RSA_sign(NID_sha256, hash, SHA256_DIGEST_LENGTH, signature, (unsigned int*)sig_len, rsa) != 1) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }

    RSA_free(rsa);
    return signature;
}

// 使用公钥验证签名
int verify_signature(const char *public_key_file, const char *message, unsigned char *signature, size_t sig_len) {
    BIO *pub_bio = BIO_new_file(public_key_file, "rb"); //rb 具体代表“读取二进制”模式
    if (!pub_bio) {
        perror("无法打开公钥文件");
        exit(EXIT_FAILURE);
    }

    RSA *rsa = PEM_read_bio_RSAPublicKey(pub_bio, NULL, NULL, NULL);
    BIO_free_all(pub_bio);

    if (!rsa) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }

    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256((unsigned char*)message, strlen(message), hash); // 计算消息的SHA256哈希

    int result = RSA_verify(NID_sha256, hash, SHA256_DIGEST_LENGTH, signature, sig_len, rsa);
    RSA_free(rsa);
    return result;
}

int main() {
    const char *private_key_file = "private_key.pem";
    const char *public_key_file = "public_key.pem";
    const char *message = "这是一个用于签名和验证的消息";

    // 生成RSA密钥对
    generate_keys(private_key_file, public_key_file);

    // 签名消息
    size_t sig_len;
    unsigned char *signature = sign_message(private_key_file, message, &sig_len);

    // 验证签名
    int verified = verify_signature(public_key_file, message, signature, sig_len);
    if (verified) {
        printf("签名验证成功\n");
    } else {
        printf("签名验证失败\n");
    }

    free(signature);
    return 0;
}

场景3实现

结合使用RSA加密/解密 和 消息验证,确保消息的机密性、完整性和真实性

#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/sha.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// 定义密钥长度和公钥指数
#define KEY_LENGTH 2048
#define PUB_EXP 3

// 错误处理函数,打印错误信息并终止程序
void handle_errors() {
    ERR_print_errors_fp(stderr);
    abort();
}

// 生成RSA密钥对的函数
RSA *generate_RSA_keypair() {
    RSA *rsa = RSA_new();
    BIGNUM *bn = BN_new();

    if (!BN_set_word(bn, PUB_EXP)) {
        handle_errors();
    }

    if (!RSA_generate_key_ex(rsa, KEY_LENGTH, bn, NULL)) {
        handle_errors();
    }

    BN_free(bn);
    return rsa;
}

// 将密钥写入文件的函数
void write_key_to_file(RSA *rsa) {
    BIO *bp_public = BIO_new_file("public.pem", "w+");
    PEM_write_bio_RSAPublicKey(bp_public, rsa);
    BIO_free_all(bp_public);

    BIO *bp_private = BIO_new_file("private.pem", "w+");
    PEM_write_bio_RSAPrivateKey(bp_private, rsa, NULL, NULL, 0, NULL, NULL);
    BIO_free_all(bp_private);
}

// 打印密钥的函数
void print_keys(RSA *rsa) {
    BIO *bp_public = BIO_new_fp(stdout, BIO_NOCLOSE);
    PEM_write_bio_RSAPublicKey(bp_public, rsa);
    BIO_free_all(bp_public);

    BIO *bp_private = BIO_new_fp(stdout, BIO_NOCLOSE);
    PEM_write_bio_RSAPrivateKey(bp_private, rsa, NULL, NULL, 0, NULL, NULL);
    BIO_free_all(bp_private);
}

// 使用私钥对消息进行签名
unsigned char* sign_message(RSA *rsa, const char *message, size_t *sig_len) {
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256((unsigned char*)message, strlen(message), hash);

    unsigned char *signature = (unsigned char*)malloc(RSA_size(rsa));
    if (RSA_sign(NID_sha256, hash, SHA256_DIGEST_LENGTH, signature, (unsigned int*)sig_len, rsa) != 1) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }

    return signature;
}

// 使用公钥验证签名
int verify_signature(RSA *rsa, const char *message, unsigned char *signature, size_t sig_len) {
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256((unsigned char*)message, strlen(message), hash);

    int result = RSA_verify(NID_sha256, hash, SHA256_DIGEST_LENGTH, signature, sig_len, rsa);
    return result;
}

// 执行加密、解密、签名和验证操作的函数
void rsa_operations() {
    RSA *rsa = generate_RSA_keypair();

    //print_keys(rsa);//打印密钥对的函数
    write_key_to_file(rsa);


    const char *message = "Hello, this is a test message.";

    //第一步,使用私钥对原始消息签名
    size_t sig_len;
    unsigned char *signature = sign_message(rsa, message, &sig_len);
    printf("消息签名成功!\n");

    //第二步,使用接收方的公钥对原始消息加密
    unsigned char encrypted[KEY_LENGTH/8];
    unsigned char decrypted[KEY_LENGTH/8];
    int encrypted_length = RSA_public_encrypt(strlen(message)+1, (unsigned char*)message, encrypted, rsa, RSA_PKCS1_OAEP_PADDING);
    if(encrypted_length == -1) {
        handle_errors();
    }

    printf("加密成功!加密后的信息: ");
    for(int i = 0; i < encrypted_length; i++) {
        printf("%02x", encrypted[i]);
    }
    printf("\n");


    //第三步,chengg接收方使用自己的私钥对密文进行解密,恢复出原始消息
    int decrypted_length = RSA_private_decrypt(encrypted_length, encrypted, decrypted, rsa, RSA_PKCS1_OAEP_PADDING);
    if(decrypted_length == -1) {
        handle_errors();
    }

    printf("解密成功!解密后的信息: %s\n", decrypted);

    //第四步,接收方使用发送方的公钥对解密后的消息进行签名验证,确认消息的完整性和真实性
    int verified = verify_signature(rsa, message, signature, sig_len);
    if (verified) {
        printf("签名认证成功\n");
    } else {
        printf("签名认证失败\n");
    }

    free(signature);
    RSA_free(rsa);
}

int main() {
    OpenSSL_add_all_algorithms();
    ERR_load_BIO_strings();
    ERR_load_crypto_strings();

    rsa_operations();

    return 0;
}

编译和运行

使用以下代码进行编译&运行:

gcc rsa_XXX.c -lssl -lcrypto
./a.out

 场景3运行结果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1714820.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小阿轩yx-Shell编程之正则表达式与文本处理器

小阿轩yx-Shell编程之正则表达式与文本处理器 正则表达式 &#xff08;RegularExpression&#xff0c;RE&#xff09; 正则表达式概述 正则表达式的定义 又称 正规表达式常规表达式 代码中常简写为 regex、regexp 或 RE 正则表达式 使用单个字符串来描述、匹配一系列符…

社会网络,生态网络,贸易网络,复杂网络边介数蓄意和随机攻击(增边策略)

网络分析工具使用说明 简介 本工具是一个用于进行网络分析的客户端应用。用户可以加载包含网络边信息的Excel文件&#xff0c;根据设定的百分比增加网络边&#xff0c;并将结果导出为新的Excel文件。以下是详细的使用说明。 使用步骤 1. 加载输入文件 输入文件: 输入文件…

大数据技术分享 | Kylin入门系列:基础介绍篇

Kylin入门教程 在大数据时代&#xff0c;如何高效地处理和分析海量数据成为了企业面临的挑战之一。Apache Kylin作为一个开源的分布式分析引擎&#xff0c;提供了Hadoop之上的SQL查询接口及多维分析&#xff08;OLAP&#xff09;能力&#xff0c;使得对超大规模数据集的分析变…

4月平板电脑行业线上销售数据分析

由于全球科技发展趋势&#xff0c;如AI技术的应用&#xff0c;以及厂商新品发布计划&#xff1b;同时&#xff0c;平板电脑作为个人电脑的延伸产品&#xff0c;其便携性和生产力相较于手机具有明显优势&#xff0c;这也为行业的进一步发展提供了动力。 据鲸参谋数据统计&#…

【PB案例学习笔记】-12秒表实现

写在前面 这是PB案例学习笔记系列文章的第11篇&#xff0c;该系列文章适合具有一定PB基础的读者。 通过一个个由浅入深的编程实战案例学习&#xff0c;提高编程技巧&#xff0c;以保证小伙伴们能应付公司的各种开发需求。 文章中设计到的源码&#xff0c;小凡都上传到了gite…

安卓赤拳配音v1.0.2Ai配音神器+百位主播音色

Ai配音神器 本人自用版本&#xff01;超级稳定&#xff01;百位主播音色 登陆即可用 链接&#xff1a;https://pan.baidu.com/s/1WVsrYZqLaPAriHMMLMdPBg?pwdz9ru 提取码&#xff1a;z9ru

如何编写高效的单片机代码?

单片机的程序比软开少一些&#xff0c;真正想编写出高效的代码&#xff0c;还是要积累很多年的。 在做研发工程师的10年里&#xff0c;我经历过几个公司&#xff0c;看过很多工程师写的代码&#xff0c;但真正能让我跪着看完的&#xff0c;极少。哪怕是大厂工程师&#xff0c;也…

装机必备——截图软件PixPin安装教程

装机必备——截图软件PixPin安装教程 软件下载 软件名称&#xff1a;PixPin 1.5 软件语言&#xff1a;简体中文 软件大小&#xff1a;30.1M 系统要求&#xff1a;Windows7或更高&#xff0c; 64位操作系统 硬件要求&#xff1a;CPU2GHz &#xff0c;RAM2G或更高 下载通道①迅…

断开自定义模块与自定义库的链接

断开自定义模块与自定义库的链接 1、断开模块与库的链接 1、断开模块与库的链接 如果摸个库文件添加到模型中&#xff0c;无法“Disable Link”时&#xff0c;可以使用save_system命令进行断开到模型中用户定义的库模块的链接&#xff1b; 参考链接&#xff1a; 传送门 save…

软件无线电学习-发射机体系结构

本文知识内容摘自《软件无线电原理和应用》 软件无线电主要由发射机和接收机两大部分组成。软件无线电发射机的主要功能是把需发射或传输的用户信息(话音、数据或图像)经基带处理(完成诸如FM、AM、FSK、PSK、MSK、QAM 等调制)和上变频&#xff0c;调制到规定的载频(中心频率)上…

leetCode-hot100-数组专题之子数组+二维数组

数组专题之子数组二维数组 子数组238.除自身以外数组的乘积560.和为K的子数组 二维数组48.旋转图像 子数组 数组的子数组问题是算法中常见的一类问题&#xff0c;通常涉及到数组的连续元素。在解决这类问题时&#xff0c;常用的方法有前缀和、滑动窗口、双指针&#xff0c;分治…

SAP 没有项目类别表存在(表 T184L LF LEIH CHSP)

在项目上&#xff0c;客户在废品出库的时候&#xff0c;出现这个报错 查了相关资料&#xff0c;是因为后台确少配置&#xff1a;IMG-后勤执行-装运-交货-在交货时定义项目类别确定

敏感数据的授权和传输加密解决方案

需求背景&#xff1a;解决敏感数据的访问授权和安全传输。 KSP密钥管理系统结合USB Key实现CA证书签发的过程可以大致分为以下几个步骤&#xff1a; 1. 生成密钥对&#xff1a; 用户首先使用USB Key生成一对密钥&#xff0c;包括公钥和私钥。公钥用于加密和验证数字签名&…

Keil5 ~STM32报错Solutions#1

一、error: #268: declaration may not appear after executable statement in block

STM32的时钟介绍

目录 前言1. 简介1.1 时钟是用来做什么的1.2 时钟产生的方式 2. 时钟树的组成2.1 时钟源2.1.1 内部时钟2.1.2 外部时钟 2.2 PLL锁相环2.3 SYSCLK2.4 AHB和HCLK2.5 APB和PCLK2.6 总结 3. STM32时钟的如何进行工作4.我的疑问4.1 使用MSI和HSI有什么区别吗&#xff1f;4.2 MSI的频…

tensorrt输出结果为nan的解决方案

系统环境&#xff1a; ubuntu20.04 python3.9 cuda11.8 cudnn8.9.7.29 torch1.13.1cu117&#xff08;pip install torch1.13.1&#xff09; 1.针对cuda版本查了一下trt支持版本&#xff0c;发现V10和V8版本都支持 本着用新不用旧标准&#xff0c;果断下载了8.6&#xff0c…

【4.vi编辑器使用(下)】

一、vi编辑器的光标移动 二、vi编辑器查找命令 1、命令&#xff1a;:/string 查找字符串 n&#xff1a;继续查找 N&#xff1a;反向继续查找 /^the 查找以the开头的行 /end 查找以 查找以 查找以结尾的行 三、vi编辑器替换命令 1、语法: : s[范围,范围]str1/str2[g] g表示全…

2.1.2 基于配置方式使用MyBatis

文章目录 实战目标实战步骤1. 创建Maven项目2. 添加项目依赖3. 创建用户实体类4. 创建用户映射器配置文件5. 创建MyBatis配置文件6. 创建日志属性文件7. 测试用户操作8. 运行测试方法 预期结果实战方法结论 实战目标 本实战的目标是演示如何使用MyBatis框架来操作数据库。通过…

深度学习21天 —— 卷积神经网络(CNN):识别验证码( 第12天)

目录 一、前期准备 1.1 标签数字化 1.2 加载数据 1.3 配置数据 二、其他 2.1 损失函数 categorical_crossentropy 2.2 plt.legend(loc ) 2.3 history.history 活动地址&#xff1a;CSDN21天学习挑战赛 学习&#xff1a;深度学习100例-卷积神经网络&#xff08;CNN&…

什么是访问控制漏洞

什么是AC Bugs&#xff1f; 实验室 Vertical privilege escalation 仅通过隐藏目录/判断参数来权限控制是不安全的&#xff08;爆破url/爬虫/robots.txt/Fuzz/jsfinder&#xff09; Unprotected functionality 访问robots.txt 得到隐藏目录&#xff0c;访问目录 &#xff0c;…