银行安全传输平台(五)OpenSSL配置和RSA模块


前言

为了实现密钥的交换,我们需要将协商好的对称加密密钥使用RSA加密传输,这里我们就要用到OpenSSL库,因为博主本身在校期间已经花了很大一部分时间在学密码,所以本篇笔记就不再详细讲述RSA或者哈希函数背后的数论知识,只简单介绍接口,封装好我们所要用的模块即可。


一、OpenSSL

OpenSSL 是一个安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议,并提供丰富的应用程序供测试或其它目的使用。

1.1 配置OpenSSL

因为这个项目主要实在Linux上运行,所以我主要记录Linux下如何配置,Windows稍微麻烦一点,感兴趣的可以自己了解一下。
安装包下载地址:https://www.openssl.org/source/
这里我以openssl-3.0.13为例
首先解压缩:

$ tar zxvf openssl-3.0.13.tar.gzip
# 加压完成得到目录: openssl-3.0.13

然后进入解压目录openssl-3.0.13,安装:

$ ./config
$ make
$ make test 		
$ make install	 (使用管理员权限)

然后验证是否安装成功:

openssl version -a

如果安装成功应该会输出版本号

二、密码学基础

这里我们简单记录一下密码学的一些基础概念,以理解这些东西在整个加密通信过程中做了些什么。
加密三要素、对称加密非对称加密这些概念不再介绍,主要理解对称加密效率高非对称加密效率低和几种常用的加密方式,我们在这个项目中,用rsa来进行密钥交换,用aes进行加密通信即可。
接下来我们主要介绍秘钥交换、数字前面、消息认证码、哈希算法等知识。

2.1 秘钥交换

假设我们有两个要通信的双方客户端c和服务端s
非对称加密秘钥分发方便, 但是效率低 -> 改进: 需要使用对称加密;
使用对称加密 -> 秘钥分发困难 -> 改进: 使用非对称加密进行秘钥分发。

  • 分发是对称加密的秘钥, 本质就是一个字符串
    1. 在服务器端生成一个非对称加密的密钥对: 公钥, 私钥
    2. 服务器将公钥发送给客户端, 客户端有了公钥
    3. 在客户端生成一个随机字符串 -> 这就是对称加密需要使用的秘钥
    4. 在客户端使用公钥对生成的对称加密的秘钥进行加密 -> 密文
    5. 将加密的密文发送给服务器
    6. 服务器端使用私钥解密 -> 对称加密的秘钥
    7. 双方使用同一秘钥进行对称加密通信

2.2 哈希算法

简单说,不管原始数据多长,经过哈希算法计算,得到固定长度的数据,一个安全的哈希算法应该是抗碰撞的(很难找到两个数据对应到同一个数据),所以可以用来将进行勾走消息认证码HMAC。
一定要注意的是哈希算法不是加密算法。

2.3 消息认证码

主要作用是用来检验数据是否被篡改
(原始数据 + 秘钥) * 哈希函数 = 消息认证码
发送方将原始数据和消息认证码加密之后发送出去,接收方解密之后用同样的算法对原始数据进行哈希看看是否匹配,匹配则说明没有被篡改,否则说明被篡改了。

2.4 数字签名

用私钥签名公钥验证,一个安全的签名算法应该还要加上消息认证码,主要作用是验证发送者的身份,证明发送者是发送者。

三、封装密码学接口

关于openssl的api这里不再介绍,我们直接来看如何封装我们所要用到的接口。
首先还是要说明的是这里面有一个base64编码的用法我们留到下一篇笔记解释

3.1 RsaCrypto.h

#pragma once
#include <string>
#include <openssl/rsa.h>
#include <openssl/pem.h>

using namespace std;

enum SignLevel
{
	Level1 = NID_md5,
	Level2 = NID_sha1,
	Level3 = NID_sha224,
	Level4 = NID_sha256,
	Level5 = NID_sha384,
	Level6 = NID_sha512
};

class RsaCrypto
{
public:
	RsaCrypto();
	RsaCrypto(string fileName, bool isPrivate = true);
	~RsaCrypto();

