使用openssl库进行数据加密与签名


前言

提示:这里可以添加本文要记录的大概内容:
工作中常常听到很多人把加密,签名的概念搞混,甚至把数据摘要叫做加密,再加上openssl库的资料比较杂,决定写一篇文章来做个总结。

文章主要讲两个内容,并附实战代码
1、RSA加密与签名的区别及信息防泄漏与信息防篡改的做法
2、openssl开源库的使用


一、RSA加密与签名的区别与作用

openssl中有很多对称加密算法和非对称加密算法,其中就有RSA算法的实现。RSA算法是第一个既能用于数据加密,也能用于数据签名的算法。那么加密与签名的区别是什么呢。
单纯的讨论加密与签名的区别其实意义不大,而是要看我们的目的是什么,也就是数据加密或数据签名各有什么作用才是理解的关键。

1. 数据加密
数据揭秘的作用是防止信息泄露。
一般来说使用RSA的加密过程如下:
(1)A(一般是服务器端,PKI系统)生成一对公私钥对,私钥A自己保存,不对外公开,公钥则公开x,通常是服务器通过某种下发证书的方式给到指定客户端;
(2)B(通常为客户端)收到A的公钥后(一般是从证书中提取),使用A给的公钥对信息加密;
(3)若A收到B通过公钥加密的信息,则可以使用私钥对数据进行解密;
因为私钥只有A拥有,也就是说A才能解密数据,那么及时在A将公钥下发的过程,或是B数据传输的过程,被黑客拦截,但黑客无法对数据进行解密,因此达到了防信息泄露的目的。
这个过程存在的风险在于,如果公钥被黑客截获后,黑客可以对数据进行篡改后或伪造数据,再利用公钥进行加密。A端收到数据后能正常解密,但并不知道数据已经被篡改了或该数据是伪造的。

