分类: 嵌入式
2012-06-23 22:24:23
非对称加密在理论上似乎比对称加密简单,但是在实际应用中的细节却复杂得多,为了能由浅入深地理解.NET中的非对称加密,本小节分步理解其中的细节。
当使用一个非对称加密类创建一个该类的实例的时候,构造函数会生成一个“公钥/私钥”对。我们可以选择是否保存该密钥和保存的方式。
先从代码清单6-8的内容来熟悉下非对称密钥的密钥结构。
代码清单6-8 输出非对称密钥
class Program
{
staticvoid Main(string[] args)
{
RSACryptoServiceProvider rcp = new RSACryptoServiceProvider();
RSAParameters parameter1 = rcp.ExportParameters(true);
RSAParameters parameter2 = rcp.ExportParameters(false);
WriteParameInfo(parameter1,"parameter1");
WriteParameInfo(parameter2,"parameter2");
Console.Read();
}
privatestaticvoid WriteParameInfo(RSAParameters parameter,string name)
{
Console.WriteLine(name+".D:");
Console.WriteLine(ConverBitTostring(parameter.D));
Console.WriteLine(name+"DP:");
Console.WriteLine(ConverBitTostring(parameter.DP));
Console.WriteLine(name+".DQ");
Console.WriteLine(ConverBitTostring(parameter.DQ));
Console.WriteLine(name+".InverseQ");
Console.WriteLine(ConverBitTostring(parameter.InverseQ));
Console.WriteLine(name+".Modulus");
Console.WriteLine(ConverBitTostring(parameter.Modulus));
Console.WriteLine(name+".P");
Console.WriteLine(ConverBitTostring(parameter.P));
Console.WriteLine(name+".Q");
Console.WriteLine(ConverBitTostring(parameter.Q));
Console.WriteLine(name + ".Exponent");
Console.WriteLine(ConverBitTostring(parameter.Exponent));
}
publicstaticstring ConverBitTostring(byte[] bytes)
{
if (bytes == null)
{
return"该值为空!";
}
else
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
sb.Append(bytes[i].ToString());
}
return sb.ToString();
}
}
}
在代码清单6-8中,首先使用RSACryptoServiceProvider rcp = new RSACryptoServiceProvider();来生成RSA实现类的实例rcp,然后调用ExportParameters方法导出密钥。ExportParameters方法的参数是bool值,如果该值为true表示可以导出私钥,否则为不可导出私钥。密钥导出的结果为RSAParameters结构,在了解该结构的各个属性值之前,先看看输出结果,parameter1的输出结果如图6-15所示,parameter2的输出结果如图6-16所示。
图6-15 parameter1的输出结果
图6-16 parameter2的输出结果
如果对RSA算法的原理还不是很熟悉的话,建议再看一看6.3.2节的相关内容,这里为了叙述图6-15和图6-16中的输出结果,简要说明RSA算法:
若要生成密钥对,可以从创建名为p和q的两个大的质数开始。这两个数相乘,结果称为n。因为p和q都是质数,所以n的全部因数为1、p、q和 n。如果仅考虑小于n的数,则与n为互质数(即与n没有公因数)的数的个数等于 (p - 1)(q - 1)。现在,选择一个数e,它与计算的值为互质数。则公钥表示为 {e, n}。若要创建私钥,则必须计算 d,它是满足 (d)(e) mod n = 1的一个数。根据 Euclidean 算法,私钥为{d, n}。纯文本m到密码文本c的加密定义为 c = (m ^ e) mod n。解密则定义为m = (c ^ d) mod n。
现在结合对RSA算法简单介绍RSAParameters的各个属性值的含义。
D:d,私钥指数
DP:d mod (p - 1)
DQ:d mod (q - 1)
Exponent:e,公钥指数
InverseQ:(InverseQ)(q) = 1 mod p
Modulus:n
P:p
Q:q
因为公钥是重复使用的,如果想使用导出的密钥,可以使用ImportParameters方法,导入RSAParameters结构。
如果想把密钥保存到本地文件中,可以使用ToXmlString方法把密钥导出到本地,如代码清单6-9所示。
代码清单6-9 导出密钥到XML
staticvoid Main(string[] args)
{
RSACryptoServiceProvider rcp = new RSACryptoServiceProvider();
string keyString = rcp.ToXmlString(true);
FileStream fs = File.Create("e:\\key.xml");
fs.Write(Encoding.UTF8.GetBytes(keyString), 0, Encoding.UTF8.GetBytes(keyString).Length);
fs.Flush();
fs.Close();
Console.WriteLine(keyString);
Console.Read();
}
在代码清单6-9中,调用了rcp.ToXmlString(true)方法,返回XML字符串,然后把该字符串写入到本地磁盘E的key.xml文件中。结果如图6-17所示。
图6-17 保存成功的XML文件
仍然可以使用FromXmlString方法来使用保存的密钥。但是将密钥原封不动地存在本地文件中,密钥的安全性被完全破坏了,.NET提供了安全的保存密钥的方式——密钥容器。代码清单6-10演示了如何将密钥保存到密钥容器中。
代码清单6-10 将密钥保存的密钥容器
CspParameters cp = new CspParameters();
cp.KeyContainerName = "xuanhunKeyContainer";
RSACryptoServiceProvider rcp = new RSACryptoServiceProvider(cp);
string keyString = rcp.ToXmlString(true);
FileStream fs = File.Create("e:\\key1.xml");
fs.Write(Encoding.UTF8.GetBytes(keyString), 0, Encoding.UTF8.GetBytes(keyString).Length);
fs.Flush();
fs.Close();
代码清单6-10中,首先初始化了CspParameters实例cp,然后为该实例的属性KeyContainerName赋值为"xuanhunKeyContainer"。在初始化RSACryptoServiceProvider实例的时候传入cp,这样密钥就保存在了密钥容器中。为了测试这段程序是不是如预期的这样,关闭程序然后运行如代码清单6-11所示的代码。
代码清单6-12 获取密钥容器中的密钥
CspParameters cp = new CspParameters();
cp.KeyContainerName = "xuanhunKeyContainer";
RSACryptoServiceProvider rcp = new RSACryptoServiceProvider(cp);
string keyString = rcp.ToXmlString(true);
FileStream fs = File.Create("e:\\key2.xml");
fs.Write(Encoding.UTF8.GetBytes(keyString), 0, Encoding.UTF8.GetBytes(keyString).Length);
fs.Flush();
fs.Close();
代码清单6-12和代码清单6-11几乎是一模一样的,那么获取密钥的机制是什么呢?如果系统中不存在指定名称的密钥容器,那么当执行以上代码时会创建新的密钥容器,如果存在,则从存在的密钥容器中加载密钥。为了测试,在代码清单中6-12与6-11中仍然生成了两个新的XML文件,key.xml和key2.xml,如果两个文件一模一样,说明密钥保存在了系统的密钥容器中,可以被反复利用,否则不然。在命令行使用cp命令,结果如图6-18所示。
图6-18 比对key.xml和key2.xml
图6-18证明了可以将密钥保存到系统的密钥容器中。
通过上面的操作,我们获得了密钥,现在就可以利用获得的密钥进行数据的加、解密操作了。由于非对称加密在性能上的瓶颈,通常只用它来加密少量的数据,比如对称加密的密钥和初始化向量(IV)。
下面使用RSA算法的实现类RSACryptoServiceProvider来加密DESCryptoServiceProvider实例生成的初始化向量(IV)和密钥。如代码清单6-13所示。
代码清单6-13 加密数据
class Program
{
staticvoid Main(string[] args)
{
RSACryptoServiceProvider rcp = new RSACryptoServiceProvider(cp);
DESCryptoServiceProvider dcp = new DESCryptoServiceProvider();
byte[] iv = dcp.IV;
byte[] key = dcp.Key;
Console.WriteLine("DES:V:\r\n{0}\r\n", ConverBitTostring(iv));
Console.WriteLine("DES:Key:\r\n{0}\r\n", ConverBitTostring(key));
byte[] EnriptIV = rcp.Encrypt(iv, false);
byte[] EncriptKey= rcp.Encrypt(key, false);
Console.WriteLine("DES:EnriptIV:\r\n{0}\r\n", ConverBitTostring(EnriptIV));
Console.WriteLine("DES:EncriptKey:\r\n{0}\r\n", ConverBitTostring(EncriptKey));
Console.Read();
}
publicstaticstring ConverBitTostring(byte[] bytes)
{
if (bytes == null)
{
return"该值为空";
}
else
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
sb.Append(bytes[i].ToString()+" ");
}
return sb.ToString();
}
}
}
如代码清单6-13所示,首先生成两个实例,rcp(RSACryptoServiceProvider实例)和dcp(DESCryptoServiceProvider实例),然后保存dcp的初始化向量(IV)和密钥到数字iv和数组key。输出未加密的iv和key后,执行RSACryptoServiceProvider类的Encrypt方法对数据进行加密。
说明Encrypt方法有两个参数,第一个参数是要加密的数据;第二个参数是是一个bool值,如果为 true,则使用OAEP填充(仅在运行Microsoft Windows XP或更高版本的计算机上可用)执行直接的RSA加密;否则,如果为false,则使用PKCS#1 1.5版填充。
加密之后,输出加密结果和未加密数据进行比对。结果如图6-19所示。
图6-19 代码清单6-13 运行结果
从图6-19的运行结果可以看出,对密钥和初始化向量(IV)成功地进行了加密。
对数据进行解密的过程同样简单,只需要使用RSACryptoServiceProvider类的Decript方法,对代码清单6-13做微小的改动即可,如代码清单6-14所示。
代码清单6-14 解密数据
staticvoid Main(string[] args)
{
CspParameters cp = new CspParameters();
cp.KeyContainerName = "xuanhunKeyContainer";
RSACryptoServiceProvider rcp = new RSACryptoServiceProvider(cp);
DESCryptoServiceProvider dcp = new DESCryptoServiceProvider();
byte[] iv = dcp.IV;
byte[] key = dcp.Key;
Console.WriteLine("DES:êoIV:êo\r\n{0}\r\n", ConverBitTostring(iv));
Console.WriteLine("DES:Key:\r\n{0}\r\n", ConverBitTostring(key));
byte[] EnriptIV = rcp.Encrypt(iv, false);
byte[] EncriptKey= rcp.Encrypt(key, false);
Console.WriteLine("DES:êoEnriptIV:êo\r\n{0}\r\n", ConverBitTostring(EnriptIV));
Console.WriteLine("DES:EncriptKey:\r\n{0}\r\n", ConverBitTostring(EncriptKey));
byte[] DecriptIV = rcp.Decrypt(EnriptIV, false);
byte[]DecriptKey = rcp.Decrypt(EncriptKey,false);
Console.WriteLine("DES:êoDeriptIV:êo\r\n{0}\r\n", ConverBitTostring(DecriptIV));
Console.WriteLine("DES:DEcriptKey:\r\n{0}\r\n", ConverBitTostring(DecriptKey));
Console.Read();
}
在代码清单6-14中,使用Decrypt方法对已经加密的IV和密钥进行解密,解密结果如图6-20所示。
图6-20 代码清单6-14运行结果
对比图6-20中的输出结果,解密之后的数据和加密之前得数据完全相同。但是与对称加密不同的是,加密使用的是公钥,解密使用的是私钥,公钥需要被公布出去。那么在实际应用中如何从RSACryptoServiceProvider的实例中分离公钥和密钥,实际上在前文关于密钥的基本操作中已经解决了这个问题。在密钥导出时不论使用ExportParameters方法还是ToXml方法,可以通过设置其参数是true或者false来指定导出的密钥是否包含私钥。导出的密钥如果不包含私钥,那么导入此密钥的加密算法实例只能执行加密任务而不能执行解密任务;如果导出的密钥既包含私钥也包含公钥,那么导入该密钥的加密算法既可执行加密任务也能执行解密任务。
-----------------------注:本文部分内容改编自《.NET 安全揭秘》