string publicKeyString =
“MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy5yWJbLtLQTWiBCnLUdbPp1T8UiPvt+8IqIhn3Ny8GZs3539tgiHAERDWSy2WDz2iZCx5i5MPiKphDZNWovL+l0XJuDtfhPqg5pFzvX6z0DFpKMv+VeKc6HNC9gKDHvDVv4h+jUqHyKuI7YsfiWJ8YocQ1WHczZSWQI4NRxpTmhQB3AJv7GYaMTbvaj/eK5h6gjXd/0YcaskxxxS4TkRqnqKJCaxqjNQdb1ftLQCMlpBkEB35RIrr3vI89Sn+dyKq+3mb6VdqtHe9kqCsnkRhVTzHSghtJ2WdK+i2uFx67fBo4m4CmsQDaa5eJgfNr0iC5s3Eal9i2qmwm7I7F+FYQIDAQAB”
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(strPublicKey.c_str());
if ((bio = BIO_new_mem_buf(chPublicKey, -1)) == NULL) //从字符串读取RSA公钥
{
cout<<"BIO_new_mem_buf failed!"< 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) 这个类库,更加便捷。