First we try, then we trust

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  183 随笔 :: 111 文章 :: 3325 评论 :: 358 引用

前一阵给公安局做项目,用到了公钥加密技术及对称密钥加密技术。信息通过3DES进行加密,而密钥通过RSA公钥体系传送。客户端使用CPU卡eKey进行解密。但是在系统编写过程中发现,.net中的RSA加密算法为了提高安全性,在待加密数据前要添加一些随机数,因此,使用.NET中的RSA加密算法一次最多加密117字节数据(多于117字节需要拆分成多段分别加密再连接起来),经过加密后得到一个长度为128字节的加密数据。但这对于需要进行收发双方身份确认的公钥体系来说会带来不少麻烦。在我的系统中,我需要通过以下步骤实现对用户会话密钥的网上加密传递:

加密过程:
1、对会话密钥添加随机数,补充到128位,
2、使用CA私钥解密,结果为128位数据,
3、对数据使用用户公钥加密,得到128位数据,通过网络传送。


解密过程:
1、使用用户私钥解密网上传送的128位数据;
2、将结果使用CA公钥加密;
3、去掉用来混淆的随机数,提取出会话密钥

但.net中的RSA加密最多只能对117字节数据进行操作,导致128位数据不得不分两部分进行处理,于是加密数据不断膨胀。为了解决这个问题,并使得RSA加密、解密过程与eKey上的过程相一致,我只好编写自己的RSA加密算法。

经过查找了大量资料后,我决定利用现成的 BigInteger 类。可以参考http://www.codeproject.com/csharp/biginteger.asp 得到更多的信息。利用 BigInteger,我添加了两个方法RSAEncrypt和RSADecrypt,实现RSA加密解密。这样就再也不用受117字节的限制了。

下面给出了两段程序,程序一是使用.Net自带RSA加密算法实现加密解密,不过 TextLength 属性一旦超过 117,系统将无法加密; 程序二是经过改造的系统,可以对128位的数据进行加密,没有了117的限制。程序二省略了BigInteger类,需要的话可以从http://www.codeproject.com/csharp/biginteger.asp下载,不要忘了注释其中的Main方法,否则在编译时会有一个编译错误,说有两个入口点(当然也可以在项目属性中指定一个入口点)。

程序一:

using System;
using System.Security.Cryptography;
using System.Text;

class OldRSA
{
   
static void Main()
   
{
      
int TextLength = 117;
      
byte[] encryptedData;
      
byte[] decryptedData;
      
string Key1 = "<RSAKeyValue><Modulus>4n6EJsx4qNFpp6h+wcPdJz8sSMMRJEVJaBQEGsOOBHKNePo/v3M94Nf89+zL5lLH7/LuRgcUfnizVIETH/z9+H/yDuM0F3fjImN3UtK1TK0ioFf0cVC9lnErbEoEjmkeQIVUJUC4c+BmqtTN6UrhFCY3R3zGp3feeGqORLjeKVc=</Modulus><Exponent>AQAB</Exponent><P>7w2qsVRBn168Ehc4V/fiPML+7WUkORRIJ9I8i21Fs5GlvYrja2CzBzPLKrAHumLOCLgd/qKj0iApF17471nfKw==</P><Q>8oztAlInRK1VDuVLHnPPcNQsehbP9IF5p+kwRu07sFGwAHnyeWuRG0EpebvbGOE/1KzpKqb/WU8vSN4OeauohQ==</Q><DP>DIh+5oUwW5av7ZLiFVqdtenTS8b9uzBhCBVxry2vddaxBdr+SWbse/gvMrG/9fmwK6zbhbopNJ8TCHKmQoZHuQ==</DP><DQ>6g96q/GxeUG3Qk+dBP8HIL9vSEX5Wd8UEigicV9/aS/7IwqLJgbama1xI8tXrBO6MDbIL2PGKF4UqEG5QEqZrQ==</DQ><InverseQ>nyx28u1fREiIgXgx2S5+PXbB8wq0xVxnE2G2Mt0vq9xQDHbaXEFpfznjNaga8AhVluNahqG5uRGRY3OgQONO4g==</InverseQ><D>PVKj1R1nTc3lHU+xgiTVq9qe0tR9v6RCy7sfoV9xBCM/ypF20Q8Sod3Y0Ad87U9ccssDWFJyagukAi0wUGjfGfalF8/4PFwqzrGBLsN96klmKLMy7C6oihlriW+MyxmvagGsp3/r4sE6wGk5ISchjKIKyv/PyWoobDRe6orDzIE=</D></RSAKeyValue>";

      
try
      
{
         RSACryptoServiceProvider RSA 
= new RSACryptoServiceProvider();
         RSA.FromXmlString(Key1);

         
byte[] dataToEncrypt = GenerateBytes(TextLength);
         Console.WriteLine(
"Original buff:  " + Convert.ToBase64String(dataToEncrypt) + " ");

         encryptedData 
= RSA.Encrypt(dataToEncrypt, false);
         Console.WriteLine(
"Encrypted buff:  " + Convert.ToBase64String(encryptedData) + " ");

         decryptedData 
= RSA.Decrypt(encryptedData,false);
         Console.WriteLine(
"Decrypted buff:  " + Convert.ToBase64String(decryptedData) + " ");
      }

      
catch
      
{
         Console.WriteLine(
"Encryption failed.");
      }

   }


   
//***********************************************************************
   
// 随机生成一指定长度的字节数组
   
//***********************************************************************
   public static byte[] GenerateBytes(int byteLength)
   
{
      
byte[] buff = new Byte[byteLength];
      RNGCryptoServiceProvider rng 
= new RNGCryptoServiceProvider();

      
// 该数组已使用密码增强的随机字节进行填充
      rng.GetBytes(buff);
      
return buff;
   }

}

