源代码概述(CNG 示例)

本概述给出了下一代加密技术 (CNG) 安全通信示例中各代码元素之间的交互的深入说明。

它讨论了该示例的以下方面:

  • 应用程序

  • 文件

  • 安全版本

  • 会话概述

  • 加密密钥(版本 2-5)

  • 通过公共通道交换的数字签名(版本 3)

  • 通过专用通道交换的数字签名(版本 4)

  • 会话终止(版本 5)

  • 其他说明

  • CNG 示例的限制

应用程序

此示例由三个独立的控制台应用程序组成,它们分别是:Alice、Bob 和 Mallory。每个应用程序都是作为一个单独的 Visual Studio 项目生成的,由一个主文件和三个共享文件组成。下表显示了这些应用程序及其文件。

应用程序(项目)名称

文件

Alice

  • Alice.cs(主文件)

  • Utilities.cs

  • ChannelManager.cs

  • Communicator.cs

Bob

  • Bob.cs(主文件)

  • Utilities.cs

  • ChannelManager.cs

  • Communicator.cs

Mallory

  • Mallory.cs(主文件)

  • Utilities.cs

  • ChannelManager.cs

  • Communicator.cs

文件

以下各部分中的表对每个应用程序使用的类和方法按照它们在源代码中的出现顺序进行了概要说明。

Alice.cs、Bob.cs 和 Mallory.cs

类、方法或

全局变量名

请使用

CNG_SecureCommunicationExample

项目范围的分部类。

MyColor

OtherColor

fDisplaySendMessage

全局变量。

Main

每个应用程序的程序入口点。此方法负责以下事项:

  • 当由 Alice 调用时,将自动加载 Bob.exe 和 Mallory.exe。

  • 创建处理循环。

  • 调用 InitConsole 方法来设置控制台大小、位置和标题。

  • 调用 Run 方法。

Run

调用 InitializeOptions 方法并创建选定的安全方案的方法。

Utilities.cs

类、方法或

全局变量名

请使用

CNG_SecureCommunicationExample

项目范围的分部类。

Version

fVerbose

fMallory

全局变量。

Autoloader

由 Alice 调用来加载 Bob.exe 和 Mallory.exe 应用程序的方法。

InitConsole

处理用户界面菜单和应用程序级消息的方法。

SplashScreen

提供控制台窗口标题的方法。

ReadALine

从控制台读取用户输入的一行内容的实用工具方法。

ReadAChar

显示一个问题并提示用户选择肯定或否定答案的实用工具方法。

InitializeOptions

显示多个选项并提示用户进行选择的方法。此外,该方法还设置 Version、fVerbose 和 fMallory 全局标志。

Display(string s)

两个 Display 方法中的第一个。该方法将字符串和 MyColor 变量传递给第二个 Display 方法。

Display(string DisplayString, int color)

两个 Display 方法中的第二个。该方法包装 Console.WriteLine 语句并为输出行提供颜色支持。

有关这些类、方法和变量的更多信息,请参见实用工具类的代码分析(CNG 示例)

ChannelManager.cs

类、方法或

全局变量名

用途

CNG_SecureCommunicationExample

项目范围的分部类。

AppControl

提供内部应用程序控制并同步这三个控制台应用程序的方法。

Alice 使用此方法将程序选项(Version 和 fVerbose 标志)发送给 Bob 和 Mallory。

AppControl 不是消息处理方法。其内容未经过加密或签名。它不调用 Communicator 类。

SendChannelName

ReceiveChannelName

用于从 PublicChannel 命名管道切换到 AliceAndBobChannel 和 AliceAndBobChannel1 命名管道的方法。这些方法支持 CNG 示例概述中讨论的有意制造的安全漏洞。

ChannelManager

一个类,用于为应用程序提供进程间通信框架。

有关这些类和方法的更多信息,请参见 ChannelManager 类的代码分析(CNG 示例)

Communicator.cs

类、方法或

全局变量名

用途

CNG_SecureCommunicationExample

项目范围的分部类。

Communicator

一个类,用于封装所有加密功能。该类处理 Alice、Bob 和 Mallory 之间的所有消息。它不处理 ChannelManagerAppControl 方法发送的消息。

m_DSKey

m_ECDH_Cng

m_ECDH_local_publicKey_XML

m_ECDH_remote_publicKey

ChMgr

类变量。

Communicator

构造 Communicator 对象的方法。

Dispose

释放专有资源的方法。

StoreDSKey

存储数字签名密钥的方法。

Send_or_Receive_PublicCryptoKey

提供密钥交换支持的方法。

iv

ciphertext

signature

用于加密纯文本消息的私有类变量。这些变量是在 ReceiveMessage() 方法附近声明的。

ReceiveMessage

接收纯文本或加密消息(具体取决于安全版本)的方法。

