使用C语言openssl库实现 RSA加密 和 消息验证
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≤𝑚<𝑛
- 使用公钥 (𝑒,𝑛)进行加密,得到密文 𝑐=
(mod 𝑛)
解密
- 使用私钥 (𝑑,𝑛) 进行解密,恢复明文 𝑚=
(mod 𝑛)

RSA消息验证步骤
签名
- 发送方使用其私钥对消息进行签名。具体做法是对消息进行哈希处理,得到一个消息摘要,然后使用私钥对这个摘要进行加密,生成签名
- 签名与原始消息一起发送给接收方
验证
- 接收方使用发送方的公钥对签名进行验证。具体做法是对收到的签名使用公钥解密,得到解密后的摘要
- 接收方同时对收到的原始消息进行哈希处理,得到本地计算的消息摘要
- 比较解密后的摘要和本地计算的摘要,如果两者相同,则验证成功,消息没有被篡改且确实来自发送方
举个例子:
假设Alice要给Bob发送一条消息,并且需要确保Bob知道这条消息确实来自Alice且没有被篡改,Alice和Bob可以使用RSA进行消息签名和验证。
Alice生成密钥对:
- 公钥:(𝑒,𝑛)
- 私钥:(𝑑,𝑛)
Alice签名消息:
- 对消息进行哈希处理,得到消息摘要 𝐻(𝑀)
- 使用私钥对消息摘要进行加密,生成签名 𝑆=𝐻(𝑀)𝑑 (mod 𝑛)
- 将消息 𝑀 和签名 𝑆 发送给Bob。
Bob验证签名:
- 使用公钥对签名进行解密,得到解密后的摘要 𝐻 ′(𝑀)=𝑆𝑒 (mod 𝑛)
- 对收到的消息 𝑀 进行哈希处理,得到本地计算的消息摘要 𝐻(𝑀)
- 比较解密后的摘要 𝐻 ′(𝑀)和本地计算的摘要 𝐻(𝑀),如果两者相同,则验证成功。
通过上述过程,Bob可以确认消息确实来自Alice且未被篡改。
RSA加密/解密 & RSA消息验证
在刚刚的消息验证 例子中,公钥用于解密,而私钥用于加密,这似乎违背了在开头所说的“公钥加密,私钥解密”,其实并没有,这是因为RSA加/解密 和 RSA消息验证是两个不同的概念:
他们可以各自单独使用,也可以结合使用,以下是三个不同的场景帮助理解:
1. 单独使用RSA加密
场景1:确保消息的机密性
步骤:
- 密钥生成:接收方生成一对RSA密钥对(公钥和私钥)。
- 公钥分发:接收方将公钥分发给发送方。
- 消息加密:
- 发送方使用接收方的公钥对消息进行加密。
- 发送方将加密后的消息发送给接收方。
- 消息解密:
- 接收方使用自己的私钥对加密的消息进行解密,恢复原始消息

2. 单独使用RSA消息验证(数字签名)
场景2:确保消息的完整性和真实性
步骤:
- 密钥生成:发送方生成一对RSA密钥对(公钥和私钥)。
- 公钥分发:发送方将公钥分发给接收方。
- 消息签名:
- 发送方对消息进行哈希处理,生成消息摘要。
- 发送方使用自己的私钥对消息摘要进行加密,生成签名。
- 发送方将原始消息和签名一起发送给接收方。
- 签名验证:
- 接收方使用发送方的公钥对签名进行解密,得到解密后的消息摘要。
- 接收方对收到的原始消息进行哈希处理,生成本地计算的消息摘要。
- 接收方比较解密后的消息摘要和本地计算的消息摘要,如果两者相同,则验证成功。

3. 结合使用RSA加密和消息验证
场景3:确保消息的机密性、完整性和真实性
步骤:
- 密钥生成:
- 发送方和接收方各自生成一对RSA密钥对(各自的公钥和私钥)。
- 公钥分发:
- 发送方和接收方互相交换公钥。
- 消息签名和加密:
- 发送方对消息进行哈希处理,生成消息摘要。
- 发送方使用自己的私钥对消息摘要进行加密,生成签名。
- 发送方将签名附加到原始消息上。
- 发送方使用接收方的公钥对包含签名的消息进行加密。
- 发送方将加密后的消息发送给接收方。
- 消息解密和验证:
- 接收方使用自己的私钥对加密的消息进行解密,得到原始消息和签名。
- 接收方使用发送方的公钥对签名进行解密,得到解密后的消息摘要。
- 接收方对收到的原始消息进行哈希处理,生成本地计算的消息摘要。
- 接收方比较解密后的消息摘要和本地计算的消息摘要,如果两者相同,则验证成功。
相关函数认识
密钥对生成相关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-256const 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_RSAPrivateKey 和 PEM_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_KEYSprint_keys(rsa); // 如果定义了 PRINT_KEYS,则打印密钥#endif#ifdef WRITE_TO_FILEwrite_key_to_file(rsa); // 如果定义了 WRITE_TO_FILE,则写密钥到文件#endifconst 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运行结果:

相关文章:
使用C语言openssl库实现 RSA加密 和 消息验证
Q:什么是RSA? A:RSA(Rivest-Shamir-Adleman)是一种非对称加密算法,是最早的一种用于公开密钥加密和数字签名的算法。它使用一对公钥(public key)和私钥(private key&…...
海外投放面试手册
海外投放面试手册 岗位职责: 负责Google 、Facebook、TikTok、Twitter等海外主流广告平台的自主投放操作及合作渠道沟通;负责海外合作渠道媒体的广告投放管理、媒体数据监测、效果分析、优化调整等工作; 3.了解海外各渠道&…...
第十三章 进程与线程
第十三章 进程与线程 程序与进程的概念 程序: 英文单词为Program,是指一系列有序指令的集合,使用编程语言所编写,用于实现一定的功能。 进程: 进程则是指启动后的程序,系统会为进程分配内存空间。 函数式…...
Flink面试整理-对Flink的高级特性如CEP(复杂事件处理)、状态后端选择和调优等有所了解
Apache Flink 提供了一系列高级特性,使其成为一个强大的实时数据处理框架,特别适用于复杂的数据处理场景。其中,复杂事件处理(CEP)、状态后端的选择和调优是其中重要的几个方面。 复杂事件处理(CEP) CEP 概念:CEP 是用于在数据流中识别复杂模式的技术。它允许用户指定事…...
算法:树状数组
文章目录 面试题 10.10. 数字流的秩327. 区间和的个数315. 计算右侧小于当前元素的个数 树状数组可以理解一种数的存储格式。 面试题 10.10. 数字流的秩 假设你正在读取一串整数。每隔一段时间,你希望能找出数字 x 的秩(小于或等于 x 的值的个数)。 请实现数据结构…...
Kafka SASL_SSL集群认证
背景 公司需要对kafka环境进行安全验证,目前考虑到的方案有Kerberos和SSL和SASL_SSL,最终考虑到安全和功能的丰富度,我们最终选择了SASL_SSL方案。处于知识积累的角度,记录一下kafka SASL_SSL安装部署的步骤。 机器规划 目前测试环境公搭建了三台kafka主机服务,现在将详…...
同城交友论坛静态页面app Hbuild
关注...
spring session+redis存储session,实现用户登录功能,并在拦截器里面判断用户session是否过期,过期就跳转到登录页面
在Spring应用中,使用Redis存储Session是一种常见的方式,可以实现分布式环境下的Session管理。以下是实现用户登录功能,并在拦截器中判断Session是否过期并跳转到登录页面的基本步骤: 添加依赖:首先,确保你的…...
Debug-013-el-loading中显示倒计时时间
前言: 今天实现一个小小的优化,业务上是后端需要从设备上拿数据,所以前端需要不断调用一个查询接口,直到后端数据获取完毕,前后端根据一个ending字段为true判断停止调用查询接口。由于这个查询时间比较久&…...
5月29日,每日信息差
第一、据悉,微信视频号直播电商团队将并入到微信开放平台(小程序、公众号等)团队,原微信视频号直播电商团队转由微信开放平台负责人负责。知情人士表示,此次调整,将有助于微信视频号直播电商业务更好地融入…...
2024年弘连网络FIC大会竞赛题线下决赛题
总结: FIC决赛的时候,很多小问题没发现,在pve平台做题确实很方便。 这套题目复盘完,服务器这块的知识确实收获了很多,对pve集群平台和网络拓扑也有了一定的认识,感谢各位大佬悉心指导。 接下来࿰…...
Element-UI 入门指南:从安装到自定义主题的详细教程
Element-UI 是一个基于 Vue.js 的前端组件库,它提供了丰富的 UI 组件,可以帮助开发者快速构建高质量的用户界面。以下是使用 Element-UI 的快速入门指南: 安装 Element-UI Element-UI 是一个基于 Vue.js 的组件库,它提供了丰富的…...
vs工程添加自定义宏
一、简介 用户可以添加自定义宏变量方便工程路径名称的修改和配置 例:$(SolutionDir) 为解决方案路径,$(PojectDir) 为工程所在路径 测试环境:vs2017,qt5.14.0 二、配置 1、打开属性窗口:视图-》其他窗口-》属性管…...
shell脚本:将一维数组以二维数组显示
shell脚本:将一维数组改成二维数组显示 1.编辑脚本文件 vi output_array.sh2.编写脚本 #!/bin/bash# 假设一维数组one_array已经包含9个元素 one_array(1 2 3 4 5 6 7 8 9) # 获取数组长度 length${#one_array[]} # 数组长度除以3获得新数组行数n n$((length / …...
QT C++ 读写mySQL数据库 图片 例子
在上篇文章中描述了怎样搭建读写数据库的环境。 本文更进一步,描述了读写mySQL数据库,字符、整型数字、图片。读写图片相对难点。 数据库的图片字段用BLOB,如果图片较大要用longblob,否则会报错。 另外,读写数据库都使用了短连…...
Unix环境高级编程--8-进程控制---8.1-8.2进程标识-8.3fork函数-8.4 vfork函数
1、进程控制几个过程 创建进程--》执行进程---》终止进程 2、进程标识 (1)专用进程:ID为0的进程是调度进程,常常被称为交换进程,也称为系统进程; ID为1通常是init进程,在自举结束时由内核调用…...
Facebook之魅:数字社交的体验
在当今数字化时代,Facebook作为全球最大的社交平台之一,承载着数十亿用户的社交需求和期待。它不仅仅是一个简单的网站或应用程序,更是一个将世界各地的人们连接在一起的社交网络,为用户提供了丰富多彩、无与伦比的数字社交体验。…...
【重装windows遇到网络适配器无法更改】
可以尝试手动在cmd中修改,命令: netsh interface ip set address name"以太网 x" static 新IP地址 子网掩码 网关 注意以太网x之间有空格,以太网外面的引号是英文的。 也可以先在cmd依次输入“netsh”、“interface”࿰…...
FFmpeg+QT播放器实战1---UI页面的设计
1、播放器整体布局的设计 该部分使用QT的UI工具,进行整体页面设置,如下图1所示: 2、控制布局的设计 创建ctrBar的UI页面并进行页面布局设置,如下图2所示: 将图1中ctrBarWind对象提升为ctrBar类(该界面替代原先的控…...
C/C++语法|pthread线程库的使用
笔记主要内容来自 爱编程的大柄–线程 爱编程的大柄–线程同步 在进入代码实践之前,我们应该搞清楚。 线程是成语的最小执行单位,进程是操作系统中最小的资源分配单位。 这样的话我们可以理解以下两点: 同一地址空间中的多个线程独有的是&…...
深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
论文阅读:Matting by Generation
今天介绍一篇关于 matting 抠图的文章,抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法,已经有很多的工作和这个任务相关。这两年 diffusion 模型很火,大家又开始用 diffusion 模型做各种 CV 任务了&am…...
基于开源AI智能名片链动2 + 1模式S2B2C商城小程序的沉浸式体验营销研究
摘要:在消费市场竞争日益激烈的当下,传统体验营销方式存在诸多局限。本文聚焦开源AI智能名片链动2 1模式S2B2C商城小程序,探讨其在沉浸式体验营销中的应用。通过对比传统品鉴、工厂参观等初级体验方式,分析沉浸式体验的优势与价值…...
如何通过git命令查看项目连接的仓库地址?
要通过 Git 命令查看项目连接的仓库地址,您可以使用以下几种方法: 1. 查看所有远程仓库地址 使用 git remote -v 命令,它会显示项目中配置的所有远程仓库及其对应的 URL: git remote -v输出示例: origin https://…...
边缘计算网关提升水产养殖尾水处理的远程运维效率
一、项目背景 随着水产养殖行业的快速发展,养殖尾水的处理成为了一个亟待解决的环保问题。传统的尾水处理方式不仅效率低下,而且难以实现精准监控和管理。为了提升尾水处理的效果和效率,同时降低人力成本,某大型水产养殖企业决定…...