程序二:

using System;
using System.Security.Cryptography;
using System.Text;

class NewRSA
{
   
public static void Main()
   
{
      
int TextLength = 128;
      
byte[] encryptedData;
      
byte[] decryptedData;
      
string Key1 = "<RSAKeyValue><Modulus>4n6EJsx4qNFpp6h+wcPdJz8sSMMRJEVJaBQEGsOOBHKNePo/v3M94Nf89+zL5lLH7/LuRgcUfnizVIETH/z9+H/yDuM0F3fjImN3UtK1TK0ioFf0cVC9lnErbEoEjmkeQIVUJUC4c+BmqtTN6UrhFCY3R3zGp3feeGqORLjeKVc=</Modulus><Exponent>AQAB</Exponent><P>7w2qsVRBn168Ehc4V/fiPML+7WUkORRIJ9I8i21Fs5GlvYrja2CzBzPLKrAHumLOCLgd/qKj0iApF17471nfKw==</P><Q>8oztAlInRK1VDuVLHnPPcNQsehbP9IF5p+kwRu07sFGwAHnyeWuRG0EpebvbGOE/1KzpKqb/WU8vSN4OeauohQ==</Q><DP>DIh+5oUwW5av7ZLiFVqdtenTS8b9uzBhCBVxry2vddaxBdr+SWbse/gvMrG/9fmwK6zbhbopNJ8TCHKmQoZHuQ==</DP><DQ>6g96q/GxeUG3Qk+dBP8HIL9vSEX5Wd8UEigicV9/aS/7IwqLJgbama1xI8tXrBO6MDbIL2PGKF4UqEG5QEqZrQ==</DQ><InverseQ>nyx28u1fREiIgXgx2S5+PXbB8wq0xVxnE2G2Mt0vq9xQDHbaXEFpfznjNaga8AhVluNahqG5uRGRY3OgQONO4g==</InverseQ><D>PVKj1R1nTc3lHU+xgiTVq9qe0tR9v6RCy7sfoV9xBCM/ypF20Q8Sod3Y0Ad87U9ccssDWFJyagukAi0wUGjfGfalF8/4PFwqzrGBLsN96klmKLMy7C6oihlriW+MyxmvagGsp3/r4sE6wGk5ISchjKIKyv/PyWoobDRe6orDzIE=</D></RSAKeyValue>";

      
try
      
{
         RSACryptoServiceProvider RSA 
= new RSACryptoServiceProvider();
         RSA.FromXmlString(Key1);
         RSAParameters RSAKeyInfo 
= RSA.ExportParameters(true);

         
byte[] dataToEncrypt = GenerateBytes(TextLength);
         Console.WriteLine(
"Original buff:  " + Convert.ToBase64String(dataToEncrypt) + " ");

         encryptedData 
= RSAEncrypt(dataToEncrypt, RSAKeyInfo.Exponent, RSAKeyInfo.Modulus);
         Console.WriteLine(
"Encrypted buff:  " + Convert.ToBase64String(encryptedData) + " ");

         decryptedData 
= RSADecrypt(encryptedData, RSAKeyInfo.D, RSAKeyInfo.Modulus);
         Console.WriteLine(
"Decrypted buff:  " + Convert.ToBase64String(decryptedData) + " ");
      }

      
catch
      
{
         Console.WriteLine(
"Encryption failed.");
      }

   }


   
//***********************************************************************
   
// RSA Encrypt
   
//***********************************************************************
   static public byte[] RSAEncrypt(byte[] dataToEncrypt, byte[] Exponent, byte[] Modulus)
   
{
      BigInteger original 
= new BigInteger(dataToEncrypt);
      BigInteger e 
= new BigInteger(Exponent);
      BigInteger n 
= new BigInteger(Modulus);

      BigInteger encrypted 
= original.modPow(e,n);
      
return HexStringToByte(encrypted.ToHexString());
   }


   
//***********************************************************************
   
// RSA Decrypt
   
//***********************************************************************
   static public byte[] RSADecrypt(byte[] encryptedData, byte[] D, byte[] Modulus)
   
{
      BigInteger encrypted 
= new BigInteger(encryptedData);
      BigInteger d 
= new BigInteger(D);
      BigInteger n 
= new BigInteger(Modulus);

      BigInteger decrypted 
= encrypted.modPow(d,n);
      
return HexStringToByte(decrypted.ToHexString());
   }


   
//***********************************************************************
   
// 将 HexString 转换为 byte[] 数组
   
//***********************************************************************
   static public byte[] HexStringToByte(string hexString)
   
{
      
byte[] byteResult = new byte[hexString.Length/2];

      
for(int i = 0; i < hexString.Length/2; i++
         byteResult[i] 
= Convert.ToByte(hexString.Substring(i*2,2),16);

      
return byteResult;
   }


   
//***********************************************************************
   
// 随机生成一指定长度的字节数组
   
//***********************************************************************
   public static byte[] GenerateBytes(int byteLength)
   
{
      
byte[] buff = new Byte[byteLength];
      RNGCryptoServiceProvider rng 
= new RNGCryptoServiceProvider();

      
// 该数组已使用密码增强的随机字节进行填充
      rng.GetBytes(buff);
      
return buff;
   }

}

posted on 2004-06-30 23:05 吕震宇 阅读(5686) 评论(11)  编辑 收藏

评论

#1楼 2005-05-09 21:14 LEE
不明白楼主为何是不能使用.NET的RSACryptoServiceProvider,
使用3DES数据加密的随机密钥长度是24字节+IV向量(8字节),一共才32字节,不到117字节,使用.NET的RSA.Encrypt加密,会自动添加随机数补足128字节,然后加密。这样不是很好吗?
干嘛非得自已搞一套呢?很多FREE CODE都多多少少有一些BUG:(
不幸撞上就不好罗。
 回复 引用   

#2楼[楼主] 2005-05-13 17:08 吕震宇      
没有办法,因为我的解密客户端是“USB智能卡”,它只支持RSA,并且没有117字节限制,所以当需要交互时不得不“处理”一下了。
 回复 引用 查看   

当数据长度为超过128很多时,比如长度为400出现这样的异常:System.ArithmeticException: Byte overflow in constructor.
在 Midapexsoft.Security.BigInteger..ctor(Byte[] inData) 位置 F:\Dengyangjun\Midapexsoft\Core\Core\Security\BigInteger.cs:行号 372
在 Midapexsoft.Security.RSACryptService.RSADecrypt(Byte[] encryptedData, Byte[] D, Byte[] Modulus) 位置 F:\Dengyangjun\Midapexsoft\Core\Core\Security\RSACryptService.cs:行号 65
 回复 引用   

#4楼 2006-08-18 17:42 Kuangyf[未注册用户]
这个问题我也发现了,就是用.net的加密每次加出来的结果都是不一样的,非常奇怪,所以我怀疑.net是在前面加了随机数加密的,楼主有没有这个规范啊?就是他们如何填充这个随机的?因为他们加密的我用java的包解不开,非常郁闷,现在所有人都卡在这了…………
 回复 引用   

#5楼[楼主] 2006-08-19 21:44 吕震宇      
@Dengyangjun
数据超出128时,要将数据切分成长度为128的多个小块,然后分别加密再连接起来。不能直接进行加密运算。

@Kuangyf
时间很久了,记得.net似乎是在数据的开始或终结出增加了随机数。你可以尝试使用reflactor软件反编译一下.net类库,看看究竟是怎么一回事。
 回复 引用 查看   

非常好。

我被这个问题困扰很久了,只是公司不用,所以没动力区解决。
 回复 引用   

我测试了您的代码,发现存在问题。例如被加密的字符串是:
YRdb8AeFzrDtibZBSWqqR+6Yb/GXHzOoHvmzQll/4KzKrqy98MDdvfeSe4b37hcBxKTQm8HHUBMG8ogTTnxwhhZICF+tFu14wZSezO5w3I2f16BNIF1flTQ5967Xsfdh81w+mcVfgqXxnGDRjCVmnFJ+mUimMf443hVHnjNK/CY=

解密后的字符串是:
Ŵ���𬶘�P�×|E$�)+,�-�����$*�z��KS ������H�t����j$�!��>Ph� �����4��sgj{Qe2�)��h�*��8���Nr~\>�m�F?G7x�;9�<Q!b�V�f3�E

我把这样的语句修改了也不行
//return HexStringToByte(decrypted.ToHexString());
return decrypted.getBytes();


而且,被加密的字符串长度有限,这句话(class BigInteger)做了限制
private const int maxLength = 70;

总之,就目前来说,您提供的方法存在bug,不是使用。
 回复 引用   

#8楼 2008-02-09 23:05 yezie      
汗!!
我也是解密后的字符串是乱码
同楼上
 回复 引用 查看   

#9楼 2008-11-10 16:50 caidao[未注册用户]
楼主的代码不能用呀

Original buff: DX/CG4eLwyLtG+0dfWrPT4AaWBr9GLBQSrZzNkg397sCFK733k51znb2rGmlQT4GbG9AkJD/MYH4wsC5oPxpJHT+YC+KXEf1Z/CFkfDMkY6n3kvQSxrOvTPjpbS7E0zZ8tPZcUd65Qj6To4gn8kp0rEsYq0bTppg4fBS1QlfkBo=
Encrypted buff: msBO/YJ9VmfuPbHbUpqQ7LIZ4z61N9bVPNI4YQvBV656em14CHiCgCnmoJdjsJrucmtLj2K4194NXxifF7bgOAm93t5MqOsTkrUe/pLNvT7T/98GzvbnNpWmaNzvBqvMxZIj+dXm7glJqgMGw0IE224+/yf7IfSQkKlVdBpX/7Q=
Decrypted buff: 1/whuHi8Mi7RvtHX1qz0+AGlga/RiwUEq2czZIN/e7AhSu995Odc529qxppUE+BmxvQJCQ/zGB+MLAuaD8aSR0/mAvilxH9WfwhZHwzJGOp95L0Esazr0z46W0uxNM2fLT2XFHeuUI+k6OIJ/JKdKxLGKtG06aYOHwUtUJX5AQ==

 回复 引用   

#10楼 2011-05-07 21:25 大石头      
引用吕震宇:没有办法,因为我的解密客户端是“USB智能卡”,它只支持RSA,并且没有117字节限制,所以当需要交互时不得不“处理”一下了。
<br>


非对称加密,入门不容易,熟练使用更难。

很多人说每次加密得到的都不一样,那是因为初始化向量IV在作怪。不一样才是对的,因为在密码安全体系里面,同样的密码加密同样的数据,加密后得到不一样,可以避免重放攻击!
 回复 引用 查看   

#11楼 2011-06-03 14:57 海边的风      
看看我的解决办法吧,

C#使用RSA私钥加密公钥解密的改进,解决特定情况下解密后出现乱码的问题http://www.cnblogs.com/hhh/archive/2011/06/03/2070692.html
 回复 引用 查看