一、公钥加密
1、概述
公钥加密是非对称的,因此加密和解密需要使用不同的密钥。对称加密的密钥可以是一串长度合适的任意字节串。但非对称加密则需要专门制作一对密钥。
这个密钥对包含一个公钥和一个私钥。它们将一起完成如下工作:
公钥加密消息
私钥对消息进行解密
制作密钥对的一方会保证私钥的安全,而将公钥*分发。这种加密技术的特点在于无法仅通过公钥而计算出私钥。因此,如果私钥丢失就无法解密了。相反,如果私钥泄露了,那么加密系统也就无法起到保护作用了。
公钥握手可以使两台计算机在公共网络中无须事先达成一致或共享安全信息就能安全地通信。
为了说明其中的环节,不妨假设计算机Origin希望给另一台计算机Target发送一条机密信息:
1.Target计算机生成一个公钥/私钥对,并将公钥分发给Origin。
2.Origin使用Target的公钥将机密消息加密,并发送给Target。
3.Target使用私钥解密机密消息。
窃听者可以获取以下数据:
1、Target的公钥
2、使用Target的公钥加密后的消息
但是在没有Target的私钥的情况下,加密的消息是无法解密的。
这种方式并不能防范中间人攻击。换句话说,Origin无法知道Target是否是恶意攻击方。为了验证接收者,发送者需要事先知道接收者的公钥,或者通过网站的数字证书验证其身份。
Origin发送给Target的加密消息中一般包含了一个新创建的对称加密密钥,而接下来的通信将使用该密钥进行对称加密。这样,本次会话中的后续通信就不需要再使用Origin的公钥了。这是因为对称加密算法更适合于处理大的消息。如果每一次会话都会生成新的公钥/私钥对的话,这种协议就更加安全了,因为通信双方不需要存储任何密钥。
公钥加密算法要求加密的信息小于密钥的长度,因此更适合于加密少量的数据(例如对称加密的密钥)。如果试图加密的数据长度大于密钥长度的一半,则会抛出异常,所以如果数据长度太大需要分段加密。
.NET Framework提供了很多非对称加密算法。其中RSA算法最为流行。
2、RSA加密demo-1,不指定公钥私钥
byte[] data = { 5,6,8,4,10 };
using (var rsa = new RSACryptoServiceProvider())
{
byte[] encrypted = rsa.Encrypt(data, true);
byte[] decrypted = rsa.Decrypt(encrypted, true);
}
由于没有指定公钥或私钥,因此加密程序会默认使用1024位长度自动生成一个密钥对。我们可以在构造器中,以8字节为增量来指定更长的密钥。对于安全性要求较高的应用程序,应当选用2048位密钥长度:
var rsa = new RSACryptoServiceProvider(2048)
3、生成密钥对
第一次调用ToXmlString方法会强制生成一个新的密钥对,fasle仅为公钥,true位公钥和私钥。
using (var rsa = new RSACryptoServiceProvider())
{
File.WriteAllText("PublicKeyOnly.xml", rsa.ToXmlString(false));
File.WriteAllText("PublicPrivate.xml", rsa.ToXmlString(true));
}
4、RSA加密demo-2,指定公钥私钥
使用上面生成的公钥私钥进行加密解密。
byte[] data = Encoding.UTF8.GetBytes("一个短消息");
string PublicKeyOnly = File.ReadAllText("PublicKeyOnly.xml");
string PublicPrivate = File.ReadAllText("PublicPrivate.xml");
byte[] encrypted;
byte[] decrypted;
//加密
using (var rsaPublicOnly = new RSACryptoServiceProvider())
{
rsaPublicOnly.FromXmlString(PublicKeyOnly);
encrypted = rsaPublicOnly.Encrypt(data, true);
}
//解密
using (var rsaPublicPrivate = new RSACryptoServiceProvider())
{
rsaPublicPrivate.FromXmlString(PublicPrivate);
decrypted = rsaPublicPrivate.Decrypt(encrypted, true);
}
5、RSA加密demo-3,分段加密
1024位的证书,加密时最大支持117个字节,解密时为128;
2048位的证书,加密时最大支持245个字节,解密时为256。
string PublicKeyOnly = File.ReadAllText("PublicKeyOnly.xml");
string PublicPrivate = File.ReadAllText("PublicPrivate.xml");
string a = "测试中文,如果这是一个数字and一串英文字母,这个够长不够长?可能不太够长,也可能太长了?哈哈,顶顶顶缺陷检测和图像处理图像识别项目一般,好像涉及到一些线性代数的计算";
string encrypted, decrypted;
//加密
using (var rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(PublicPrivate);
byte[] bytes = Encoding.UTF8.GetBytes(a);
int encriptedChunckSize = rsa.KeySize / 8 - 11;
int dataLength = bytes.Length;
int iterations = dataLength / encriptedChunckSize + 1;
ArrayList arrayList = new ArrayList();
for (int i = 0; i < iterations; i++)
{
byte[] tempBytes = new byte[encriptedChunckSize];
System.Buffer.BlockCopy(bytes, encriptedChunckSize * i, tempBytes, 0, (tempBytes.Length* (i + 1) > bytes.Length) ? (bytes.Length - tempBytes.Length * i) : tempBytes.Length);
byte[] data1 = rsa.Encrypt(tempBytes, false);
arrayList.AddRange(data1);
}
var descrypt = (byte[])arrayList.ToArray(typeof(Byte));
encrypted = Convert.ToBase64String(descrypt);
}
using (var rsa = new RSACryptoServiceProvider())
{
//解密
//byte[] buffer = Encoding.UTF8.GetBytes(encrypted);
//string keys = Encoding.UTF8.GetString(buffer);
var bytes = Convert.FromBase64String(encrypted);
rsa.FromXmlString(PublicPrivate);
int encriptedChunckSize = rsa.KeySize / 8;
int dataLength = bytes.Length;
int iterations = dataLength / encriptedChunckSize;
ArrayList arrayList = new ArrayList();
for (int i = 0; i < iterations; i++)
{
byte[] tempBytes = new byte[encriptedChunckSize];
System.Buffer.BlockCopy(bytes, encriptedChunckSize * i, tempBytes, 0, (tempBytes.Length * (i + 1) > bytes.Length) ? (bytes.Length - tempBytes.Length * i) : tempBytes.Length);
arrayList.AddRange(rsa.Decrypt(tempBytes, false));
}
var descrypt = (byte[])arrayList.ToArray(typeof(Byte));
decrypted = Encoding.UTF8.GetString(descrypt).Trim();
}
其它代码参考
6、数字签名
公钥算法可以对消息或文档进行数字签名。签名与散列值类似,但是这个散列值会用私钥进行加密从而防止伪造,而公钥则用来验证这个数字签名。例如:
要制作数字签名首先要对数据进行散列,而后用非对称加密算法加密散列值。由于散列值的长度固定且不大,因此即使是大型的文档也可以很快地进行数字签名(公钥加密比散列算法的计算量大得多)。如果需要,还可以手动对数据进行散列,而后调用SignHash而不是SignData:
SignHash方法需要提供散列算法的信息。CryptoConfig.MapNameToOID方法可以以一个用户友好的名字(例如“SHA1”)来查询该信息。
RSACryptoServiceProvider将创建长度和密钥匹配的签名。目前,主流的算法生成的签名长度都不会少于128字节(例如,生成产品激活码)。
为了更加高效地添加数字签名,接收者必须了解并信任发送者的公钥。这可以通过事前通信、预先配置或保存网站证书来实现。网站证书包含发送者的公钥和名称,并且它本身会由独立受信的机构进行签名。System.Security.Cryptography.X509Certificates命名空间中的类型可用来处理证书相关的操作。