【openssl】使用openssl库进行base64加解密,aes解密,rsa验证签名

一、前言

    近期在处理http请求的时候接触到了有关加密解密的部分,因为之前几乎没碰过这方面,遇到了很多坑,所以记录一下解决过程,用到的加密解密函数都是来自openssl库。openssl库包含主要的密码算法、常用的密钥和证书封装管理功能以及SSL协议。

二、base64加密解密

2.1 base64编码

    Base64编码是从二进制到字符的过程,可用于在HTTP环境下传递较长的标识信息。采用Base64编码具有不可读性,需要解码后才能阅读,所以base64加密实际上就是对传输的字符进行了base64编码,让它变得不可读。

2.2 加密解密代码

//加密
char * Base64Encode(const char * input, int length, bool with_new_line)
{
    if(!input || !length)
        return NULL;
	BIO * bmem = NULL;
	BIO * b64 = NULL;
	BUF_MEM * bptr = NULL;
	b64 = BIO_new(BIO_f_base64());
	if(!with_new_line) {
		BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
	}
	bmem = BIO_new(BIO_s_mem());
	b64 = BIO_push(b64, bmem);
	BIO_write(b64, input, length);
	BIO_flush(b64);
	BIO_get_mem_ptr(b64, &bptr);
 
	char * buff = (char *)malloc(bptr->length + 1);
	memcpy(buff, bptr->data, bptr->length);
	buff[bptr->length] = '\0';
	BIO_free_all(b64);
 
	return buff;
}

//解码
char* Base64Decode(const char* input, int length, bool with_new_line)
{
    if(!input || !length)
        return NULL;
	BIO * b64 = NULL;
	BIO * bmem = NULL;
	char * buffer = (char *)malloc(length);
	memset(buffer, 0, length);
 
	b64 = BIO_new(BIO_f_base64());
	if(!with_new_line) {
		BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
	}
	bmem = BIO_new_mem_buf(input, length);
	bmem = BIO_push(b64, bmem);
	unsigned int size = BIO_read(bmem, buffer, length);
    buffer[size] = '\0';
	BIO_free_all(bmem);
	//调试时建议这里加log,看看你的输入输出分别是什么 
	return buffer;
}

2.3 需要注意的点

    with_new_line,是编码或者解码是否在同一行上,所以,如果加密的时候设置了他,那么解密的时候也要设置,否则会导致base64解密结果不正确或者为空。

三、AES解密

    AES算法的数据分组长度为128比特、密钥长度为128/192/256比特。加密解密函数搜索出来一大堆,我只用到了解密,所以就只说解密过程中遇到的问题。用到的两个函数:

//设置解密密钥
int AES_set_decrypt_key(const unsigned char *userKey, const int bits,  AES_KEY *key);

userKey: 密钥数值;
bits:密钥长度,以bit为单位,如果密钥数字是16个字节,则此参数值应为128;
key: AES_KEY对象指针;
所以,一定要清楚自己的密钥长度是多少,根据自己密钥长度传入第二个参数,否则肯定是不对的。

//使用AES算法加密或者解密数据
void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,   size_t length, const AES_KEY *key,    unsigned char *ivec, const int enc);

in: 需要加密/解密的数据;
out: 计算后输出的数据;
length: 数据长度(这里不包含初始向量数据长度)
key:密钥
ivec: 初始向量(一般为16字节全0)
enc: AES_ENCRYPT 代表加密, AES_DECRYPT代表解密;
解密代码:

AesDecode(const unsigned char* encrypt_data, unsigned chra* decrypt_data, const size_t len, const unsigned char* key, unsigned char* ivec)
{
    if(!encrypt || !key)
        return -1;
    AES_KEY aes_key;
    memset(&aes_key, 0x00, sizeof(AES_KEY));
    //我的密钥是16的字节,所以这里传入128
    if(AES_set_decrypt_key(key, 128, &aes_key)  < 0);
    {
        //建议打log
        return -1;
    }
    AES_cbc_encrypt(encrypt_data, decrypt_data, len, &aes_key, ivec, AES_DECRYPT);
    return 0;
}

四、RSA签名验证

