Communicator 类的代码分析(CNG 示例)

Communicator.cs 文件封装了下一代加密技术 (CNG) 安全通信示例的加密和解密方法。该文件仅包含一个名为 Communicator 的类。该类包含下面几节讨论的成员和方法:

  • 类成员

  • 类构造函数

  • Dispose 方法

  • StoreDSKey 方法

  • Send_or_Receive_PublicCryptoKey 方法

  • SendMessage 方法

  • ReceiveMessage 方法

有关本主题提到的版本的示例和说明的概述,请参见下一代加密技术 (CNG) 安全通信示例

类成员

Communicator 类包含下列私有成员:

  • CngKey m_DSKey

    Communicator.StoreDSKey 使用该成员存储一个数字签名密钥。

  • ECDiffieHellmanCng m_ECDH_Cng

    构造函数使用该成员存储 ECDiffieHellmanCng 类的一个实例。ReceiveMessage 和 SendMessage 方法使用该成员派生密钥材料。

  • string m_ECDH_local_publicKey_XML

    构造函数使用该成员存储本地椭圆曲线 Diffie-Hellman (ECDH) 加密公钥的一个 XML 字符串表示形式。Alice、Bob 和 Mallory 使用 Send_or_Receive_PublicCryptoKey 方法交换此 XML 字符串。

  • ECDiffieHellmanPublicKey m_ECDH_remote_publicKey

    Send_or_Receive_PublicCryptoKey 方法使用该成员存储远程 ECDH 加密公钥。

Communicator 类还提供了下列公共成员:

  • ChannelManager ChMgr

    Alice、Bob 和 Mallory 使用该成员提供命名管道服务。

类构造函数

public Communicator(string mode, string ChannelName)

构造函数创建下面三个对象:

  • 具有随机密钥对的一个 ECDiffieHellmanCng 类实例,使用 521 位的密钥大小:

    m_ECDH_Cng = new ECDiffieHellmanCng(521)
    

    最后生成的对象作为私有成员绑定到 Communicator 类。每个 Communicator 对象创建该类的一个实例。

    Alice 和 Bob 各创建一个 Communicator 对象,并且能够分别访问一个 m_ECDH_Cng 成员。Mallory 创建两个 Communicator 对象:一个用于 Alice,另一个用于 Bob。因此,Mallory 能够访问两个 m_ECDH_Cng 成员。

  • ECDH 公钥作为一个私有 XML 字符串:

    m_ECDH_XmlString = m_ECDH_CryptoKey.ToXmlString(ECKeyXmlFormat.Rfc4050)
    

    ECDiffieHellmanCng.ToXmlString 方法使用 ECKeyXmlFormat.Rfc4050 格式序列化 ECDH 公钥。

    在示例的版本 2 至 5 中,Alice 使用以下代码语句在其 Run 方法中向 Bob 发送最后生成的 XML 字符串:

    Alice.Send_or_Receive_PublicCryptoKey("send", MyColor);
    
  • ChannelManager 类的一个公共实例:

    ChMgr = new ChannelManager(mode, ChannelName)
    

    构造函数接受下面两个参数:

    • mode:指示如何创建命名管道的字符串。此参数可以为“server”或“client”。

    • ChannelName:提供名称来标识新命名管道的字符串。

Dispose 方法

public void Dispose()

Communicator 类继承 IDisposable 接口,并提供用于立即释放敏感资源的 Dispose 方法。这包括 m_ECDH_Cng、m_ECDH_local_publicKey_XML 和 ChMgr 对象。

每个对象都是在一个 C# using 语句内创建的,以确保当对象超出范围时立即被释放。

StoreDSKey 方法

public void StoreDSKey(byte[] DSKeyBlob)

此方法接受字节数组中包含的一个二进制大对象 (BLOB)。它使用 CngKey.Import(array<Byte[], CngKeyBlobFormat) 方法从密钥 BLOB 中提取一个数字签名密钥。然后通过以下代码存储该密钥:

m_DSKey = CngKey.Import(DSKeyBlob,CngKeyBlobFormat.Pkcs8PrivateBlob);

Alice、Bob 和 Mallory 根据以下版本,在各自的 Run 方法中调用 StoreDSKey 方法:

  • 在版本 3 至 5 中,Alice、Bob 和 Mallory 使用该方法存储同一个公共传输的密钥。

  • 在版本 4 和 5 中,Alice 和 Bob 第二次调用该方法,并使用私自传输的密钥覆盖 m_DSKey 成员。

Send_or_Receive_PublicCryptoKey 方法

Send_or_Receive_PublicCryptoKey(string mode, int color)

此方法接受两个参数:

  • mode:一个字符串,指示是创建一个管道服务器发送密钥,还是创建一个管道客户端接收密钥。此参数可以为“server”或“client”。

  • color:一个整数,指定用于显示密钥的颜色。

Send_or_Receive_PublicCryptoKey 方法与 CommunicatorReceiveMessage 和 SendMessage 方法类似,不同的是它发送或接收未加密的加密密钥,而不是加密消息。ReceiveMessage 和 SendMessage 将尝试对密钥进行加密和解密,因此不能将这两种方法用于加密密钥。

交换密钥之后,示例的版本 3 至 5 验证密钥的数字签名。版本 3 使用一个通过 PublicChannel 命名管道传输的数字签名。该签名被 Mallory 截获并用来签署他发送给 Alice 和 Bob 的替换密钥和消息。由于 Alice、Bob 和 Mallory 使用同一数字签名密钥,因此版本 3 总是能成功进行签名验证。

