前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >非对称加密

非对称加密

作者头像
小蜜蜂
发布2019-07-24 17:10:54
8180
发布2019-07-24 17:10:54
举报
文章被收录于专栏:明丰随笔明丰随笔

非对称加密的类型之间的关系如图所示。

加密模式

加密模式只有一种实现,即RSACryptoServiceProvider,采用的是RSA算法。DSACryptoServiceProvider只能进行认证模式,即数字签名,不能进行加密模式。

下面便以RSACryptoServiceProvider为例,来说明加密模式的实现过程。

不管是对称加密还是非对称加密,密钥都是关键。

在对称加密中,密钥可以是开发者自行设定的字符串。

对于非对称加密来说,根据算法的不同密钥的格式也不相同,并且会复杂很多。因此密钥通常是算法自动生成的,而不是由开发者来创建。

在创建RSACryptoServiceProvider类型的实例时,会自动创建一个公/私密钥对。

可以在实例上调用ToXmlString()方法来获得,ToXmlString()返回的结果不仅包含了密钥,还包含了一些其他用于优化算法执行效率的信息。

这个方法接受一个bool类型的参数:

当该参数值为true时,返回的字符串中将包含公钥和私钥;

当该参数值为false时,仅包含公钥信息。

因此,可以通过下面的语句来获得公/私密钥对,或者仅获取公钥:

代码语言:javascript
复制
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
string publicPrivate = provider.ToXmlString(true); // 获得公/私密钥对
string publicOnly = provider.ToXmlString(false); // 只获得公钥

得到的结果类似于下面,需要注意每次执行上面的代码,得到的结果都不相同:

代码语言:javascript
复制
<!--包含公钥和私钥的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()方法相同:

代码语言:javascript
复制
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
RSAParameters pPublicPrivate = provider.ExportParameters(true); // 获得公/私密钥对
RSAParameters pPublicOnly = provider.ExportParameters(false); // 只获得公钥

RSAParameters类型的属性对应XML中的节点,例如DQ、D、P等,它们的值与私钥相关,可以视为私钥由这些值组合而成。作为开发者而言,并不需要关心这些内容,可以简单地将其视为公/私密钥对。

在首次创建了公/私密钥对以后,就可以将公钥公开,将私钥保存。

在发送方发送消息前,使用接收方的公钥进行加密;

在接收方收到消息后,使用私钥进行解密。

可以创建一个RSACryptoHelper帮助类,其中包含以下两个方法:

代码语言:javascript
复制
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;
  }
}

最后,进行以下测试:

代码语言:javascript
复制
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帮助类,添加下面两个方法:

代码语言:javascript
复制
//私钥签名
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;
}

接下来,可以对上面的方法进行以下测试:

代码语言:javascript
复制
// 发送方
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(),从简洁性来说差一些,但从步骤上来说更为清晰,便于大家理解。

代码语言:javascript
复制
//私钥签名
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;
}

执行结果与前面是一样的。

全文回顾:

非对称加密的类型之间的关系

加密模式

自动创建一个公/私密钥对

数字签名

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 明丰随笔 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档