2.数据签名
数据签名的作用是防止数据被篡改(或者说篡改了能被发现)
RSA数据签名的过程如下:
(1)B(一般是客户端)生成一对公私钥,私钥B自己保留(一般要求安全存储),不对外公开,公钥公开,通常随数据一块发送给对端;
(2)B使用自己的私钥对源数据的摘要加密(因为摘要是唯一且不可逆的,摘要值不变,说明数据也没变,就形成了签名,B将源数据和签名一起发送给A;
(3)A收到数据和签名后,使用同样的摘要算法进行摘要计算,并使用B给的公钥对公钥对签名值解密,若解密后的摘要值与A端计算的摘要值一致,则说明数据没被篡改;
这个过程中,如果黑客截获了数据和B发给A的公钥,以及数据签名,但由于只有使用私钥才能对数据进行签名,因此黑客没办法伪造数据和其签名值,因此能防止信息被篡改。但因为数据本身是明文,因此信息会被泄露。

那么没有既防止信息泄露又防止数据被篡改的方法呢,答案就是同时使用加密和签名,但通常使用非对称算法进行加密,资源消耗会远大于对称算法进行加密。

二、使用openssl库完成数据的签名

1.openssl的学习与使用的个人建议

网上的openssl资料比较杂乱,很多人刚用可能感觉这个能用那个能用,但又搞不清楚到底用哪个。因为openssl是个宝库,除了信息安全的用途,里面的hash函数hash表的用处也很多,个人也不敢说是精通openssl的使用。写点个人经验仅供参考,诸君如有个人更好的使用方法欢迎分享探讨 。
学习openssl,个人建议要抓住三个点:
(1)openssl的源码框架。
这个建议大家去看看网上一搜就能找到的《openssl编程手册》,某个大佬抽空总结汇总的,看的过程中结合openssl源码,主要先理解它的设计框架,大致划分下来,其实主要也就是几部分:哈希函数库,对称、非对称加解密函数库;openssl自身实现的对各种对称,非对称算法的封装;数据的编码库;engine的支持。看的时候可以适当地试试书中的例子,但切勿陷入其中。
(2)具体问题具体函数用时再去查找
在了解了其框架之后,你要解决某个问题你就知道该往哪个方向去查找了。在使用的过程中,思路大概就是确定要使用哪个库,再确定要使用哪个接口及对应接口名,可通过搜索或在头文件中查找指定接口的方式,然后通查找该接口的使用方法,或通过头文件及源码,定位到实现。
由于书中有些例子有缺陷,因此建议不要仅通过书中的例子来使用openssl。

2.数据签名的实现

由于我们要实现的是使用本地的私钥对数据摘要进行加密,不好使用EVP封装好的签名与验签库,我们直接使用RSA库,其实EVP调用的也是RSA算法库的实现。这里就直接上代码,代码中有对应较详细的注释。

首先我们实现秘钥的生成,实际使用中,秘钥的生成和使用秘钥进行加解密一般是独立的。
对库的移植,头文件的包含就不再赘述了。

#include <openssl/rsa.h>
#include <openssl/objects.h>
#include <openssl/pem.h>
#include <openssl/sha.h>

#define INFO printf
#define RSA_MODE RSA_2048
#define RSA_2048 2048

int main(int argc, char *argv[])
{	
	int i;
	int ret;
	//初始化key生成必要参数
	int bits = RSA_MODE;
	BIGNUM *bne;
	RSA *r;
	//大数相关函数还可以看看以下博客
	//http://blog.sina.com.cn/s/blog_4fcd1ea30100sj05.html
	r = RSA_new();
	bne = BN_new();
	//一般bne设置为65537,也即RSA_F4
	ret = BN_set_word(bne, RSA_F4);
	ret = RSA_generate_key_ex(r, bits, bne, NULL);
	if(ret != 1)
	{
		INFO("RSA_generate_key_ex err!\n");
		return -1;
	}
	//打印生成信息
	RSA_printf_fp(stdout, r, 11);
	//到此,已生成公私钥对包含在r结构中
	//其实已经可以使用r来进行加解密,但实际中一般不会这样做
	//提取公私钥生成各自公私钥文件
	//这里直接写个路径,读者自己替换自己的路径即可
	FILE *pri_key_file = fopen("./pri_key.pem", "w+");
	FILE *pub_key_file = fopen("./pub_key.pem", "w+");
	//密钥对象的IO读写共有四种:公钥对象PUBKEY的IO,如
	//PEM_read_bio_PUBKEY(),PEM_read_PUBKEY()
	//RSA私钥对象RSAPrivateKey的IO,如:PEM_read_bio_RSAPrivateKey()
	//RSA公钥对象RSAPublicKey的IO
	//RSA公钥对象RSA_PUBKEY的IO
	//可参考此文:
	//https://blog.csdn.net/lazyclough/article/details/7646696
	PEM_write_RSAPrivateKey(pri_key_file, r, NULL, NULL, 0, NULL, NULL);
	PEM_write_RSA_PUBKEY(pub_key_file, r);
	RSA_free(r);
	fclose(pri_key_file);
	fclose(pub_key_file);
	return 0;
}

通过上述程序,我们可以得到一个公钥文件和一个私钥文件,我们另外一个程序怎么通过公钥文件来读取密钥来加解密呢,代码如下:

#include <openssl/rsa.h>
#include <openssl/objects.h>
#include <openssl/pem.h>
#include <openssl/sha.h>

#define INFO printf
#define RSA_MODE RSA_2048
#define RSA_2048 2048
//使用RSA_PKCS1_PADDING填充模式时,输入的长度要比RSA密钥模长短11字节
//其他填充模式的自行查找
#define RSA_INPUT_LEN ((RSA_MODE / 8) - 11)
#define BUFLEN 32767

RSA *prikey;
RSA *pubkey;
//文件hash值的计算
//这里计算sha256值,通过参数hash_value传出
void sha256_file(const char *filepath, const char *filemode, unsigned char *hash_value)
{
	FILE *fp = fopen(filepath, filemode);
	if(fp == NULL)
	{
		INFO("file open failed\n");
		exit(0);
	}
	unsigned char temp_hash_val[SHA256_DIGEST_LENGTH];
	SHA256_CTX sha256_ctx;
	SHA256_Init(&sha256_ctx);
	unsigned char readbuf[BUFLEN];
	unsigned int read_bytes = 0;
	while((read_bytes = fread(readbuf, 1, BUFLEN, fp)))
	{
		SHA256_Update(&sha256_ctx, readbuf, read_bytes);
	}
	SHA256_Final(temp_hash_value, &sha256_ctx);
	strcpy(hash_value, temp_hash_value);
	fclose(fp);	
}

//使用rsa加密的函数
void rsa_hash_val(unsigned char *rsa_input, int inlen, unsigned char *rsa_output, RSA **r, int padding)
{
	int en_count = 0;
	int i = 0;
	int flen;
	if(inlen > RSA_INPUT_LEN)
	{
		en_count = inlen / RSA_INPUT_LEN;
		flen = RSA_INPUT_LEN;
	}
	for(i = 0; i <en_count; i++)
	{
		RSA_private_encrypt(flen, rsa_input + i * flen, rsa_output + i * RSA_OUTPUT_LEN, *r, padding);
	}
	flen = inlen % RSA_INPUT_LEN;
	RSA_private_encrypt(flen, rsa_input + i * flen, rsa_output + i * RSA_OUTPUT_LEN, *r, padding);	
}

//初始化rsa, 读取密钥
void init_rsa()
{
	prikey = RSA_new();
	pubkey = RSA_new();
	FILE *pri_key_file = fopen("./pri_key.pem", "r+");
	FILE *pub_key_file = fopen("./pub_key.pem", "r+");
	prikey = PEM_read_RSAPrivateKey(pri_key_file, &prikey, NULL, NULL);
	pubkey = PEM_read_RSA_PUBKEY(pub_key_file, &pubkey, NULL, NULL);
}
int main()
{
	int i;
	unsigned char file_hash[SHA256_DIGEST_LENGTH + 1];
	unsigned char signature[RSA_OUTPUT_LEN + 1];
	memset(file_hash, 0, SHA256_DIGEST_LENGTH + 1);
	sha256_file("./xxx.txt", "rb", file_hash);
	init_rsa();
	rsa_hash_val(file_hash, SHA256_DIGEST_LENGTH, signature, &prikey, RSA_PKCS1_PADDING);
	//至此,对数据摘要已加密完毕,读者可使用%X的输出形式将摘要值和签名值一个字节一个字节地打印
	//并且使用公钥进行解密,看看加密前和解密前和解密后是否一致,在此就不再演示
	free(prikey);
	free(pubkey);
	return 0;
}
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值