C++使用openssl建立证书,进行签名,验签,加密,解密(基于RSA)

话不多说,步骤详细,总结坑点

巨坑 RSA_verify()验证签名总是无法成功

官网的介绍

int RSA_verify(int type, const unsigned char *m, unsigned int m_len,
   unsigned char *sigbuf, unsigned int siglen, RSA *rsa);
/*RSA_verify() verifies that the signature sigbuf of size siglen matches a given message digest m of size m_len. type denotes the message digest algorithm that was used to generate the signature. rsa is the signer's public key.*/

意思就是type需要为NID_sha1,NID_md5,等等哈希类型,然后m是信息摘要(坑点),m_len是该摘要的长度,sigbuf是签名的内容,siglen是签名长度(坑点),rsa是读取的RSA私钥信息。

听起来很简单的样子,实际上对信息做签名很容易,但是私钥验证总是会报错,各种各样都有。下面总结坑点:

1:以前以为签名和验证签名就只要把message塞进签名函数,然后和签名后的信息放进验证函数里就行了,后来发现不是这样的,RSA的签名是对信息摘要(hash)来做的签名,以为着我们需要先把原先的信息做哈希,才能签名。同理,验签的时候也需要该哈希,和签名后的数据。(PS:其实真正的通信在签名过后还需要进行base64编码处理,对应接收时也需要base64解码,因为签名后的数据是二进制的,无法正常阅读)

2:明明做了哈希,却一直验签不成功?
官网告诉了我们一个很好用的函数接口,会给出具体的错误提示:
unsigned long ulErr = ERR_get_error();
这个函数会给出错误代码,配合其余的代码会告诉我们哪里出错,比如他告诉了我,我的签名数据的长度错误,siglen error,因为这里的签名是二进制,不能通过使用strlen()等方式获取,所以我在签名后打印了签名的长度(自动绑定在outlen中),才发现长度是128.(不管我做的sha1还是sha256或者md5)

RSA_sign(NID_sha1, (const unsigned char*)strData.c_str(), strData.length() , (unsigned char*)pEncode, &outlen, pRSAPriKey);
//签名数据存储在pEncode,对应长度存储在outlen

3:为什么???我做了哈希,然后按照官网的参数说明,总是报错:错误的私钥(公钥)
rsa routines:RSA_padding_check_PKCS1_type_1:invalid padding
可以明确的是,我的公私钥没有问题,因此开始定位,也没啥好说的,问题肯定在哈希函数里,所以我试了网上的很多办法,SHA256,SHA1,MD5等等,都无法成功验证,最后回归官网rsa_verify
使用了官方给出的SHA1头文件中的函数进行哈希,才成功的

生成1024位rsa私钥,保存为pem格式:

openssl genpkey -out prikey.pem -algorithm rsa
生成对应的公钥:

openssl pkey -in prikey.pem -pubout -out pubkey.pem


#include <iostream>
#include "Socket.hpp"
#include <functional>
#include <bits/stdc++.h>
#include <openssl/sha.h>

#include <openssl/rsa.h>
#include <openssl/err.h>
#include <openssl/pem.h>
 
#include <iostream>
#include <string>
#include <cstring>
#include <cassert>
using namespace std;



//加密
std::string EncodeRSAKeyFile( const std::string& strPemFileName, const std::string& strData )
{
	if (strPemFileName.empty() || strData.empty())
	{
		assert(false);
		return "";
	}
	FILE* hPubKeyFile = fopen(strPemFileName.c_str(), "rb");
	if( hPubKeyFile == NULL )
	{
		assert(false);
		return ""; 
	}
	std::string strRet;
	RSA* pRSAPublicKey = RSA_new();
	if(PEM_read_RSA_PUBKEY(hPubKeyFile, &pRSAPublicKey, 0, 0) == NULL)
	{
		assert(false);
		return "";
	}
 
	int nLen = RSA_size(pRSAPublicKey);
	char* pEncode = new char[nLen + 1];
	int ret = RSA_public_encrypt(strData.length(), (const unsigned char*)strData.c_str(), (unsigned char*)pEncode, pRSAPublicKey, RSA_PKCS1_PADDING);
	if (ret >= 0)
	{
		strRet = std::string(pEncode, ret);
	}
	delete[] pEncode;
	RSA_free(pRSAPublicKey);
	fclose(hPubKeyFile);
	CRYPTO_cleanup_all_ex_data(); 
	return strRet;
}

