非对称加密的类型之间的关系如图所示。
加密模式
加密模式只有一种实现,即RSACryptoServiceProvider,采用的是RSA算法。DSACryptoServiceProvider只能进行认证模式,即数字签名,不能进行加密模式。
下面便以RSACryptoServiceProvider为例,来说明加密模式的实现过程。
不管是对称加密还是非对称加密,密钥都是关键。
在对称加密中,密钥可以是开发者自行设定的字符串。
对于非对称加密来说,根据算法的不同密钥的格式也不相同,并且会复杂很多。因此密钥通常是算法自动生成的,而不是由开发者来创建。
在创建RSACryptoServiceProvider类型的实例时,会自动创建一个公/私密钥对。
可以在实例上调用ToXmlString()方法来获得,ToXmlString()返回的结果不仅包含了密钥,还包含了一些其他用于优化算法执行效率的信息。
这个方法接受一个bool类型的参数:
当该参数值为true时,返回的字符串中将包含公钥和私钥;
当该参数值为false时,仅包含公钥信息。
因此,可以通过下面的语句来获得公/私密钥对,或者仅获取公钥:
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
string publicPrivate = provider.ToXmlString(true); // 获得公/私密钥对
string publicOnly = provider.ToXmlString(false); // 只获得公钥
得到的结果类似于下面,需要注意每次执行上面的代码,得到的结果都不相同:
<!--包含公钥和私钥的XML -->
<RSAKeyValue>
<Modulus>2hERzSJlfyX0aKuBwTgZYDUyX+kP7yOBrwPfNhWL9E/Ykq0eevDm+fqQO4M0ax+ISPkBgFEQUvWCeUS33DCmpgN0HrvJwr68RQB33LjoaNGPvJ9RyW5UquPPXc8gGiBMVAJjXIdRf0Y4abJoazsYDaeH4G5H4x2Ys+Hqqm+h3pE=</Modulus>
<Exponent>AQAB</Exponent>
<P>5I8eCXeqxj5UuPBV2Kbt2nYQfR4deCp7pvmjIT6YeMSkv46cKvNM6pk1NMyHslrhmZjskbR/eSW1xhZYy3H1hw==</P>
<Q>9D92rDXFPw7y93E5qtzWsRt1801h8T8ykS8J0fy/gwlmAaM5lywZ0+PyFNeFW6rjTak688qfvUlgQBkNPiyRJw==</Q>
<DP>SdR5TXGcdqFX2M25zVxO5QzSUrhRqKmAe/WT3n9L3WcYGNDGXZFuPTH0X/PZuaFl0qn1cTOvIcEusKgzUrSjLQ==</DP>
<DQ>UQl9ZkWw2+sp0c9PQtFiqgBicgcKp/A/5sukhndFU0SbA5AUW4PWTecjOqcHKBLat7meRaTEuxjNRncJXceLoQ==</DQ>
<InverseQ>qpKLZdqeM314Jc4ue7xaTN+U4iRA/DflzhinLS+WrXRKVkvbXxy+9+5kKPTsfkvE7sisQNhm/dpzs3LfxCI60A==</InverseQ>
<D>JfVys9KY+FkTAmVYYNnzENwxuKBJNcdoe56g7Dkz84Myn9WiyKPGkR0cnj9okH0crBcsO7ngrZAu9g0QNDQDze/egPgvVfcRaubV+vqgWjgU2DmxvJC+lt0KVx7v4xuXIAlJJbyNy9dUsWqrQ/l0hVVyzY035WIHzILDRlS7oEE=</D>
</RSAKeyValue>
<!--仅包含公钥的XML -->
<RSAKeyValue>
<Modulus>2hERzSJlfyX0aKuBwTgZYDUyX+kP7yOBrwPfNhWL9E/Ykq0eevDm+fqQO4M0ax+ISPkBgFEQUvWCeUS33DCmpgN0HrvJwr68RQB33LjoaNGPvJ9RyW5UquPPXc8gGiBMVAJjXIdRf0Y4abJoazsYDaeH4G5H4x2Ys+Hqqm+h3pE=</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
XML节点的数据是用Base64String字符串表示的byte[]字节数组,通过ExportParameters()方法直接获得字节数组。ExportParameters()方法参数的意义与ToXmlString()方法相同:
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
RSAParameters pPublicPrivate = provider.ExportParameters(true); // 获得公/私密钥对
RSAParameters pPublicOnly = provider.ExportParameters(false); // 只获得公钥
RSAParameters类型的属性对应XML中的节点,例如DQ、D、P等,它们的值与私钥相关,可以视为私钥由这些值组合而成。作为开发者而言,并不需要关心这些内容,可以简单地将其视为公/私密钥对。
在首次创建了公/私密钥对以后,就可以将公钥公开,将私钥保存。
在发送方发送消息前,使用接收方的公钥进行加密;
在接收方收到消息后,使用私钥进行解密。
可以创建一个RSACryptoHelper帮助类,其中包含以下两个方法:
public class RSACryptoHelper
{
// 发送方公钥加密
public static string Encrypt(string publicKeyXml, string plainText)
{
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.FromXmlString(publicKeyXml); // 使用公钥初始化对象
byte[] plainData = Encoding.UTF8.GetBytes(plainText);
byte[] encryptedData = provider.Encrypt(plainData, true);
return Convert.ToBase64String(encryptedData);
}
// 接收方私钥解密
public static string Decrypt(string privateKeyXml, string encryptedText)
{
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.FromXmlString(privateKeyXml); // 使用公/私钥对初始化对象
byte[] encryptedData = Convert.FromBase64String(encryptedText);
byte[] plainData = provider.Decrypt(encryptedData, true);
string plainText = Encoding.UTF8.GetString(plainData);
return plainText;
}
}
最后,进行以下测试:
string plainText = "Hello, world!";
string publicKey = "...";
string encryptedText = RSACryptoHelper.Encrypt(publicKey, plainText);
Console.WriteLine(encryptedText);
string privateKey = "...";
string clearText = RSACryptoHelper.Decrypt(privateKey, encryptedText);
Console.WriteLine(clearText);
最后运行的结果如下:
数字签名
数字签名可以说是极大地优化了认证模式,并且实现起来也不会增加太多的复杂度,因此应用得更加广泛。在.NET中可以使用RSACryptoServiceProvider或者DSACryptoServiceProvider来完成数字签名。
下面继续以RSACryptoServiceProvider为例来说明这一过程。
在RSACryptoServiceProvider类型中,有一对方法SignData()和VerifyData():
SignData()用于运算原文的摘要,并对摘要进行数字签名,最后返回签名后的摘要;
VerifyData()用于重新运算消息,得出本地摘要,并解密传递进来的原始摘要,最后对本地摘要和原始摘要进行对比,并返回bool型的结果。
我们可以扩展前面的RSACryptoHelper帮助类,添加下面两个方法:
//私钥签名
public static string SignData(string plainText, string privateKeyXml)
{
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.FromXmlString(privateKeyXml);
byte[] plainData = Encoding.UTF8.GetBytes(plainText);
// 设置获取摘要的算法
HashAlgorithm sha1 = HashAlgorithm.Create("SHA1");
// 获得签名过的摘要
byte[] signedDigest = provider.SignData(plainData, sha1);
return Convert.ToBase64String(signedDigest);
}
//公钥验证
public static bool VerifyData(string plainText, string signature, string publicKeyXml)
{
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.FromXmlString(publicKeyXml);
byte[] plainData = Encoding.UTF8.GetBytes(plainText);
byte[] signedDigest = Convert.FromBase64String(signature);
HashAlgorithm sha1 = HashAlgorithm.Create("SHA1");
bool isDataIntact = provider.VerifyData(plainData, sha1, signedDigest);
return isDataIntact;
}
接下来,可以对上面的方法进行以下测试:
// 发送方
string plainText = "Hello, readers";
string privateKey = "<RSAKeyValue>...</RSAKeyValue>";
string signedDigest = RSACryptoHelper.SignData(plainText, privateKey);
Console.WriteLine(signedDigest);
// 接收方
string publicKey = "<RSAKeyValue>...</RSAKeyValue>";
bool isCorrect = RSACryptoHelper.VerifyData(plainText, signedDigest, publicKey);
Console.WriteLine(isCorrect);
如果上面的publicKey和privateKey是匹配的,则返回true。如果接收方对消息plainText进行了修改,或者使用了错误的公钥,VerifyData()则会返回false.
SignData()和VerifyData()方法执行了太多的操作,大家可能理解得不够清楚。在RSACryptoServiceProvider中还有一对方法SignHash()和VerifyHash(),只针对摘要进行操作,相当于将上面的过程进行了拆分。
下面的两个方法SignData2()、VerifyData2()作用等同于前面的SignData()和VerifyData(),从简洁性来说差一些,但从步骤上来说更为清晰,便于大家理解。
//私钥签名
public static string SignData2(string plainText, string privateKeyXml)
{
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.FromXmlString(privateKeyXml);
byte[] plainData = Encoding.UTF8.GetBytes(plainText);
// 设置获取摘要的算法
HashAlgorithm sha1 = HashAlgorithm.Create("SHA1");
// 获得原始摘要
byte[] digestData = sha1.ComputeHash(plainData);
// 对原始摘要签名
byte[] signedDigest = provider.SignHash(digestData, "SHA1");
return Convert.ToBase64String(signedDigest);
}
//公钥验证
public static bool VerifyData2(string plainText, string signedDigest, string publicKeyXml)
{
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.FromXmlString(publicKeyXml);
byte[] plainData = Encoding.UTF8.GetBytes(plainText);
byte[] signedDigestData = Convert.FromBase64String(signedDigest);
// 获得本地摘要
HashAlgorithm sha1 = HashAlgorithm.Create("SHA1");
byte[] digest = sha1.ComputeHash(plainData);
// 解密签名,并判断摘要是否一致
bool isDataIntact = provider.VerifyHash(digest, "SHA1", signedDigestData);
return isDataIntact;
}
执行结果与前面是一样的。
全文回顾:
非对称加密的类型之间的关系
加密模式
自动创建一个公/私密钥对
数字签名
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有