备注

版本 4 和 5 使用专用数字签名来签署密钥和消息,并显示安全警告。这两个版本只给了 Alice 和 Bob。因此,Mallory 不知道他的截获行为已经被检测到了。

SendMessage 方法

public bool SendMessage(string plainTextMessage, bool fShowMsg)

此方法由 Alice、Bob 和 Mallory 从各自的 Run 方法中调用。此方法按以下步骤加密、数字签名并发送消息:

  1. 如果 fShowMsg 值为 true,则显示纯文本消息。

  2. 使用以下代码将消息转换为 Unicode 字节数组:

    byte[] UnicodeText = Encoding.Unicode.GetBytes(plainTextMessage);
    
  3. 决定是否应该以纯文本形式发送消息。如果示例运行的是版本 1,则 SendMessage 在使用以下代码发送消息后返回。

    ChMgr.SendMessage(UnicodeText);
    
  4. 通过调用 ECDiffieHellmanCng.DeriveKeyMaterial(ECDiffieHellmanPublicKey) 方法派生密钥材料:

    byte[] aesKey = m_ECDH_Cng.DeriveKeyMaterial(m_ECDH_remote_publicKey)
    
  5. 创建一个临时的 Aes 对象:

    Aes aes = new AesCryptoServiceProvider()
    
  6. 使用在步骤 4 中派生的密钥材料初始化 Aes 对象:

    aes.Key = aesKey;
    
  7. 创建一个临时的 MemoryStream 对象来存放一个加密字符串。

  8. 创建一个临时的 CryptoStream 对象,使用该对象加密消息并将其写入 MemoryStream 对象。

  9. 保存密码文本和初始化向量:

    iv = aes.IV
    ciphertext = ms.ToArray();
    
  10. 如果示例运行的是版本 3、4 或 5,则按如下所述签署密码文本:

    • 创建一个临时的 ECDsaCng.ECDsaCng(CngKey) 对象:

      ECDsaCng ecdsa = new ECDsaCng(m_DSKey)
      
    • 将该对象的 HashAlgorithm 属性初始化为 Sha512 安全哈希算法:

      ecdsa.HashAlgorithm = CngAlgorithm.Sha512
      
    • 通过签署密码文本创建一个数字签名:

      signature = ecdsa.SignData(ciphertext);
      
  11. 如下所述准备输出消息:

    • 创建一个三字节的数组来存放消息的初始化向量、密码文本和签名的长度。

    • 创建四个 System.Collections.Generic.List<T> 对象来存放前一步骤中的长度数组、初始化向量、密码文本和签名。

    • 连接这四个 System.Collections.Generic.List<T> 对象将其转换成一个输出消息字节数组:

      byte[] message = list1.ToArray();
      
  12. 发送输出消息:

    ChMgr.SendMessage(message)
    

ReceiveMessage 方法

public string ReceiveMessage()

此方法由 Alice、Bob 和 Mallory 从各自的 Run 方法中调用。它接收和解密消息,并验证其数字签名。

消息不作为参数传递给此方法。Communicator 类的 ChannelManager 成员通过使用下面的代码读取消息:

byteBuffer = ChMgr.ReadMessage();

ReceiveMessage 方法执行下列步骤:

  1. 决定是否以纯文本形式发送消息。如果示例运行的是版本 1,则消息采用纯文本形式,并且不需要解密。在本例中,消息在使用以下代码转换成一个 ASCII 字符串后返回:

    AsciiMessage = Encoding.Unicode.GetString(byteBuffer);
    
  2. 将消息分为多个组成部分。在版本 2 至 5 中将加密消息。在本例中,消息分为三个单独的字节数组。第一个数组包含初始化向量,第二个数组包含密码文本,第三个数组包含密码文本的数字签名。

  3. 如果设置了 fVerbose 标志,则以 ASCII 文本形式显示初始化向量、密码文本和签名。

  4. 派生密钥材料。Communicator 类的私有 ECDiffieHellmanCng 成员 m_ECDH_Cng 使用 ECDiffieHellmanCng.DeriveKeyMaterial 方法来派生共享密钥材料,如下面的代码所示:

    byte[] aesKey = m_ECDH_Cng.DeriveKeyMaterial(m_ECDH_remote_publicKey);
    
  5. 通过实例化一个 AesCryptoServiceProvider 对象来创建一个 Aes 对象:

    Aes aes = new AesCryptoServiceProvider()
    
  6. 使用从前面的步骤中派生的密钥材料和初始化向量来初始化 Aes 对象。

  7. 使用 System.IO.MemoryStreamSystem.Security.Cryptography.CryptoStream 对象来解密消息。

  8. 显示解密后的消息。

  9. 在版本 3 至 5 中使用数字签名验证消息的密码文本。由于 Alice、Bob 和 Mallory 使用同一签名,因此版本 3 不显示安全警告。如果消息的密码文本具有无效签名,版本 4 将显示安全警告。不过,只为 Alice 和 Bob 提供了版本 4,因此 Mallory 永远不会看到任何警告。在版本 5 中,具有无效签名的加密密钥会导致程序终止,因此不会有机会传输并验证任何消息。

请参见

概念

下一代加密技术 (CNG) 安全通信示例

Communicator.cs 源代码(CNG 示例)

源代码概述(CNG 示例)

分步执行密钥和消息交换(CNG 示例)

预期的输出(CNG 示例)