	// 通过解析字符串得到秘钥
	void parseKeyString(string keystr, bool pubKey = true);
	// 生成RSA密钥对
	void generateRsakey(int bits, string pub = "public.pem", string pri = "private.pem");
	// 公钥加密
	string rsaPubKeyEncrypt(string data);
	// 私钥解密
	string rsaPriKeyDecrypt(string encData);
	// 使用RSA签名
	string rsaSign(string data, SignLevel level = Level3);
	// 使用RSA验证签名
	bool rsaVerify(string data, string signData, SignLevel level = Level3);

	// base64编码
private:
	string toBase64(const char* str, int len);
	// base64解码
	char* fromBase64(string str);
	// 得到公钥
	bool initPublicKey(string pubfile);
	// 得到私钥
	bool initPrivateKey(string prifile);

private:
	RSA* m_publicKey;	// 私钥
	RSA* m_privateKey;	// 公钥
};


由于c++中不建议使用宏,所以我们使用常量/枚举/内联->空间换时间,这里我们默认用sha224来进行签名和认证。

3.2 RsaCrypto.cpp

#include "RsaCrypto.h"
#include <openssl/bio.h>
#include <openssl/err.h>
#include <iostream>
#include <openssl/buffer.h>
#include <string.h>

RsaCrypto::RsaCrypto()
{
	m_publicKey = RSA_new();
	m_privateKey = RSA_new();
}

RsaCrypto::RsaCrypto(string fileName, bool isPrivate)
{
	m_publicKey = RSA_new();
	m_privateKey = RSA_new();
	if (isPrivate)
	{
		initPrivateKey(fileName);
	}
	else
	{
		initPublicKey(fileName);
	}
}


RsaCrypto::~RsaCrypto()
{
	RSA_free(m_publicKey);
	RSA_free(m_privateKey);
}

// 将公钥/私钥字符串数据解析到 RSA 对象中
void RsaCrypto::parseKeyString(string keystr, bool pubKey)
{
	// 字符串数据 -> BIO对象中
	BIO* bio = BIO_new_mem_buf(keystr.data(), keystr.size());
	// 公钥字符串
	if (pubKey)
	{
		PEM_read_bio_RSAPublicKey(bio, &m_publicKey, NULL, NULL);
	}
	else
	{
		// 私钥字符串
		PEM_read_bio_RSAPrivateKey(bio, &m_privateKey, NULL, NULL);
	}
	BIO_free(bio);
}

void RsaCrypto::generateRsakey(int bits, string pub, string pri)
{
	RSA* r = RSA_new();
	// 生成RSA密钥对
	// 创建bignum对象
	BIGNUM* e = BN_new();
	// 初始化bignum对象
	BN_set_word(e, 456787);
	RSA_generate_key_ex(r, bits, e, NULL);

	// 创建bio文件对象
	BIO* pubIO = BIO_new_file(pub.data(), "w");
	// 公钥以pem格式写入到文件中
	PEM_write_bio_RSAPublicKey(pubIO, r);
	// 缓存中的数据刷到文件中
	BIO_flush(pubIO);
	BIO_free(pubIO);

	// 创建bio对象
	BIO* priBio = BIO_new_file(pri.data(), "w");
	// 私钥以pem格式写入文件中
	PEM_write_bio_RSAPrivateKey(priBio, r, NULL, NULL, 0, NULL, NULL);
	BIO_flush(priBio);
	BIO_free(priBio);

	// 得到公钥和私钥
	m_privateKey = RSAPrivateKey_dup(r);
	m_publicKey = RSAPublicKey_dup(r);

	// 释放资源
	BN_free(e);
	RSA_free(r);
}

bool RsaCrypto::initPublicKey(string pubfile)
{
	// 通过BIO读文件
	BIO* pubBio = BIO_new_file(pubfile.data(), "r");
	// 将bio中的pem数据读出
	if (PEM_read_bio_RSAPublicKey(pubBio, &m_publicKey, NULL, NULL) == NULL)
	{
		ERR_print_errors_fp(stdout);
		return false;
	}
	BIO_free(pubBio);
	return true;
}

