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 方法中调用。此方法按以下步骤加密、数字签名并发送消息:
如果 fShowMsg 值为 true,则显示纯文本消息。
使用以下代码将消息转换为 Unicode 字节数组:
byte[] UnicodeText = Encoding.Unicode.GetBytes(plainTextMessage);
决定是否应该以纯文本形式发送消息。如果示例运行的是版本 1,则 SendMessage 在使用以下代码发送消息后返回。
ChMgr.SendMessage(UnicodeText);
通过调用 ECDiffieHellmanCng.DeriveKeyMaterial(ECDiffieHellmanPublicKey) 方法派生密钥材料:
byte[] aesKey = m_ECDH_Cng.DeriveKeyMaterial(m_ECDH_remote_publicKey)
创建一个临时的 Aes 对象:
Aes aes = new AesCryptoServiceProvider()
使用在步骤 4 中派生的密钥材料初始化 Aes 对象:
aes.Key = aesKey;
创建一个临时的 MemoryStream 对象来存放一个加密字符串。
创建一个临时的 CryptoStream 对象,使用该对象加密消息并将其写入 MemoryStream 对象。
保存密码文本和初始化向量:
iv = aes.IV ciphertext = ms.ToArray();
如果示例运行的是版本 3、4 或 5,则按如下所述签署密码文本:
创建一个临时的 ECDsaCng.ECDsaCng(CngKey) 对象:
ECDsaCng ecdsa = new ECDsaCng(m_DSKey)
将该对象的 HashAlgorithm 属性初始化为 Sha512 安全哈希算法:
ecdsa.HashAlgorithm = CngAlgorithm.Sha512
通过签署密码文本创建一个数字签名:
signature = ecdsa.SignData(ciphertext);
如下所述准备输出消息:
创建一个三字节的数组来存放消息的初始化向量、密码文本和签名的长度。
创建四个 System.Collections.Generic.List<T> 对象来存放前一步骤中的长度数组、初始化向量、密码文本和签名。
连接这四个 System.Collections.Generic.List<T> 对象将其转换成一个输出消息字节数组:
byte[] message = list1.ToArray();
发送输出消息:
ChMgr.SendMessage(message)
ReceiveMessage 方法
public string ReceiveMessage()
此方法由 Alice、Bob 和 Mallory 从各自的 Run 方法中调用。它接收和解密消息,并验证其数字签名。
消息不作为参数传递给此方法。Communicator 类的 ChannelManager 成员通过使用下面的代码读取消息:
byteBuffer = ChMgr.ReadMessage();
ReceiveMessage 方法执行下列步骤:
决定是否以纯文本形式发送消息。如果示例运行的是版本 1,则消息采用纯文本形式,并且不需要解密。在本例中,消息在使用以下代码转换成一个 ASCII 字符串后返回:
AsciiMessage = Encoding.Unicode.GetString(byteBuffer);
将消息分为多个组成部分。在版本 2 至 5 中将加密消息。在本例中,消息分为三个单独的字节数组。第一个数组包含初始化向量,第二个数组包含密码文本,第三个数组包含密码文本的数字签名。
如果设置了 fVerbose 标志,则以 ASCII 文本形式显示初始化向量、密码文本和签名。
派生密钥材料。Communicator 类的私有 ECDiffieHellmanCng 成员 m_ECDH_Cng 使用 ECDiffieHellmanCng.DeriveKeyMaterial 方法来派生共享密钥材料,如下面的代码所示:
byte[] aesKey = m_ECDH_Cng.DeriveKeyMaterial(m_ECDH_remote_publicKey);
通过实例化一个 AesCryptoServiceProvider 对象来创建一个 Aes 对象:
Aes aes = new AesCryptoServiceProvider()
使用从前面的步骤中派生的密钥材料和初始化向量来初始化 Aes 对象。
使用 System.IO.MemoryStream 和 System.Security.Cryptography.CryptoStream 对象来解密消息。
显示解密后的消息。
在版本 3 至 5 中使用数字签名验证消息的密码文本。由于 Alice、Bob 和 Mallory 使用同一签名,因此版本 3 不显示安全警告。如果消息的密码文本具有无效签名,版本 4 将显示安全警告。不过,只为 Alice 和 Bob 提供了版本 4,因此 Mallory 永远不会看到任何警告。在版本 5 中,具有无效签名的加密密钥会导致程序终止,因此不会有机会传输并验证任何消息。