//签名 use private key
std::string SignRSAKeyFile( const std::string& strPemFileName, std::string& strData )
{
        if (strPemFileName.empty() || strData.empty())
        {
                assert(false);
                return "";
        }
        FILE* hPriKeyFile = fopen(strPemFileName.c_str(), "rb");
        if( hPriKeyFile == NULL )
        {
                assert(false);
                return "";
        }
        std::string strRet;
        RSA* pRSAPriKey = RSA_new();
        if(PEM_read_RSAPrivateKey(hPriKeyFile, &pRSAPriKey, 0, 0) == NULL)
        {
                assert(false);
                return "";
        }

        int nLen = RSA_size(pRSAPriKey);
        char* pEncode = new char[nLen + 1];
	unsigned int outlen;
        int ret = RSA_sign(NID_sha1, (const unsigned char*)strData.c_str(), strData.length() , (unsigned char*)pEncode, &outlen, pRSAPriKey);
        if (ret >= 0)
        {
                strRet = std::string(pEncode);
		std::cout << "\n" << strRet << endl;
		//std::cout << "next \n" << pEncode << endl;
		std::cout << "critical length:\n" << outlen << endl;
        }
	if( ret != 1)
		std::cout << "sign failed\n";
        delete[] pEncode;
        RSA_free(pRSAPriKey);
        fclose(hPriKeyFile);
        CRYPTO_cleanup_all_ex_data();
        return strRet;
}
 
//解密
std::string DecodeRSAKeyFile( const std::string& strPemFileName, const std::string& strData )
{
	if (strPemFileName.empty() || strData.empty())
	{
		assert(false);
		return "";
	}
	FILE* hPriKeyFile = fopen(strPemFileName.c_str(),"rb");
	if( hPriKeyFile == NULL )
	{
		assert(false);
		return "";
	}
	std::string strRet;
	RSA* pRSAPriKey = RSA_new();
	if(PEM_read_RSAPrivateKey(hPriKeyFile, &pRSAPriKey, 0, 0) == NULL)
	{
		assert(false);
		return "";
	}
	int nLen = RSA_size(pRSAPriKey);
	char* pDecode = new char[nLen+1];
 
	int ret = RSA_private_decrypt(strData.length(), (const unsigned char*)strData.c_str(), (unsigned char*)pDecode, pRSAPriKey, RSA_PKCS1_PADDING);
	if(ret >= 0)
	{
		strRet = std::string((char*)pDecode, ret);
	}
	delete [] pDecode;
	RSA_free(pRSAPriKey);
	fclose(hPriKeyFile);
	CRYPTO_cleanup_all_ex_data(); 
	return strRet;
}

//验证签名 use pubkey
int VerifyRSAKeyFile( const std::string& strPemFileName, const std::string& strData , const std::string& sign_data)
{
        if (strPemFileName.empty() || strData.empty())
        {
                assert(false);
                return 0;
        }
        FILE* hPubKeyFile = fopen(strPemFileName.c_str(), "rb");
        if( hPubKeyFile == NULL )
        {
                assert(false);
                return 0;
        }
        std::string strRet;
        RSA* pRSAPublicKey = RSA_new();
        if(PEM_read_RSA_PUBKEY(hPubKeyFile, &pRSAPublicKey, 0, 0) == NULL)
        {
                assert(false);
                return 0;
        }

        int nLen = RSA_size(pRSAPublicKey);
        char* pEncode = new char[nLen + 1];
	unsigned int outlen;
        int ret = RSA_verify(NID_sha1, (const unsigned char*)strData.c_str(), strlen(strData.c_str()),  (const unsigned char*)sign_data.c_str(), 128,  pRSAPublicKey);
	if(ret != 1){
		std::cout << "verify error\n";
		unsigned long ulErr = ERR_get_error();
		char szErrMsg[1024] = {0};  
		cout << "error number:" << ulErr << endl; 
        char *pTmp = NULL;  
        pTmp = ERR_error_string(ulErr,szErrMsg); // 格式:error:errId:库:函数:原因  
        cout << szErrMsg << endl;
		return -1;
	}
	else
		std::cout << "verify success\n";
        delete[] pEncode;
        RSA_free(pRSAPublicKey);
        fclose(hPubKeyFile);
        CRYPTO_cleanup_all_ex_data();
        return 1;
}

