RSA加密解密中的长度处理
前言
一、RSA的基本原理
1.非对称秘钥的概念
首先介绍传统的对称秘钥,所谓对称秘钥就是加密和解密的秘钥是一样的(给锁锁上和解锁的钥匙是同一把),优点是简单快速,但是安全性一般比较低,普通的DES加密已经不够安全,现在使用的大多是3DES和AES。
与对称秘钥相对的概念就是非对称秘钥(即加密和解密的秘钥不是同一把)。针对非对称秘钥的经典设计RSA算法,可以进行下面的形象解释:
假设有一把非常神奇的锁,它有两种钥匙,一种叫私钥(只有一把),一种叫公钥(可以有很多把),用私钥上锁后只能用公钥打开,用公钥上锁后只能用私钥打开。由此可以衍生出很多有趣的特性:
1.你可以公开你的公钥,(在对称秘钥中如果秘钥被第三者得知,信息就能被第三者解密),即使发送消息的人和第三者有相同的秘钥,第三者也不能解密信息。
2.用私钥加密的信息只能用公钥解密,由此可以衍生出一种特殊的作用–身份验证。在公布公钥时你需要将公钥广布于众,如果有人接收到了一个加密信息而且能用你的公钥解密,那么就能确定是你发送的信息而不是其他人伪造的(容易遭到重放攻击,因此还需要对签名进行其他保护,如使用消息的hash结果进行私钥签名)
3.私钥不能用来加密发送的信息本身(除非你想让所有有公钥的人都能解密信息)而用来进行签名。
4.由于RSA的加密解密算法比较复杂,对资源的消耗比较大,因此一般使用于加密短信息或者少数几次的长信息,如登录时使用RSA加密传送AES秘钥,之后的通信就使用AES算法加解密信息。
2.非对称秘钥的结构和算法
结构 | 内容 |
---|---|
公钥 | (D,N) |
私钥 | (E,N) |
秘钥对 | (E.D.N) |
加密算法
密
文
=
明
文
E
m
o
d
N
密文=明文^{E}modN
密文=明文EmodN
解密算法
明
文
=
密
文
D
m
o
d
N
明文=密文^{D}modN
明文=密文DmodN
二、RSA的使用限制
1.长度限制
正是由于RSA算法中的取模运算,因此RSA对加密信息和解密信息有长度限制。(比如5mod3=2mod3)
2.特殊字符的限制
不同的设备对特殊字符有可能会有不同的处理方案,比如有些设备不支持unicode编码,有些设备会把特殊字符作为结束符等,而明文和密文中有可能存在特殊字符,如果不加处理,容易造成很多非预期的错误。
3.针对长度的解决方案
加密长明文时必须一段一段的加密,组合后发送,然后在解密时同样一段一段的解密再组合成明文。
4.针对特殊字符的解决方案
使用base64算法将输入转化成只包含可见ASCII字符的字符串序列,在传输中使用base64编码后的字符串,处理时再进行解码。
三、代码示例
//加密函数
bool MySsl::EncryptionPubclic(const char *plaintext, size_t size_in, unsigned char **crypttext, size_t &size_out, std::string key)
{
RSA* rsa=RSA_new();
BIO *keybio = BIO_new_mem_buf((unsigned char *)key.c_str(), -1);
const std::string publickey1_flag="-----BEGIN RSA PUBLIC KEY-----";
const std::string publickey8_flag="-----BEGIN PUBLIC KEY-----";
if(std::strncmp(key.c_str(),publickey1_flag.c_str(),publickey1_flag.length())==0){
rsa=PEM_read_bio_RSAPublicKey(keybio,NULL,NULL,NULL);
}else if(std::strncmp(key.c_str(),publickey8_flag.c_str(),publickey8_flag.length())==0){
rsa=PEM_read_bio_RSA_PUBKEY(keybio,NULL,NULL,NULL);
}else{
return false;
}
int rsa_len = RSA_size(rsa);
int slice_max=rsa_len-11;//单次处理的长度,11是一个预留的空间,如果过小则安全性降低
int nums=size_in/slice_max;
if(size_in%slice_max!=0){
nums+=1;
}//计算总共需要进行的分段加密次数
int all_len=rsa_len*nums;//生成的密文长度
*crypttext = (unsigned char *)malloc(all_len);
memset(*crypttext, 0, rsa_len);
for(int i=0;i<nums-1;i++){
if (RSA_public_encrypt(slice_max, (unsigned char *)plaintext+i*slice_max,*crypttext+rsa_len*i, rsa, RSA_PKCS1_PADDING) < 0){
return false;
}
}
if(size_in!=0){
int end_len=size_in%slice_max;
if(end_len==0) end_len=slice_max;
if (RSA_public_encrypt(end_len, (unsigned char *)plaintext+(nums-1)*slice_max,*crypttext+rsa_len*(nums-1), rsa, RSA_PKCS1_PADDING) < 0){
return false;
}
}
size_out=all_len;
BIO_free(keybio);
RSA_free(rsa);
return true;
}
//解密函数
bool MySsl::DecryptPrivate(const char *crypttext,size_t size_in,unsigned char **plaintext,size_t size_out,std::string key){
RSA* rsa=RSA_new();
BIO *keybio = BIO_new_mem_buf((unsigned char *)key.c_str(), -1);
const std::string privatekey_flag="-----BEGIN RSA PRIVATE KEY-----";
if(std::strncmp(key.c_str(),privatekey_flag.c_str(),privatekey_flag.length())==0){
rsa=PEM_read_bio_RSAPrivateKey(keybio,NULL,NULL,NULL);
}else{
return false;
}
int rsa_len = RSA_size(rsa);
int slice_max=rsa_len-11;
int nums=size_in/rsa_len;
*plaintext = (unsigned char *)malloc(size_out);
std::memset(*plaintext, 0, size_out);
for(int i=0;i<nums;i++){
if (RSA_private_decrypt(rsa_len, (u_char *)crypttext+i*rsa_len, *plaintext+slice_max*i,rsa, RSA_PKCS1_PADDING) < 0){
// return false;
}
}
RSA_free(rsa);
return true;
}
四、开源代码指南
Crypto-Example
该项目中包含了AES,RSA的秘钥生成、秘钥保存和加解密以及base64编码,并且附带了测试代码,非常值得学习。
我的项目
其中的AES以及base64部分代码参考了上面的项目实现,RSA部分代码则为参考部分教学代码后改进(分段加解密部分),相比上面的项目能够直接处理没有进行base64编码的明文和密文(但是在传输过程中仍然需要进行base64编码,这里只是能够处理包含特殊字符的输入,例如需要直接加密数据结构)。该项目在持续改进中,欢迎关注。