代码改变世界

net加密基础2-非对称加密

2012-02-09 20:31  海不是蓝  阅读(2478)  评论(6编辑  收藏  举报

非对称密码算法

非对称密码算法使用2个不同但在数学上却相关的密钥。用于加密数据的密钥不能用于解密。

非对称密码技术统称称为“公钥加密技术”,没有双方必要保密的单个密钥。

公钥加密技术只有一个必须由一方保密的私钥,第二个密钥就是公钥,任何想与他们通信的人都可以随意使用它。

安全web通信就是采用的这种技术。该技术可以用于电子商务。

 

非对称和对称的区别

对称加密和非对称加密的密钥类型分别是私钥和公钥。

 

对称加密使用的是私钥加密,也就是密钥只能是加密者和解密者知道,第3方不能知道。

而非对称加密情况完全不一样,非对称加密有2个不同的密钥,一个是公共的密钥,一个是私有的密钥,公钥是可以让外人知道的,因为公钥只用来加密数据,而私钥只能由解密方知道,只有私钥能解密!

 

官方的啰嗦!

公钥加密使用一个必须对未经授权的用户保密的私钥和一个可以对任何人公开的公钥。公钥和私钥都在数学上相关联;用公钥加密的数据只能用私钥解密,而用私钥签名的数据只能用公钥验证。公钥可以提供给任何人;公钥用于对要发送到私钥持有者的数据进行加密。两个密钥对于通信会话都是唯一的。公钥加密算法也称为不对称算法,原因是需要用一个密钥加密数据而需要用另一个密钥来解密数据。

 

经典的小红和小明

双方(小红和小明)可以按照下列方式使用公钥加密。首先,小红生成一个公钥/私钥对。如果小明想要给小红发送一条加密的消息,他将向她索要她的公钥。小红通过不安全的网络将她的公钥发送给小明,小明接着使用该密钥加密消息。(如果小明在不安全的信道如公共网络上收到小红的密钥,则小明必须同小红验证他具有她的公钥的正确副本。)小明将加密的消息发送给小红,而小红使用她的私钥解密该消息。

 

但是,在传输小红的公钥期间,未经授权的代理可能截获该密钥。而且,同一代理可能截获来自小明的加密消息。但是,该代理无法用公钥解密该消息。该消息只能用小红的私钥解密,而该私钥没有被传输。小红不使用她的私钥加密给小明的答复消息,原因是任何具有公钥的人都可以解密该消息。如果小红想要将消息发送回小明,她将向小明索要他的公钥并使用该公钥加密她的消息。然后,小明使用与他相关联的私钥来解密该消息。

 

在一个实际方案中,小红和小明使用公钥(不对称)加密来传输私(对称)钥,而对他们的会话的其余部分使用私钥加密。

 

NET中的非对称加密算法类

算法

抽象算法类

默认实现类

有效密钥大小(位)

默认密钥大小(位)

RSA

RSA

RSACryptoServiceProvider

364-16384

1024

DSA

DSA

DSACryptoServiceProvider

364-512

1024

 

注意:公钥加密算法使用固定的缓冲区大小,而私钥加密算法使用长度可变的缓冲区。公钥算法无法像私钥算法那样将数据链接起来成为流,原因是它只可以加密少量数据。因此,不对称操作不使用与对称操作相同的流模型。

 

所有的非对称加密算法都是由AsymmetricAlgorithm类派生。

 

 

非对称RSA加密算法

  实际使用中很少直接用RSA类来实现加密算法,因为它们采用不进行填充的原始计算。而是使用RSACryptoServiceProvider实现类的加密和解密方法

public byte[] Decrypt(byte[] rgb, bool fOAEP);

public byte[] Encrypt(byte[] rgb, bool fOAEP);

 

创建个rsa算法的对象

RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();

 

我们怎么获取公钥和私钥信息?

之前的对称算法可以获取算法的key和IV信息。

但是非对称算法不提供可以用于检索密钥信息的任何属性!如果需要保存密钥信息供以后使用,就只能用ToXmlString()方法输出密钥对的信息。

public override string ToXmlString(bool includePrivateParameters);

这个布尔参数如果设置为true,那么就会输出带私钥的xml,如果是false,那么就只输出带公钥的xml。

 

尝试输出完整的密钥对

rsa.ToXmlString(true)

----------------------------完整的密钥对信息-----------------------------

<RSAKeyValue>

<Modulus>yvGBq09qcWhIi+u3UHGK0EH5zav018FVGM+BPiXK23nFZeRky9HLrOH9DP5Bc7NXyBxoE0MC3YmaG68tr6nHtLhI7

TVshAsfMX1E2BjTfLq1wHhcFwTJSvyNpwnR9NHMZsDE166XgRQ5vHkfFwBWvk7F8YVcACKp3Y6OcvAcp1s=</Modulus>

<Exponent>AQAB</Exponent>

<P>7+9YUu/6SFlOW4RrON/Lie+bMSmOZB/wfqj9AAdmX+n8joPQ/WLA69/CtetzIZ6KIOkD2fd8MFiFrjoHSXmpUQ==</P>

<Q>2IgZma4hTV3EWFP3TXasajl23oRoLBeuWZldu+Q2pyeCwuBE6Mmp2aTbXauPGw3ptUAX/8pw0QyI/6i0b3wa6w==</Q>

<DP>F4AW45Czr/BnV1lh8yEgW3NHfQo38yCZup4soZsX8N8HKKJKjvbkNHYnKRBVp35SwyRvhyLRXB7fgRAX9J9g8Q==</DP>

<DQ>SSgCbje0rKznb2g+/37+1YzAqoFVqL//eeolDxwVkvf4Z9rZrUSlDBF0w/r4iI10znXvJc7Buv9fMfFPtPLbLQ==</DQ>

<InverseQ>pJ37e7nw8Joy2dmnrYf5urx88NQz1TBKZF/xGReSeTiBsbkugqkoHWvX6GoWV8TL7Gx9ZRmAma/rs3YWQbk00Q==</InverseQ>

<D>PrjbibW2wSwo183XTy54Z5sseIt/1brz8QIZALsvchu1jaNEH9ZMa7dAvWZLllXEeJ2G8QUR+qRPk+TVaug/RydliAPdf11h+7nnIoNOzk4LJtBFmNROPXTOLNwap+GJWIQM1Hd72V4kDiv1He3HUNLsLgGmUelDWekISUzrYOE=</D>

</RSAKeyValue>

 

上面的xml信息可以在我们需要使用的时候导入到算法里面。

 

只输出公钥信息

rsa.ToXmlString(false)

----------------------------只包含公钥信息-----------------------------

<RSAKeyValue>

<Modulus>igBXA4/oLVLNP+r9jxdx1lLfqNNkSUOe0ApKql8/jmUGc/dIzSMF6bgBcvw5dBxXncYVXxrtk7

AEilemz8PQui07M0NbJ4IeGQzRcJJNlAsQJvGWTydaoH/xLm4I0wH13RG2V9

UjcOYPMOHBEapB4Flkst44qqclw8SVuh55OWk=</Modulus>

  <Exponent>AQAB</Exponent>

</RSAKeyValue>

 

一个简单的RSA例子

RSA简单例子
static void Main()
{
Byte[] StrByte = Encoding.UTF8.GetBytes("我是明文");
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
string str = RSAEncryptor(rsa.ToXmlString(false), StrByte);
Console.WriteLine("密文:{0}", str);
Encoding en = Encoding.UTF8;
Console.WriteLine("明文:{0}", RSADecryptor(rsa.ToXmlString(true), Convert.FromBase64String(str), en));
Console.Read();
}

public static string RSAEncryptor(string XMLKey, byte[] Str)
{
try
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(XMLKey);
return Convert.ToBase64String(rsa.Encrypt(Str, false));
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return null;
}
}