4.1 签名验证代码

    签名验证的原理其实是这样的:对要验证的文本选取一个摘要算法提取摘要,再对摘要提取出来的摘要进行加密,这个就是代码中的sign,待验证字符串是加密前的文本,那么使用同样的摘要算法提取出来的摘要和sign解密后的结果如果是一致的,那说明传输过来的数据是没有问题的,验证通过。

//得到公钥的RSA结构体
RSA* GetPublicKeyRSA(std::string str_pubkey)
{
    int size = str_pubkey.size();
    for(int i = 64; i < size; ++i)
    {
        if(str_pubkey[i] != '\n')
        {
            str_pubkey.insert(i, "\n")
        }
        i++;
    }
    strPublicKey.insert(0, "-----BEGIN PUBLIC KEY-----\n");
	strPublicKey.append("\n-----END PUBLIC KEY-----\n");
 
	BIO *bio = NULL; 
	RSA *rsa = NULL; 
	char *chPublicKey = const_cast<char *>(str_pubkey.c_str());
	if ((bio = BIO_new_mem_buf(chPublicKey, -1)) == NULL)       //从字符串读取RSA公钥
	{     
		cout<< "error,chPublicKey:"<< chPublicKey << endl;
	}       
	rsa = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);   //从bio结构中得到rsa结构
	if (NULL == rsa)
	{
		BIO_free_all(bio);
		unsigned long ulErr = ERR_get_error(); // 获取错误号
		char szErrMsg[1024] = {0};
		char *pTmp = NULL;
		pTmp = ERR_error_string(ulErr,szErrMsg); // 格式:error:errId:库:函数:原因
		cout << szErrMsg << endl;
	}
	return rsa;
}
/* sign : 签名字符串
* content : 待验证的字符串
* publickey : 公钥
*/
bool Rsaverify(std::string publickey, const char* sign, const char* content )
{
    if(!sign || ! content || !publickey.size())
        return false;
 	// 得到公钥的RSA结构体
	RSA* rsa = GetPublicKeyRSA(publicKey);
	if (NULL == rsa)
 		return false;
    //将sign进行base64解密
    char* decodesign = Base64Decode(sign, strlen(sign), false);
    if(!decodesign)
    {
        cout << "error decode sign ,sign:" << sign;
        return false;
    }
	// 将待验证字符串经过sha1摘要(摘要算法根据实际使用来,要看sign的由来是使用的什么摘要,此处以sha1为例)
	unsigned char sha1[20];
	SHA1((const unsigned char*)content,strlen(content),sha1); 
	if(1 == RSA_verify(NID_sha1, sha1, 20, (unsigned char*)decodesign, strlen(decodesign), rsa));
	{
	    free(decodesign);
	    return true;
	}  
	free(decodesign);
    return false;
}

4.2 遇到的问题

获取公钥的RAS结构体出错,结果为空,获取错误如下:
error:0906D06C:PEM routines:PEM_read_bio:no start line,ptmp=error:0906D06C:PEMroutines:PEM_read_bio:no start line
因为openssl默认使用PEM的格式,PEM对于密钥的格式要求是很严格的,必须是以-----BEGIN PUBLIC KEY-----开头,-----END PUBLIC KEY-----结尾的,或者是-----BEGIN RSA PUBLIC KEY-----开头,-----END RSA PUBLIC KEY-----结尾,两种不一样的格式开头或者结尾,对应使用的函数也有差别,-----BEGIN PUBLIC KEY-----对应PEM_read_bio_RSA_PUBKEY(),-----BEGIN RSA PUBLIC KEY-----对应PEM_read_bio_RSAPublicKey(),并且,密钥本身的结构必须是64的字符一行的,直接把密钥格式处理好放在xml读取结果还是有问题,所以我的处理方法是,xml里不带任何格式,就是一行,读取之后代码处理换行和加开头的结尾,即上面代码中封装的GetPublicKeyRSA函数解决了这个问题。

五、总结

    在做不熟悉的东西时应该在所有提前返回的地方打上错误日志,将输入输出和用到的所有数据都输出出来有助于在出现问题的时候分析问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值