bool RsaCrypto::initPrivateKey(string prifile)
{
	// 通过bio读文件
	BIO* priBio = BIO_new_file(prifile.data(), "r");
	// 将bio中的pem数据读出
	if (PEM_read_bio_RSAPrivateKey(priBio, &m_privateKey, NULL, NULL) == NULL)
	{
		ERR_print_errors_fp(stdout);
		return false;
	}
	BIO_free(priBio);
	return true;
}


string RsaCrypto::rsaPubKeyEncrypt(string data)
{
	cout << "加密数据长度: " << data.size() << endl;
	// 计算公钥长度
	int keyLen = RSA_size(m_publicKey);
	cout << "pubKey len: " << keyLen << endl;
	// 申请内存空间
	char* encode = new char[keyLen + 1];
	// 使用公钥加密
	int ret = RSA_public_encrypt(data.size(), (const unsigned char*)data.data(),
		(unsigned char*)encode, m_publicKey, RSA_PKCS1_PADDING);
	string retStr = string();
	if (ret >= 0)
	{
		// 加密成功
		cout << "ret: " << ret << ", keyLen: " << keyLen << endl;
		retStr = toBase64(encode, ret);
	}
	else
	{
		ERR_print_errors_fp(stdout);
	}
	// 释放资源
	delete[]encode;
	return retStr;
}

string RsaCrypto::rsaPriKeyDecrypt(string encData)
{
	// text指向的内存需要释放
	char* text = fromBase64(encData);
	// 计算私钥长度
	//cout << "解密数据长度: " << text.size() << endl;
	int keyLen = RSA_size(m_privateKey);
	// 使用私钥解密
	char* decode = new char[keyLen + 1];
	// 数据加密完成之后, 密文长度 == 秘钥长度
	int ret = RSA_private_decrypt(keyLen, (const unsigned char*)text,
		(unsigned char*)decode, m_privateKey, RSA_PKCS1_PADDING);
	string retStr = string();
	if (ret >= 0)
	{
		retStr = string(decode, ret);
	}
	else
	{
		cout << "私钥解密失败..." << endl;
		ERR_print_errors_fp(stdout);
	}
	delete[]decode;
	delete[]text;
	return retStr;
}

string RsaCrypto::rsaSign(string data, SignLevel level)
{
	unsigned int len;
	char* signBuf = new char[1024];
	memset(signBuf, 0, 1024);
	int ret = RSA_sign(level, (const unsigned char*)data.data(), data.size(), (unsigned char*)signBuf,
		&len, m_privateKey);
	if (ret == -1)
	{
		ERR_print_errors_fp(stdout);
	}
	cout << "sign len: " << len << ", ret: " << ret << endl;
	string retStr = toBase64(signBuf, len);
	delete[]signBuf;
	return retStr;
}

bool RsaCrypto::rsaVerify(string data, string signData, SignLevel level)
{
	// 验证签名
	int keyLen = RSA_size(m_publicKey);
	char* sign = fromBase64(signData);
	int ret = RSA_verify(level, (const unsigned char*)data.data(), data.size(),
		(const unsigned char*)sign, keyLen, m_publicKey);
	delete[]sign;
	if (ret == -1)
	{
		ERR_print_errors_fp(stdout);
	}
	if (ret != 1)
	{
		return false;
	}
	return true;
}
string RsaCrypto::toBase64(const char* str, int len)
{
	BIO* mem = BIO_new(BIO_s_mem());
	BIO* bs64 = BIO_new(BIO_f_base64());
	// mem添加到bs64中
	bs64 = BIO_push(bs64, mem);
	// 写数据
	BIO_write(bs64, str, len);
	BIO_flush(bs64);
	// 得到内存对象指针
	BUF_MEM *memPtr;
	BIO_get_mem_ptr(bs64, &memPtr);
	string retStr = string(memPtr->data, memPtr->length - 1);
	BIO_free_all(bs64);
	return retStr;
}

char* RsaCrypto::fromBase64(string str)
{
	int length = str.size();
	BIO* bs64 = BIO_new(BIO_f_base64());
	BIO* mem = BIO_new_mem_buf(str.data(), length);
	BIO_push(bs64, mem);
	char* buffer = new char[length];
	memset(buffer, 0, length);
	BIO_read(bs64, buffer, length);
	BIO_free_all(bs64);

	return buffer;
}

要想解释清楚这个模块,还是要介绍一下里面一些比较陌生的api,如下:

#include <openssl/rsa.h>
// 申请一块内存, 存储了公钥和私钥
// 如果想得到RSA类型变量必须使用 RSA_new();
RSA *RSA_new(void);
void RSA_free(RSA *);

BIGNUM* BN_new(void);
void BN_free(BIGNUM*);
// 生成密钥对, 密钥对存储在内存中
int RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb);
	参数:
		- rsa: 通过RSA_new()获得
		- bits: 秘钥长度, 单位: bit, 常用的长度 1024*n (n正整数)
        - e: 比较大的数(5位以内)
            - 通过 BN_new 得到对应的变量
            - 初始化: BN_set_word(e, 12345);
		- cb: 回调函数, 用不到, 直接写NULL

// rsa公钥私钥类型是一样的: RSA类型
// 将参数rsa中的公钥提取出来
RSA *RSAPublicKey_dup(RSA *rsa);
	- rsa参数: 秘钥信息
	- 返回值: rsa公钥
// 将参数rsa中的私钥提取出来
RSA *RSAPrivateKey_dup(RSA *rsa);
	- rsa参数: 秘钥信息
	- 返回值: rsa私钥

// 创建bio对象
// 密钥对写磁盘文件的时候, 需要编码 -> base64
// 封装了fopen
BIO *BIO_new_file(const char *filename, const char *mode);
	参数:
		- filename: 文件名
		- mode: 文件打开方式和fopen打开方式的指定相同
		
int PEM_write_bio_RSAPublicKey(BIO* bp, const RSA* r);
int PEM_write_bio_RSAPrivateKey(BIO* bp, const RSA* r, const EVP_CIPHER* enc, 
	unsigned char* kstr, int klen, pem_password_cb *cb, void* u);
RSA* PEM_read_bio_RSAPublicKey(BIO* bp, RSA** r, pem_password_cb *cb, void* u);
RSA* PEM_read_bio_RSAPrivateKey(BIO* bp, RSA** r, pem_password_cb *cb, void* u);
	参数: 
		- bp: 通过BIO_new_file();函数得到该对象
		- r: 传递一个RSA* rsa指针的地址, 传出参数-> 公钥/私钥
		- cb: 回调函数, 用不到, 指定为NULL
		- u: 给回调传参, 用不到, 指定为NULL

//
//
RSA* PEM_read_RSAPublicKey(FILE* fp, RSA** r, pem_password_cb *cb, void* u);
RSA* PEM_read_RSAPrivateKey(FILE* fp, RSA** r, pem_password_cb *cb, void* u);

// 写入文件中的公钥私钥数据不是原始数据, 写入的编码之后的数据
// 是一种pem的文件格式, 数据使用base64进行编码
int PEM_write_RSAPublicKey(FILE* fp, const RSA* r);
int PEM_write_RSAPrivateKey(FILE* fp, const RSA* r, const EVP_CIPHER* enc, 
	unsigned char* kstr, int klen, pem_password_cb *cb, void* u);	
	参数:
		- fp: 需要打开一个磁盘文件, 并且指定写权限
		- r: 存储了密钥对
		 - 私钥独有的参数
		- enc: 指定的加密算法 -> 对称加密 -> NULL
		- kstr: 对称加密的秘钥 -> NULL
		- klen: 秘钥长度 -> 0
		- cb: 回调函数, 用不到, NULL
		- u: 给回调传参, 用不到, NULL

所以整个代码块看下来主要完成了这几个任务:生成密钥对、公钥加密、私钥解密、数据签名、验证签名。至于toBase64和fromBase64就是为了解决我们的加密后的密钥在传输过程中解决的一些问题,这都是后话了,所以整体来看,这段代码可以抽象至如下表示:

class MyRSA
{
public:
    MyRSA();
    ~MyRSA;
    // 生成密钥对
    // 公钥加密
    // 私钥解密
    // 数据签名
    // 验证签名
private:
    RSA* pubkey;
    RSA* pirKey;
}

四、关于hsah模块的说明

我们在这个项目中其实还封装了一个hash模块来调用,不过那是到最后封装外联接口支持网点之间直接用aes加解密的情况了,所以我们也是放到之后在做记录。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值