SendMessage

接收纯文本消息,再将其以纯文本或加密格式(具体取决于安全版本)发送出去的方法。

有关这些类、方法和变量的更多信息,请参见 Communicator 类的代码分析(CNG 示例)

每个项目都包含三个类:

  • public partial class CNG_SecureCommunicationExample

    该类在 Alice、Bob 和 Mallory 应用程序中包括的全部四个项目文件之间延伸。编译完成后,CNG_SecureCommunicationExample 类将包含这四个项目文件的所有类、变量和方法。有关分部类的更多信息,请参见分部类和方法(C# 编程指南)

  • internal sealed class ChannelManager

    该类提供命名管道支持。每个项目都会在程序执行期间的不同时间创建多个 ChannelManager 实例。该类的详细信息可以在 ChannelManager 类的代码分析(CNG 示例)中找到。

  • internal sealed class Communicator

    该类提供加密和解密支持。每个项目都会在程序执行期间的不同时间创建多个 Communicator 实例。该类的详细信息可以在 Communicator 类的代码分析(CNG 示例)中找到。

安全版本

源代码支持 CNG 示例概述中给出的安全方案。它实现以下五个版本,表示即时消息 (IM) 工具的五个安全级别:

  • 版本 1:使用纯文本消息和命名管道。

  • 版本 2:使用加密消息。

  • 版本 3:使用加密消息和数字签名。

  • 版本 4:使用加密消息和私有数字签名。

  • 版本 5:发生安全错误时终止应用程序。

备注

本主题的余下部分通过数字来引用这些版本。此外,“Alice”、“Bob”和“Mallory”这三个名称可能是指本示例场景中涉及的三个人,也可能是指三个 Visual Studio 应用程序,具体取决于上下文。

会话概述

Alice、Bob 和 Mallory 各有一个 Main 方法和一个 Run 方法。

Main 方法同步这些应用程序并执行以下功能:

  • 显示启动初始屏幕。

  • 要求用户选择会话选项(仅限 Alice)。

  • 将会话选项发送给 Bob 和 Mallory(仅限 Alice)。

  • 从 Alice 接收会话选项(仅限 Bob 和 Mallory)。

  • 调用 Run 方法来执行请求的安全会话。

Run 方法执行安全方案。

  • 每个会话阐释上一部分列出的一个版本。

  • 会话在 Alice、Bob 和 Mallory 进入其 Run 方法时开始,在 Alice、Bob 和 Mallory 返回其 Main 方法时结束。

  • Alice、Bob 和 Mallory 在会话期间总是运行相同的版本。

  • Alice 启动在会话期间发生的所有事务。Mallory 响应 Alice 并启动与 Bob 的事务。Bob 只是做出响应。

Alice 和 Bob 的源代码非常相似。二者的主要区别是 Alice 启动每个会话,并用作管道服务器,而 Bob 用作管道客户端。Mallory 的代码更复杂一些,因为他管理两个单独的管道:一个用于 Alice,另一个用于 Bob。

Main 方法

Alice 在其 Main 方法开始时调用 InitializeOptions 方法,并接收来自用户的会话选项(Version、fVerbose、fMallory)。她使用自己的 AppControl 方法将这些选项发送给 Bob 和 Mallory,Bob 和 Mallory 使用其各自的 AppControl 方法接收这些选项。如果用户决定键入字母“x”来关闭应用程序,则 Alice 的 AppControl 方法会将字符串“exit”而不是会话选项发送给 Bob 和 Mallory。

三个应用程序在接收到会话选项后,将调用其各自的 Run 方法。

Run 方法

Alice、Bob 和 Mallory 通过执行以下步骤来执行请求的会话:

  1. Alice 调用 SendChannelName 方法,该方法使用一个名为 PublicChannel 的通道。她发送给 Bob 一个新通道名称 (AliceAndBobChannel)。

  2. 如果 fMallory 标志设置为 true,则 Mallory 将侦听 PublicChannel 并截获来自 Alice 的新通道名称 (AliceAndBobChannel)。然后他会发送给 Bob 一个不同的通道名称 (AliceAndBobChannel1)。

  3. Alice 和 Bob 创建 Communicator 对象,这些对象按 Alice 和 Bob 命名。这些对象是在 C# using 语句中创建的,且在 Run 方法的末尾释放。

    Alice 初始化为一个管道服务器:

    using (Communicator Alice = new Communicator("server", NewChannelName))
    

    Bob 初始化为一个管道客户端:

    using (Communicator Bob = new Communicator("client", NewChannelName))
    

    Mallory 创建两个 Communicator 对象:MalloryAlice 和 MalloryBob。对于 Alice,Mallory 初始化为一个管道客户端:

    using (Communicator MalloryAlice = new Communicator("client", AliceChannelName))
    

    对于 Bob,Mallory 初始化为一个管道服务器:

    using (Communicator MalloryBob = new Communicator("server", BobChannelName"))
    
  4. Communicator 类构造函数接受通道名称并创建一个名为 ChMgr 的长期公共 ChannelManager 对象。

    ChMgr = new ChannelManager(mode, ChannelName);
    
  5. ChannelManager 构造函数接受通道名称并创建一个相应的命名管道。

    备注

    此时,Alice 和 Mallory 通过一个名为 AliceAndBobChannel 的管道进行通信,而 Mallory 和 Bob 通过一个名为 AliceAndBobChannel1 的管道进行通信。

    AliceAndBobChannel 和 AliceAndBobChannel1 是长期通道,它们在安全方案结束(即 Run 方法结束)之前一直保持开放状态。

  6. fVersion 标志(控制安全版本)和 fMallory 标志(控制是否有 Mallory 参与)的值决定了接下来发生的情况:

    在版本 3 中,Alice 通过 PublicChannel 将一个数字签名密钥发送给 Bob。它们使用此数字签名密钥来对密钥和消息进行签名。如果 fMallory 为 true,则 Mallory 将截获此数字签名密钥,并用它来对他发送给 Alice 和 Bob 的密钥和消息进行签名。

    在版本 4 和 5 中,Alice 发送两个数字签名密钥给 Bob。数字签名密钥 1 与 Alice 在版本 3 中发送的密钥一样。数字签名密钥 2 是一个秘密数字签名密钥,Mallory 不知道该私钥。

  7. 对于版本 2 到 5,Alice 和 Bob 交换加密公钥。如果 fMallory 为 true,Mallory 将截获他们的公钥并替换上自己的公钥。

  8. Alice 和 Bob 交换消息。如果 fMallory 为 true,则 Mallory 将截获、更改并重新发送 Alice 和 Bob 的消息。

  9. 在 Alice 和 Bob 完成对话后,将提示您发送自己的消息。这样您就可以查看自己的消息是如何加密的以及 Mallory 执行了哪些操作来更改这些消息。完成上述操作后,请按 Enter 将控制权交回给 Alice。

  10. Alice 结束此会话。Alice、Bob 和 Mallory 的 Run 方法将控制权返回给其各自的 Main 方法,然后示例重新开始。

加密密钥(版本 2-5)

版本 2 到 5 使用高级加密标准 (AES) 算法来加密消息。加密密钥交换是在 Alice、Bob 和 Mallory 的 Run 方法中下面这一代码语句后面实现的:

if (2 <= Version)

加密密钥是由长期公共 ChannelManager 对象 (ChMgr) 发送的,该对象由 Communicator 类构造函数创建。

下面两个代码语句演示 Alice 如何发送加密密钥以及 Bob 如何接收此密钥:

Alice.Send_or_Receive_PublicCryptoKey("send", MyColor);
Bob.Send_or_Receive_PublicCryptoKey("receive", OtherColor);

第二个参数定义接收方应用程序在显示加密密钥的内容时应使用的颜色。

人们认为通过数学方式破坏 AES 实现是行不通的。但 AES 并不提供任何防护来抵御中间人攻击。AES 提供如此强大的加密功能,而 Mallory 却可以解密 Alice 和 Bob 的消息,这似乎有些矛盾,但这种情况是有可能的,因为 Mallory 拥有 Alice 和 Bob 的共享机密协议。Mallory 所拥有的密钥截获和替代功能使得强大的 AES 加密变得无效。

在无身份验证的情况下使用加密密钥获得的是虚假的安全感。Alice 和 Bob 都认为自己拥有安全的消息传输方案(版本 2)。而事实上,在他们发送自己的第一条消息之前,安全性就已受到损害。

Alice 和 Bob 所在的公司都不知道攻击来自公司内部还是外部。他们设计第 3 版 IM 工具以找到攻击的来源。

通过公共通道交换的数字签名(版本 3)

版本 3 使用数字签名来签署密钥和消息,从而修复版本 2 中的安全缺陷。数字签名密钥交换是在 Alice、Bob 和 Mallory 的 Run 方法中下面这一代码语句后面实现的:

if (3 <= Version)

数字签名密钥与加密密钥是通过同一长期公共通道发送的。下面的代码负责发送数字签名密钥:

Alice.ChMgr.SendMessage(dsKeyBlob);

备注

发送数字签名密钥的 ChannelManager 实例 (ChMgr) 是 Alice Communicator 对象的一个成员。

Alice 和 Bob 将数字签名密钥作为其各自 Communicator 对象的私有成员来存储:

Alice.StoreDSKey(DSKey);
Bob.StoreDSKey(DSKey);

遗憾的是,Mallory 可以轻松地从 PublicChannel 复制该数字签名并保存它:

Mallory.StoreDSKey(DSKey);

当 Alice 和 Bob 接收来自对方的签名消息时,数字签名与消息完全匹配。这是因为 Mallory 签署这些消息时使用的是与 Alice 和 Bob 相同的数字签名密钥。

在版本 3 中,加密密钥和数字签名都是通过企业网络上的一个公共通道来交换的。Alice 和 Bob 所在的公司怀疑公司内部有人在窃取数据,因此创建版本 4 来测试这一想法。

通过专用通道交换的数字签名(版本 4)

版本 4 使用两个数字签名密钥:一个是版本 3 中使用的数字签名密钥,另一个是通过专用通道发送的密钥。现在将第一个密钥视为假数字密钥,用于诱骗窃取者。第二个密钥供 Alice 和 Bob 用来对其各自的加密密钥和消息进行数字签名。

第 4 版 IM 软件只提供给 Alice 和 Bob。Mallory 继续使用版本 3。因此,他绝不会发现自己使用的数字签名无效。但 Alice 和 Bob 的 IM 工具对他们接收的每个密钥和消息都会显示一个安全警报。

版本 4 还证明加密密钥和消息都被截获。这表明正在发生中间人攻击,而且此攻击在发送加密密钥之前即已开始。因此,一定有某个对企业 PublicChannel 具有访问权限的公司员工在 Bob 之前登录。数字签名密钥的巧妙应用暴露了 Mallory 的秘密活动。

数字签名密钥传输是在 Alice 和 Bob 的 Run 方法中下面这一代码语句后面实现的:

if (4 <= Version)

下面的代码语句负责创建 Alice 和 Bob 的私有 ChannelManager 实例:

ChannelManager ChMgr2 = new ChannelManager("server", "PrivateChannel")
ChannelManager ChMgr2 = new ChannelManager("client", "PrivateChannel")

Alice 和 Bob 使用的专用通道 (ChMgr2) 不是其各自 Communicator 对象的成员,通过比较下面两个代码语句即可看出这一点:

Alice.ChMgr.SendMessage(dsKeyBlob); // Public channel - fake key
ChMgr2.SendMessage(dsKeyBlob);      // Private channel - real key

第一个语句使用在 Run 方法开始时创建的长期 ChannelManager 实例 (ChMgr)。此实例作为 Alice、Bob 和 Mallory 的 Communicator 对象的一个公共成员而存在(见会话概述一节中的步骤 3),直到会话结束。第二个语句使用一个临时对象,该对象仅存在发送和接收密钥所需的足够长时间,用完后会立即被释放。

Alice 发送数字签名密钥后,Bob 使用下面的语句来接收它:

DSKey = ChMgr2.ReadMessage();

Alice 和 Bob 将此密钥作为私有成员存储在其各自的 Communicator 对象中:

Alice.StoreDSKey(DSKey);
Bob.StoreDSKey(DSKey);

这些语句还会覆盖假数字签名密钥。

在版本 4 中,Alice 和 Bob 使用数字签名密钥而不是假数字签名密钥来签署密钥和消息。版本 4 还对加密密钥进行签名,而且只要消息的数字签名与消息不匹配,就会显示安全警告。

会话终止(版本 5)

版本 5 在第一次出现错误时就结束会话,除此之外,它与版本 4 完全相同。Alice 第一次遇到错误是在接收 Bob 的加密公钥时,她发现了一个无效的数字签名。Bob 第一次遇到错误是在接收 Alice 的加密公钥时,他也发现了一个无效的数字签名。

其他说明

  • 对象释放:C# using 语句提供更强的安全性。它们用于封装所有 ChannelManager 和 Communicator 对象。如果这些对象超出范围,将立即调用它们的 Dispose 方法,并释放所有内部持有的资源。

  • 编码方法:只要传输加密数据,就应将其编码为 UTF8Unicode,而永远不要将其编码为 ASCII

CNG 示例的限制

CNG 安全通信示例的用途是演示托管的 CNG 函数,因此某些功能未被提及,其中包括:

  • 所有方法中的参数验证。

  • try/catch 块的广泛使用。

  • 可靠的管道断开连接发现功能。

  • 将屏幕输出记录到文件中。

  • 加密算法的动态可配置性。

  • 数字签名算法的动态可配置性。

  • 将数字签名密钥传输给 Bob 的另一种方法。命名管道 PrivateChannel 是一个简单的解决方案,此外还存在其他更复杂的方法。

  • 密钥保持、存储和检索。

  • 使用操作系统生成的数字签名密钥。

  • 使用公钥基础结构 (PKI) 提供的密钥。

这些功能涉及到额外的代码复杂性,超出了本示例的范围。

请参见

概念

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

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

预期的输出(CNG 示例)