openssl——从内存中读取RSA公钥并加密 以及 "PRNG not seeded" error message 的解决办法

背景:近期需要在MirrorLink项目中进行RSA会话密钥加密。网上大部分的代码都是从pem文件中读取公钥,再利用公钥加密会话密钥,但是MirrorLink是从手机中获取公钥,不是以文件的格式传递过来,于是就需要从内存中读取,查找了半天,才找到了一篇文章( openssl从内存中读取RSA公钥)。
从内存中读取的公钥为以下:

string publicKeyString =
“MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy5yWJbLtLQTWiBCnLUdbPp1T8UiPvt+8IqIhn3Ny8GZs3539tgiHAERDWSy2WDz2iZCx5i5MPiKphDZNWovL+l0XJuDtfhPqg5pFzvX6z0DFpKMv+VeKc6HNC9gKDHvDVv4h+jUqHyKuI7YsfiWJ8YocQ1WHczZSWQI4NRxpTmhQB3AJv7GYaMTbvaj/eK5h6gjXd/0YcaskxxxS4TkRqnqKJCaxqjNQdb1ftLQCMlpBkEB35RIrr3vI89Sn+dyKq+3mb6VdqtHe9kqCsnkRhVTzHSghtJ2WdK+i2uFx67fBo4m4CmsQDaa5eJgfNr0iC5s3Eal9i2qmwm7I7F+FYQIDAQAB”

该公钥的格式是DER + Base64编码,要从内存中读取该公钥,必须注意以下几点:

1、公钥字符串开头要加上“-----BEGIN PUBLIC KEY-----\n”,结尾加上“\n-----END PUBLIC KEY-----\n”。

否则会出现 error:0906D06C:PEM routines:PEM_read_bio:no start line 的错误。

2、公钥字符串每隔64个字符要加一个换行,否则会报秘钥格式错误。

c++代码实现举例:

int nPublicKeyLen = strPublicKey.size();      //strPublicKey为base64编码的公钥字符串
for(int i = 64; i < nPublicKeyLen; i+=64)
{
    if(strPublicKey[i] != '\n')
    {
        strPublicKey.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 *>(strPublicKey.c_str());
if ((bio = BIO_new_mem_buf(chPublicKey, -1)) == NULL)       //从字符串读取RSA公钥
{     
        cout<<"BIO_new_mem_buf failed!"<<endl;      
}       
rsa = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);   //从bio结构中得到rsa结构
if (!rsa)
{
        ERR_load_crypto_strings();
        char errBuf[512];
        ERR_error_string_n(ERR_get_error(), errBuf, sizeof(errBuf));
        cout<< "load public key failed["<<errBuf<<"]"<<endl;
	BIO_free_all(bio);
}

变换后的公钥格式如下:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy5yWJbLtLQTWiBCnLUdb
Pp1T8UiPvt+8IqIhn3Ny8GZs3539tgiHAERDWSy2WDz2iZCx5i5MPiKphDZNWovL
+l0XJuDtfhPqg5pFzvX6z0DFpKMv+VeKc6HNC9gKDHvDVv4h+jUqHyKuI7YsfiWJ
8YocQ1WHczZSWQI4NRxpTmhQB3AJv7GYaMTbvaj/eK5h6gjXd/0YcaskxxxS4TkR
qnqKJCaxqjNQdb1ftLQCMlpBkEB35RIrr3vI89Sn+dyKq+3mb6VdqtHe9kqCsnkR
hVTzHSghtJ2WdK+i2uFx67fBo4m4CmsQDaa5eJgfNr0iC5s3Eal9i2qmwm7I7F+F
YQIDAQAB
-----END PUBLIC KEY-----

于是成功从内存中读取了RSA的公钥,此时,仍需要利用公钥对会话密钥进行加密,加密的C++代码如下:

int rsa_len;
rsa_len=RSA_size(rsa);			//获取RSA的长度

p_en=(unsigned char *)malloc(rsa_len);
memset(p_en,0,rsa_len);

int  rsaENLen = 0;
ERR_clear_error();
/* 如果选择RSA_PKCS1_PADDING,strlen(str)的长度不能够超过RSA(rsa)-11 */
/* 如果选择RSA_PKCS1_OAEP_PADDING,strlen(str)的长度不能够超过RSA(rsa)-41 */
/* 此处选择RSA_PKCS1_PADDING,是由于项目需求 */
if((rsaENLen = RSA_public_encrypt(strlen(str),(unsigned char *)str,(unsigned char*)p_en,rsa,RSA_PKCS1_PADDING))<0)
{
	ERR_load_crypto_strings();
	char errBuf[512];
	ERR_error_string_n(ERR_get_error(), errBuf, sizeof(errBuf));
	printf("RSA_public_encrypt failed, reason: ");
	qDebug() << errBuf;
	free(p_en);
	RSA_free(rsa);
	BIO_free_all(bio);

	return NULL;
}			

//free(p_en);
RSA_free(rsa);
BIO_free_all(bio);

这样,我们就得到了加密后的会话密钥p_en。但是,编程时会发现加密不成功,总是提示这样的错误:

RSA_public_encrypt failed, reason:error:24064064:random number generator:SSLEAY_RAND_BYTES:PRNG not seeded。

在官网(http://www.openssl.org/support/faq.html#USER1)查询到“1. Why do I get a "PRNG not seeded" error message?”,看了半天还是看不懂。然后又以为RSA结构体加载错误,经过一系列的怀疑,下载了openssl 源码查看(了解源码可以先查看“openssl源代码结构),找到了“rsatest.cpp”这个源文件,里面有具体的实现代码,其中就有一句:

static const char rnd_seed[] = "string to make the random number generator think it has entropy";
RAND_seed(rnd_seed, sizeof rnd_seed);		/* or RSA_PKCS1_PADDING/OAEP may fail */
原来这里还需要openssl的什么伪随机数,猜想加密的时候还需要伪随机数的支持吧。于是添加以上代码,编译并放到车机运行,竟然完美通过了。

以下是全部的代码:

/* RSA PKCS#1 v1.5 algorithm */
QString global_strPublicKey;			//Store the VNC public key from the DAP
static const char rnd_seed[] = "string to make the random number generator think it has entropy";
unsigned char *RSA_Encrypt(char *str)		//char str[] = "1234567812345678";
{
	if(global_strPublicKey == NULL)
	{
		return NULL;
	}
	QString strPublicKey = global_strPublicKey;

	RAND_seed(rnd_seed, sizeof rnd_seed);		/* or RSA_PKCS1_PADDING/OAEP may fail */

	unsigned char *p_en;
	int nPublicKeyLen = strPublicKey.size();      //strPublicKey为base64编码的公钥字符串
	for(int i = 64; i < nPublicKeyLen; i+=64)
	{
		if(strPublicKey[i] != '\n')
		{
			strPublicKey.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 ;
	QByteArray bytePublicKey = strPublicKey.toLatin1();
	chPublicKey = bytePublicKey.data();
	if ((bio = BIO_new_mem_buf(chPublicKey, -1)) == NULL)       //从字符串读取RSA公钥
	{    
		printf("BIO_new_mem_buf failed!\n");   
		return NULL;
	}       
	rsa = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);   //从bio结构中得到rsa结构	Get RSA struct from bio
	if (!rsa)
	{
			ERR_load_crypto_strings();
			char errBuf[512];
			ERR_error_string_n(ERR_get_error(), errBuf, sizeof(errBuf));
			printf("load public key failed, reason: ");
			qDebug() << errBuf;
			BIO_free_all(bio);
			return NULL;
	}
	else 
	{
		int rsa_len;
		rsa_len=RSA_size(rsa);			//获取RSA的长度

		p_en=(unsigned char *)malloc(rsa_len);
		memset(p_en,0,rsa_len);

		int  rsaENLen = 0;
		ERR_clear_error();
		/* 如果选择RSA_PKCS1_PADDING,strlen(str)的长度不能够超过RSA(rsa)-11 */
		/* 如果选择RSA_PKCS1_OAEP_PADDING,strlen(str)的长度不能够超过RSA(rsa)-41 */
		/* 此处选择RSA_PKCS1_PADDING,是由于项目需求 */
		if((rsaENLen = RSA_public_encrypt(strlen(str),(unsigned char *)str,(unsigned char*)p_en,rsa,RSA_PKCS1_PADDING))<0)
		{
			ERR_load_crypto_strings();
			char errBuf[512];
			ERR_error_string_n(ERR_get_error(), errBuf, sizeof(errBuf));
			printf("RSA_public_encrypt failed, reason: ");
			qDebug() << errBuf;
			free(p_en);
			RSA_free(rsa);
			BIO_free_all(bio);

			return NULL;
		}
		sessionKeyEncryptedLen = rsaENLen;			
		
		//free(p_en);
		RSA_free(rsa);
		BIO_free_all(bio);
		return p_en;
	}
}

本人对openssl了解得不深,只在项目开发中研究了一下,所以如果有什么不对之处,欢迎大家指出,以供大家学习。


补充:此程序是在openssl的类库上操作的,如果使用了Qt的IDE,可以参考 QCA(Qt Cryptographic Architecture) 这个类库,更加便捷。



  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值