最近需要对一些数据加密后进行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
文章关键词:C#,RSA,私钥加密公钥解密,BigInteger类,乱码
----------END----------