最近需要对一些数据加密后进行HTTP传输,由于希望对方只能收到数据后解密,而无法知道加密方法以防止伪造,所以选择了一个通过BigInteger类,使用私钥加密,公钥解密的算法。

 

算法是网上找来的,链接如下:

 

 一开始使用得挺好,加密解密都正常,但当加密的数据超过了128byte,解密后偶尔会出现乱码,解密失败。

 

通过跟踪发现,这是算法的一个bug,是由于对BigInteger类不当使用产生的。 具体分析如下:

 

先看加密方法:


private string EncryptString(string source, BigInteger d, BigInteger n)
        {
            
int len = source.Length;
            
int len1 = 0;
            
int blockLen = 0;
            
if ((len % 128== 0)
                len1 
= len / 128;
            
else
                len1 
= len / 128 + 1;
            
string block = "";
            
string temp = "";
            
for (int i = 0; i < len1; i++)
            {
                
if (len >= 128)
                    blockLen 
= 128;
                
else
                    blockLen 
= len;
                block 
= source.Substring(i * 128, blockLen);
                
byte[] oText = System.Text.Encoding.Default.GetBytes(block);
                BigInteger biText 
= new BigInteger(oText);
                BigInteger biEnText 
= biText.modPow(d, n);
                
string temp1 = biEnText.ToHexString();
                temp 
+= temp1;
                len 
-= blockLen;
            }
            
return temp;
        }

 

由于RSA算法单次加密只能支持128byte的数据,如果数据长度超过128byte,就会被分割为几段进行加密,最后把加密结果转换为16进制字符串,并连接起来输出结果。

一般情况下,128byte的数据,加密后输出的hex字符串应该是256byte,所以对应的解密方法为:把加密后的hex字符串按256byte进行拆分,分别解密,最后得到原文。方法如下:

 


private string DecryptString(string source, BigInteger e, BigInteger n)
        {
            
int len = source.Length;
            
int len1 = 0;
            
int blockLen = 0;
            
if ((len % 256== 0)
                len1 
= len / 256;
            
else
                len1 
= len / 256 + 1;
            
string block = "";
            
string temp = "";
            
for (int i = 0; i < len1; i++)
            {
                
if (len >= 256)
                    blockLen 
= 256;
                
else
                    blockLen 
= len;
                block 
= source.Substring(i * 256, blockLen);
                BigInteger biText 
= new BigInteger(block, 16);
                BigInteger biEnText 
= biText.modPow(e, n);
                
string temp1 = System.Text.Encoding.Default.GetString(biEnText.getBytes());
                temp 
+= temp1;
                len 
-= blockLen;
            }
            
return temp;
        }

 

 这个算法一般来讲是没问题的,但问题就在于,对于128byte的数据,BigInteger类输出的加密后的hex字符串,并不一定是256byte。所以,解密的时候按照256byte进行拆分,就会出现字符串拆分不正确,最终导致解密失败,解密出来的结果是乱码。

 

我们来看看BigInteger类的ToHexString()方法,其实现如下:


public string ToHexString()
        {
            
string result = data[dataLength - 1].ToString("X");

            
for (int i = dataLength - 2; i >= 0; i--)
            {
                result 
+= data[i].ToString("X8");
            }

            
return result;
        }

 

 对于128byte的BigInteger,此方法返回的结果并不一定是256byte。

 

简单的解决办法,就是把这个方法的第一行,ToString("X")改为ToString("X8"),修改后的方法如下:


public string ToHexString()
        {
            
string result = data[dataLength - 1].ToString("X8");

            
for (int i = dataLength - 2; i >= 0; i--)
            {
                result 
+= data[i].ToString("X8");
            }

            
return result;
        }

 

这样改虽然可以解决问题,但属于治标不治本的方法,因为这样改,也不能保证输出的字符串就是256byte, 最靠谱的方法是改进加密方法和解密方法。

 

加密方法的改进: 

由于加密后的结果是通过输入16进制字符串进行保存的,输入的结果不可能包含@字符,因此我们可以用@符号来分割每128byte数据的加密结果,解密的时候按照@符号进行分割就不会出错。

改进后的加密方法如下:


private string EncryptString(string source, BigInteger d, BigInteger n)
        {
            
int len = source.Length;
            
int len1 = 0;
            
int blockLen = 0;
            
if ((len % 128== 0)
                len1 
= len / 128;
            
else
                len1 
= len / 128 + 1;
            
string block = "";
            StringBuilder result 
= new StringBuilder();
            
for (int i = 0; i < len1; i++)
            {
                
if (len >= 128)
                    blockLen 
= 128;
                
else
                    blockLen 
= len;
                block 
= source.Substring(i * 128, blockLen);
                
byte[] oText = System.Text.Encoding.Default.GetBytes(block);
                BigInteger biText 
= new BigInteger(oText);
                BigInteger biEnText 
= biText.modPow(d, n);
                
string temp = biEnText.ToHexString();
                result.Append(temp).Append(
"@");
                len 
-= blockLen;
            }
            
return result.ToString().TrimEnd('@');
        }

 

 改进后的解密方法如下:

 


private string DecryptString(string source, BigInteger e, BigInteger n)
        {
            StringBuilder result 
= new StringBuilder();
            
string[] strarr1 = source.Split(new char[] { '@' }, StringSplitOptions.RemoveEmptyEntries);
            
for (int i = 0; i < strarr1.Length; i++)
            {
                
string block = strarr1[i];
                BigInteger biText 
= new BigInteger(block, 16);
                BigInteger biEnText 
= biText.modPow(e, n);
                
string temp = System.Text.Encoding.Default.GetString(biEnText.getBytes());
                result.Append(temp);
            }
            
return result.ToString();
        }

 

相关链接:

[1]RSA私钥加密公钥解密算法。

http://blog.csdn.net/zhilunchen/archive/2008/09/17/2943158.aspx

[2]BigInteger大整数运算类。 

http://www.codeproject.com/KB/cs/biginteger.aspx

 

原文链接:http://www.cnblogs.com/hhh/archive/2011/06/03/2070692.html

欢迎留言

2 条评论

    1. @Sweeney:没有,上次加密遇到问题,C#给到的解决方案,我仿着思路,改java的问题

      Unknown Unknown