public static string RSADecryptor(string XMLKey, byte[] Str, Encoding en)
{
try
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(XMLKey);
return en.GetString(rsa.Decrypt(Str, false));
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return null;
}
}

 

如果给解密的RSA对象传输个不包含私钥的XML,那么就会异常了,

你可以讲上面调用解密的代码修改成下面这样,就能得到异常了

Console.WriteLine("明文:{0}", RSADecryptor(rsa.ToXmlString(false), Convert.FromBase64String(str), en));

 

 

使用参数来传递密钥信息

RSAParameters

RSACryptoServiceProvider有2个方法可以导出RSAParameters类型的参数对象。

在使用加密解密算法的时候我们就只需要传递RSAParameters对象

 

将上面的代码成使用RSAParameters

 

 

使用RSAParameters
static void Main()
{
Byte[] StrByte = Encoding.UTF8.GetBytes("使用RSAParameters");
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
string str = RSAEncryptor(rsa.ExportParameters(false), StrByte);
Console.WriteLine("密文:{0}", str);
Encoding en = Encoding.UTF8;
Console.WriteLine("明文:{0}", RSADecryptor(rsa.ExportParameters(true), Convert.FromBase64String(str), en));
Console.Read();
}

public static string RSAEncryptor(RSAParameters KeyPar, byte[] Str)
{
try
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(KeyPar);
return Convert.ToBase64String(rsa.Encrypt(Str, false));
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return null;
}
}

public static string RSADecryptor(RSAParameters KeyPar, byte[] Str, Encoding en)
{
try
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(KeyPar);
return en.GetString(rsa.Decrypt(Str, false));
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return null;
}
}


 

使用容器存储CSP密钥

之前使用的导出XML密钥信息的做法并不妥当,推荐使用密钥容器。

 

有关密钥容器的更多信息,请参见位于 http://www.microsoft.com/china/msdn 上的 Platform SDK 文档中的“CryptoAPI”一节

 

我们这里使用的密钥容器是CspParameters类,当我们创建个CspParameters对象时,会生成随机的密钥对信息。我们只需要使用这个对象,便能方便的把密钥信息传递给相关的加密解密方法。

 

不使用密钥容器创建2个不同的RSA对象

RSACryptoServiceProvider rsa1 = new RSACryptoServiceProvider();

string xml1 = rsa1.ToXmlString(true);

RSACryptoServiceProvider rsa2 = new RSACryptoServiceProvider();