int main(void)
{
    try
    {
        //Socket::UDP sock;
        
        //sock.bind(3000);
	string m = "test";
	//hash SHA1
	unsigned char digest[SHA_DIGEST_LENGTH];
 
	SHA_CTX ctx;
	SHA1_Init(&ctx);
	SHA1_Update(&ctx, m.c_str(), strlen(m.c_str()));
	SHA1_Final(digest, &ctx);
 
	char mdString[SHA_DIGEST_LENGTH*2+1];
	for (int i = 0; i < SHA_DIGEST_LENGTH; i++)
	sprintf(&mdString[i*2], "%02x", (unsigned int)digest[i]);
	cout << "digest:" << digest << endl;
	std::cout << "SHA1 digest translate:" << mdString << endl;


	string digest_s((char*)digest);
	string mdString1(mdString);
	auto sign_ = SignRSAKeyFile("prikey.pem",mdString1);
	std::cout << "data:" << sign_ << std::endl;
	std::cout << "verify:" << VerifyRSAKeyFile("pubkey.pem",(const string)mdString,sign_);

        
        //Socket::Datagram received = sock.receive();
        
        //cout << received.data << endl;
        
	//sock.send("127.0.0.1", 2000, "request1");

        //Socket::Datagram received1 = sock.receive();

        //cout << received1.data << endl;

        //sock.close();
    }
    catch (Socket::Exception &e)
    {
        cout << e.what() << endl;
    }
    
    return 0;
}
OpenSSL是一个开源的加密库,它提供了RSA加密解密签名验签的功能。 对于RSA加密解密,我们可以使用OpenSSL提供的命令行工具或者API来实现使用命令行工具,我们可以通过以下命令进行RSA加密openssl rsautl -encrypt -in <input file> -out <output file> -inkey <public key file> -pubin 其中,<input file>是要加密的文件,<output file>是加密后的文件,<public key file>是存储公钥的文件,-pubin参数表示输入的是公钥。 使用命令行工具,我们可以通过以下命令进行RSA解密openssl rsautl -decrypt -in <input file> -out <output file> -inkey <private key file> 其中,<input file>是要解密的文件,<output file>是解密后的文件,<private key file>是存储私钥的文件。 对于RSA签名验签,我们可以使用以下命令进行签名openssl rsautl -sign -in <input file> -out <output file> -inkey <private key file> 其中,<input file>是要签名的文件,<output file>是签名后的文件,<private key file>是存储私钥的文件。 使用以下命令进行验签openssl rsautl -verify -in <input file> -out <output file> -inkey <public key file> -pubin 其中,<input file>是要验签的文件,<output file>是验签后的文件,<public key file>是存储公钥的文件,-pubin参数表示输入的是公钥。 使用OpenSSL的API进行RSA加密解密签名验签的操作也是类似的,我们可以通过调用相应的函数来实现。需要注意的是,API的使用需要在代码中显式引入OpenSSL的头文件和OpenSSL的库文件。 总之,OpenSSL提供了便捷的工具和API来实现RSA加密解密签名验签的功能,无论是命令行工具还是API,都可以选择合适的方式进行操作。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值