string xml2 = rsa2.ToXmlString(true);

Console.WriteLine(xml1 == xml2);

输出False

 

使用密钥容器创建2个不同的RSA对象

CspParameters csp = new CspParameters();

csp.KeyContainerName = "MyCspParameters";

RSACryptoServiceProvider rsa1 = new RSACryptoServiceProvider(csp);

string xml1 = rsa1.ToXmlString(true);

RSACryptoServiceProvider rsa2 = new RSACryptoServiceProvider(csp);

string xml2 = rsa2.ToXmlString(true);

Console.WriteLine(xml1 == xml2);

输出True

 

RSA加密大数据对象

RSA算法的一个弱点是只能加密117字节数据,前面的实例都是比较小的数据。

所以加密大数据对象的时候我们只能使用分块加密,讲数据分成多次加密。

 

(注意RSA加密后的Byte数组长度固定为128,117和128都是在默认密钥大小下的规定,如果您使用的不是默认密钥大小,那么就会不一样)

 

解密的思维也是将加密后的字节数组分多次来解密,这里每次最大每次解密128字节数据。

static void Main()
{
Byte[] StrByte = Encoding.UTF8.GetBytes("王祖贤(1967年1月31日出生),英文名Joey Wong,身高1.72米。出生于台湾台北市,祖籍安徽舒城。台湾出道后于香港发展之电影女演员。17岁时拍摄第一部电影《今年的湖畔会很冷》并获得金马奖三项提名。1987年以《倩女幽魂》一片开始走红于亚洲各地。2009年7月7日传王祖贤因感情不顺在加拿大削发为尼。王祖贤本人对此表示否认,王父也通过王祖贤干爹傅达仁否认传言。");
Console.WriteLine(StrByte.Length);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
string str = RSAEncryptor(rsa.ExportParameters(false), StrByte);
Console.WriteLine("密文:{0}", str);
Encoding en = Encoding.UTF8;
Console.WriteLine("明文:{0}", RSADecryptor(rsa.ExportParameters(true), Convert.FromBase64String(str), en));
Console.Read();
}

public static string RSAEncryptor(RSAParameters par, byte[] Str)
{
try
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(par);
//获取RSA最大加密的长度
Int32 KeySize = rsa.KeySize / 8;
Int32 BlockSize = KeySize - 11;
if (Str.Length > BlockSize)
{
//分配加密次数
Int32 EncryptIndex = 0;
if (Str.Length % BlockSize != 0)//不能整除
{
EncryptIndex = Str.Length / BlockSize + 1;
}
else
{
EncryptIndex = Str.Length / BlockSize;
}
//分配存储已加密的byte数组,请注意下这里分配的大小
//每次只能加密117长度的数据,但是每次加密117长度的数据要生成128长度的密文。
Byte[] EncryptByte = new Byte[EncryptIndex * KeySize];
for (Int32 i = 0; i < EncryptIndex; i++)
{
Byte[] Block = new Byte[BlockSize];
if (i == (EncryptIndex - 1))
{
//最后一次加密
Array.Copy(Str, i * BlockSize, Block, 0, Str.Length % BlockSize);
}
else
{
Array.Copy(Str, i * BlockSize, Block, 0, BlockSize);
}
Byte[] Block1 = rsa.Encrypt(Block, false);
//将加密过的Byte数组保存到EncryptByte中
Array.Copy(Block1, 0, EncryptByte, i * KeySize, KeySize);
}
return Convert.ToBase64String(EncryptByte);
}
else
{
byte[] b = rsa.Encrypt(Str, false);
return Convert.ToBase64String(b);
}
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return null;
}
}
public static string RSADecryptor(RSAParameters par, byte[] Str, Encoding en)
{
try
{
Byte[] Block1;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(par);
Int32 KeySize = rsa.KeySize / 8;
Int32 BlockSize = KeySize - 11;
if (Str.Length == KeySize)
{
return en.GetString(rsa.Decrypt(Str, false));
}
else
{
//这个变量用来记录所有解密后的Byte数组的长度
Int32 Len = 0;
//保存每次解密后的Byte数组
List<Byte[]> list = new List<Byte[]>();
//计算解密需要的次数
Int32 EncryptIndex = Str.Length / KeySize;

for (Int32 i = 0; i < EncryptIndex; i++)
{
Byte[] Block = new Byte[KeySize];
Array.Copy(Str, i * KeySize, Block, 0, KeySize);
Block1 = rsa.Decrypt(Block, false);
Len += Block1.Length;
list.Add(Block1);
}
Byte[] EncryptByte = new Byte[Len];
for (Int32 i = 0; i < list.Count; i++)
{
Array.Copy(list[i], 0, EncryptByte, i * BlockSize, BlockSize);
}
return en.GetString(EncryptByte);
}
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return null;
}
}

 


RSA的优点和缺点

 

优点

1.安全性高,几乎不会被攻破。

缺点

1.速度慢,数学原理复杂。

2.单次加密数据小,加密大数据处理复杂

 

作者:海不是蓝

博客:http://www.cnblogs.com/hailan2012/

邮箱:hailan2012@sina.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。