六、安全编程指南
介绍
当软件应用有漏洞时,对应用开发者来说意味着高成本。一些组织为每个安全事件支付超过 50 万美元。用于消除软件应用中的漏洞的工作应该集中在安全编程上,避免在生产阶段部署任何漏洞。
编写安全的源代码是一项艰巨的任务。理解正在编写的代码的含义并有一个需要检查的东西的清单是非常重要的。该清单将帮助开发人员针对众所周知的安全问题快速验证他们的代码。通常,验证是由安全团队完成的,而不是软件开发人员或工程师。软件开发人员不能客观地对待他们自己的代码。
清单的想法应该从以下想法开始:验证将处理其域外数据的源代码,并考虑用户输入、网络通信、二进制文件的处理、从数据库管理系统或服务器接收输出等等。
当我们使用一个软件应用时(不管这个应用是桌面应用、web 应用还是移动应用),认为这个应用是安全的,因为它是由一个知名公司开发的,这只是一个神话。不要相信这种想法。依赖这种想法的公司最终会在安全事故、维护、咨询和审计会议上花费大量的预算。
软件应用有两种工作环境,在每种环境中它的行为是不同的。在一个公司中处于分析和开发过程中的软件应用代表了它的信任圈(至少,大多数公司都是这样想的,并且他们乐于认为他们的基础设施非常能够抵御安全攻击)。软件应用在信任圈中的行为代表了开发和测试应用的最关键环境。没有一个开发人员、IT 安全官员或软件分析师会黑掉自己的代码。为此,一些公司,如微软,曾经有软件开发测试工程师(sdet),他们的任务是破解或试图破解由软件开发工程师(SDE)编写的代码。这个环境是舒适的。一旦应用离开舒适区,进入真实环境,问题就开始出现了。信任边界很难同时也很容易被划定,并且在舒适区和真实区之间创建一个界限。这不是一项容易完成的任务,尤其是如果这些应用运行在虚拟化基础架构、云或大数据环境中。
在舒适区中,恶意的最终用户代表安全威胁。恶意终端用户将攻击软件应用的机密性和/或完整性。其中一个有趣的方法和概念是软件混淆 。
安全编程清单
在本节中,我们将讨论并提出一个安全的编码清单(也可以将其视为一个过程)。表 6-1 显示了这种清单的一个例子,它可以根据你的需要进行开发。该清单包含了用 C# 编写代码时可以检查的项目的最小示例,无论它是在 Windows 还是 Linux 操作系统上。开发人员经常使用的一种做法是隐藏警告,这是没有好处的。
表 6-1
安全编程清单示例
|编号#
|
要检查的项目
|
描述
|
是/否
|
笔记
|
| — | — | — | — | — |
| one | 编译器警告 | | |
| 确保 GCC 编译器将输出,并为接收针对下列项目列出的潜在错误的通知设置了一个标志。对于更多的标志及其定义和操作,建议遵循编译器选项来请求或抑制警告部分 [1 ]。如果要实现复杂的加密算法和安全方案,这将非常有用。 | | |
| Two | 处理字符串时分配足够的内存作为缓冲内存。 | | |
| 检查以下函数,查看当复制过程完成直到满足 NULL 时,目标缓冲区是否有上限。为了避免这种情况,建议在将数据复制到目标缓冲区之前,为其分配足够的内存空间。 | | |
| three | 检查系统安全的直接破坏。 | | |
| 检查不可信的输入会直接破坏应用的安全性。通过这一步,您将保护应用免受恶意用户和攻击者使用元字符试图利用程序的攻击。 | | |
| four | 检查参数大小是否错误并得到意外结果。 | | |
| 当编写复杂的程序时,比如第二章 2 中清单 2-9 中 SHA-256 的实现,给一个参数分配错误的大小或者进行错误的算术运算会导致严重的缺陷,应该立即提供修复。确保在目标端为参数分配的大小相同。作为最佳实践,尤其是在实现加密算法时,最好使用类型的返回大小。保持类型安全,不要造成溢出。 | | |
| five | 检查是否分配了过多的内存。 | | |
| 分配过多的内存和外部参数代表了一定部分的大小。这意味着处理一个错误的内存分配,你将经历拒绝服务的结果。为了避免这种情况发生,最好遵循以下标准。 | | |
| six | 避免投错。 | | |
| 避免像下面这样编码。编译器会认为内存分配会返回一个int
,这是完全不正确的。它会产生一个漏洞,很容易被黑客利用。 | | |
| seven | 避免变量参数表。 | | |
| 当您实现基于字符串的安全方案时,您可能会遇到一种新类型的问题,安全分析师或有道德的黑客在执行测试时喜欢玩这种问题。有道德的黑客通常用来检查不可信数据的一个简单测试是检查函数是否允许变量作为参数或自变量的列表,如Console.WriteLine()
或MessageBox.Show()
。不可信数据(由有道德的黑客创建)直接用作字符串格式,而不是参数。对于任何类似的情况,请遵循以下逻辑。 | | |
| eight | 同文件操作 | | |
| 在加密操作期间处理文件时,使用mkstemp().
| | |
| nine | 文件权限 | | |
| 不是每个人都有能力读写文件。为了避免给文件分配错误的权限,要养成使用FileIOPermission
类的习惯。 | | |
| Ten | 避免使用代码访问安全(CAS) | | |
| CAS 使用主程序运行过程中可以利用的资源。这种资源的例子是 XML 文件、数据库、图像、设置和配置文件等。因为不建议使用 CAS,所以它在中不受支持。网芯, .NET 5 或更高版本。一般来说,高于 7.0 的 C# 版本不支持 CAS。 | | |
| Eleven | 避免使用部分可信的代码。 | | |
| 这些程序集代表了应用中的一个脆弱点,它们很容易被利用和覆盖,以便访问函数和方法的核心以及主代码。 | | |
| Twelve | 避免使用 AllowPartiallyTrustedCaller
属性。 | | |
| 从……开始 .NET Framework 4 中,新的安全规则影响了AllowPartiallyTrustedCallersAttribute
属性的行为。从……开始 .NET Core,不再支持部分受信任的代码。建议使用. NET 4 库,并坚持使用 .NET 4 安全模型,并在需要的地方使用适当的SecurityCritical, SecuritySafeCritical
和Security Transparent
属性。 | | |
| Thirteen | 避免使用 .NET 远程处理。 | | |
| .NET Remoting 是一种技术,它使得一个被称为远程对象的对象可以跨不同的远程边界使用,这些边界基于不同的应用域(AppDomain)、进程或在网络内或通过网络连接的不同计算机。由于这一事实,进程或计算机(网络)可被利用,对象(远程对象)可被覆盖,以便恶意访问应用和应用的资源。这种情况出现在 [21 ]中。 | | |
| 14 | 避免使用 DCOM(分布式组件对象模型)。 | | |
| DCOM 代表一种编程结构,它允许计算机通过网络在另一台计算机上执行程序,就像程序在本地运行一样。通过中间人攻击,攻击者可以控制通信信道,并攻击应用与其计算机之间的通信。 | | |
| 15 | 避免使用二进制格式化程序。 | | |
| BinaryFormatter
类型从 .NET Framework 是非常危险的,在数据处理完成时不推荐使用。应用应该立即停止使用BinaryFormatter
,即使开发人员认为他们正在处理的数据是可信的。的确,BinaryFormatter
是不安全的,但某些方面可以变得安全。不过还是建议完全避免 [20 ]。 | | |
在后面的部分中,我们将讨论应用于加密算法开发过程的最重要的规则。每条规则在指南中都有很好的解释。
证书编码标准
CERT 编码标准只为 ISO/IEC 14882-2014 标准定义的 C++编程语言版本开发,但是一些编码标准可以成功地用于其他编程语言,如 C#、Java 或 Python。
编码标准组织得非常好,它遵循以下结构:标识符、不符合的代码示例和符合的解决方案、异常、风险评估、自动检测、相关漏洞和相关指南 [7 ]。
在接下来的部分中,我们将检查结构的每一项,并解释主要目标和目的。
标识符
每个标识符有三个部分:
-
三个字母的助记符,表示标准中的部分。
-
介于 00 和 99 之间的两位数字的数值。
-
与之关联的语言,用后缀(-CPP,-C,-J,-PL)表示。
-
–CPP:SEI CERT c# 编码标准 [7
-
–C:SEI CERT C 编码标准 [8
-
–J:SEI CERT Oracle Java 编码标准 [9 ]
-
–PL:SEI CERT Perl 编码标准 [10 ]
-
三个字母的助记符用于对相关编码实践进行分组,并指出相关编码属于哪个类别。
不兼容的代码示例和兼容的解决方案
不合规代码的例子显示了违反准则的代码。记住这些只是例子,这一点非常重要。示例中所有外观的移除过程并不意味着我们正在分析的代码符合 SEI CERT 标准。
规则的例外
例外具有信息性,不要求遵循。任何规则都可以有一组例外情况,这些例外情况详细说明了在哪些情况下不必遵循指南来确保软件的安全性、可靠性。
与任何类型的异常一样,编程语言并不重要,原理是一样的。有必要格外注意异常,捕捉任何可能的异常,并从中吸取教训。不要忽视他们。不要认为一种编程语言是完美的,它没有任何漏洞或某些可被利用的门。
风险评估
对于 CERT C++编码标准中的每个指南,都有一个指定的风险评估部分。风险评估部分的目的是向软件开发人员提供不遵守或不处理特定规则或建议的潜在后果。风险评估看起来像一个指标,其主要目的是帮助软件应用和复杂项目的补救过程。
每个规则和建议都有一个*优先级。*为了分配一个优先级,建议了解一下 IEC 60812 [11 ]。使用一个以三种分析类型为特征的指标来评估和分配优先级:故障模式、影响和关键程度。每个规则还将有一个值,该值被分配在 1 到 3 之间,例如严重性、可能性和补救成本(参见表 6-2 )。
表 6-2
为每个规则 [7 ]赋值
|严重性–如果忽略该规则,会有什么后果?
|
| — |
|
价值
|
意义
|
不同漏洞的示例
|
| — | — | — |
| one | 低的 | 拒绝服务攻击,意外终止 |
| Two | 中等 | 违反数据完整性,无意泄露信息 |
| three | 高的 | 运行随机代码 |
| **可能性–**从统计角度来说,通过避免和忽略规则规范而在代码中引入缺陷,从而导致可能被恶意用户利用的漏洞的可能性有多大? |
| 值 | 定义 |
| one | 不太可能的 |
| Two | 很可能的事 |
| three | 可能的 |
| **补救成本–**遵守规则的成本是多少? |
| 值 | 定义 | 检测 | 校正 |
| one | 高的 | 指南 | 指南 |
| Two | 中等 | 自动的 | 指南 |
| three | 低的 | 自动的 | 自动的 |
对于每条规则,这些值会相乘。表 6-3 中的指标为您提供了一种方法,可用于确定应用中规则的优先级。值从 1 到 27。在所有 27 个值中,只有 10 个不同的值出现,并且在大多数情况下是可用的:1、2、3、4、6、8、9、12、18 和 27。表 6-3 显示了优先级和级别的可能解释和含义。
表 6-3
级别和优先级 [7
|水平
|
优先
|
可能的解释
|
| — | — | — |
| 腰神经 2 | 12, 18, 27 | 严重程度高,可能修复成本低 |
| L2 | 6, 8, 9 | 中等严重性,便携式,中等修复成本 |
| L3 | 1, 2, 3, 4 | 严重性低,不太可能,维修费用高 |
自动检测
规则和建议包含描述自动检测过程的部分。上述部分提供了多套工具,可用作自动诊断违规的分析器。安全编程验证套件 [12 ]可用于测试分析仪提供违反 ISO/IEC TS 17961:2013[14]规定的规则的诊断信息的能力,这与 SEI CERT C 编码标准 [13 ]的规则相关。
相关指南
根据标准,当开发软件应用时,这个部分有一个特殊的槽。它还包含链接、技术规范和指南集,如*“信息技术-编程语言、其环境和系统软件接口-C 安全编程规则*[14*”;信息技术-编程语言-通过语言选择和使用避免编程语言漏洞的指南*[15*];MISRA C# 2008:在关键系统中使用 C# 语言的指南*[16*];*和 *CWE IDs 在米特里的常见弱点枚举(CWE) * [17 *]”*18
规则
在接下来的几节中,我们将给出一个主要规则的简要概述,这些规则非常适用于使用 C# 实现加密算法和安全方案。尤其是有了新版本的 it,最好了解以下规则。请注意,我们将只研究 10 条规则中的 6 条。指南 [19 ]中提供了所有解释和示例。
对于某些规则,也有一些来自 C 编程语言的规则适用于 C#。以下规则可用于表 6-1 中说明的程序。
任何信息安全官、安全分析师、道德黑客等的职责。就是通过设计这样一个清单来改进代码。此外,开发人员在开发关键的加密算法时,也可以使用该清单作为指南。建议对算法中非常脆弱的部分进行代码审查,并确保尽可能遵守规则(规则 01、规则 02、规则 03、规则 05、规则 06 和规则 07)。
遵循这些规则将使您作为安全分析师或有道德的黑客对安全机制(加密算法、安全协议、安全方案和其他加密原语)的正确实施以及常见漏洞的消除有一定程度的信任。参见表 6-4 至 6-9 。
表 6-9
规则 07–输入/输出 [19
|规则
|
在 C# 中应用
|
标题
|
| — | — | — |
| 导线 50-CPP | Y | 在没有插入定位调用的情况下,不要交替输入和输出文件流。 |
| 导线 51-CPP | Y | 不再需要文件时将其关闭。 |
| 电线 30-C | Y | 从格式字符串中排除用户输入。 |
| 32-C 导线 | Y | 不要在仅适用于文件的设备上执行操作。 |
| 34-C 导线 | Y | 区分从文件中读取的字符和 EOF 或 WEOF。 |
| 线 38-C | 普通 | 不要复制文件对象。 |
| 电线 39-C | Y | 在没有插入刷新或定位调用的情况下,不要从流中交替输入和输出。 |
| 42-C 导线 | Y | 不再需要文件时将其关闭。 |
| 电线 44-C | 普通 | 仅使用从fgetpos()
返回的fsetpos()
值。 |
| 导线 45-C | 普通 | 访问文件时避免 TOCTOU 竞争条件。 |
| 导线 46-C | Y | 不要访问关闭的文件。 |
| 导线 47-C | Y | 使用有效的格式字符串。 |
表 6-8
规则 06–内存管理 [19
|规则
|
在 C# 中应用
|
标题
|
| — | — | — |
| MEM50-CPP | Y | 不要访问释放的内存。 |
| MEM51-CPP | Y | 正确释放动态分配的资源。 |
| MEM52-CPP | Y | 检测和处理内存分配错误。 |
| MEM53-CPP | Y | 手动管理对象生存期时,显式构造和析构对象。 |
| MEM54-CPP | Y | 为新的放置提供正确对齐的指针,以提供足够的存储容量。 |
| MEM55-CPP | Y | 遵守替换动态存储管理要求。 |
| MEM56-CPP | Y | 不要将已经拥有的指针值存储在不相关的智能指针中。 |
| MEM57-CPP | Y | 避免对过度对齐的类型使用默认运算符new
。 |
| MEM30-C | Y | 不要访问释放的内存。 |
| MEM31-C | Y | 不再需要时释放动态分配的内存。 |
| MEM34-C | Y | 仅动态分配空闲内存。 |
| MEM35-C | Y | 为对象分配足够的内存。 |
| MEM36-C | 普通 | 不要通过调用realloc()
来修改对象的对齐。 |
表 6-7
规则 05–字符和字符串 [19
|规则
|
在 C# 中应用
|
标题
|
| — | — | — |
| STR50-CPP | Y | 确保字符串存储有足够的空间来存储字符数据和空终止符。 |
| STR52-CPP | 普通 | 使用有效的引用、指针和迭代器来引用basic_string
的元素。 |
| STR53-CPP | Y | 范围检查元素访问。 |
| STR30-C | Y | 不要试图修改字符串文字。 |
| STR31-C | Y | 确保字符串存储有足够的空间来存储字符数据和空终止符。 |
| STR32-C | Y | 不要将非空字符序列传递给需要字符串的库函数。 |
| STR34-C | Y | 在转换为更大的整数之前,将字符转换为无符号字符。 |
| STR37-C | Y | 字符处理函数的参数必须可以表示为无符号字符。 |
| STR38-C | Y | 不要混淆窄和宽的字符串和函数。 |
表 6-6
规则 03–整数 [19
|规则
|
在 C# 中应用
|
标题
|
| — | — | — |
| INT50-CPP | Y | 不要强制转换为超出范围的枚举值。 |
| INT30-C | Y | 确保无符号整数运算不换行。 |
| INT31-C | Y | 确保整数转换不会导致数据丢失或被误解。 |
| INT32-C | Y | 确保对有符号整数的操作不会导致溢出。 |
| INT33-C | Y | 确保除法和余数运算不会导致被零除的错误。 |
| INT34-C | Y | 不要将表达式移位负数位数或大于或等于操作数中存在的位数。 |
| INT35-C | Y | 不要用不匹配的语言链接调用函数。 |
表 6-5
规则 02–表达式 [19
|规则
|
在 C# 中应用
|
标题
|
| — | — | — |
| EXP50-CPP | Y | 不要依赖于副作用的评估顺序。 |
| EXP51-CPP | Y | 不要通过不正确类型的指针删除数组。 |
| EXP52-CPP | Y | 不要依赖未赋值操作数的副作用。 |
| EXP53-CPP | Y | 不要读取未初始化的内存。 |
| EXP54-CPP | Y | 不要访问超出其生存期的对象。 |
| EXP56-CPP | Y | 不要用不匹配的语言链接调用函数。 |
| EXP57-CPP | Y | 不要强制转换或删除指向不完整类的指针。 |
| EXP60-CPP | Y | 不要跨越执行边界传递非标准布局类型的对象。 |
| EXP61-CPP | Y | lambda 对象不能比它的任何引用捕获对象活得长。 |
| EXP62-CPP | Y | 不要访问对象表示中不属于对象值表示的位。 |
| EXP63-CPP | Y | 不要依赖移出对象的值。 |
表 6-4
规则 01–声明和初始化 [19
|规则
|
在 C# 中应用
|
标题
|
| — | — | — |
| DCL51-CPP | Y | 不要声明或定义保留标识符。 |
| DCL52-CPP | Y | 永远不要用 const 或 volatile 限定引用类型。 |
| DCL53-CPP | Y | 不要写语法不明确的声明。 |
| DCL54-CPP | Y | 重载分配和解除分配在同一范围内成对运行。 |
| DCL55-CPP | Y | 在跨越信任边界传递类对象时避免信息泄漏。 |
| DCL56-CPP | Y | 避免静态对象初始化期间的循环。 |
| DCL57-CPP | Y | 不要让异常从析构函数或释放函数中逃脱。 |
| DCL58-CPP | Y | 不要修改标准名称空间。 |
| DCL59-CPP | Y | 不要在头文件中定义未命名的命名空间。 |
| DCL60-CPP | Y | 遵守一个定义规则。 |
| DCL30-C | Y | 声明具有适当存储期限的对象。 |
| DCL39-C | Y | 在跨信任边界传递结构时避免信息泄漏。 |
| DCL40-C | Y | 不要创建同一函数或对象的不兼容声明。 |
规则 01。声明和初始化(DCL)
规则 02。表达式(表达式)
规则 03。整数(INT)
规则 05。字符和字符串(STR)
规则 06。内存管理(MEM)
规则 07。输入/输出
结论
在本章中,你学习了规则和建议。您踏上了在开发加密算法和安全方案的过程中需要考虑的最重要的安全方面的旅程。
理解规则和建议之间的区别非常重要。一般的想法是,与建议相比,规则必须遵循特定数量的标准,而建议代表了改进代码质量的建议。
你已经获得了大量的知识。在本章结束时,您现在能够执行源代码的安全性分析,创建安全的编码清单,筛选对您的应用至关重要的方面,并指导开发人员在实现加密算法和编写相关源代码时如何进行。
文献学
-
请求或禁止警告的 GCC 选项。网上有:
https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#Warning-Options
。 -
HXprox_*,libHX–把事情做好。网上有:
http://libhx.sourceforge.net/
。 -
[ISO/IEC TR 24772:2013] ISO/IEC。信息技术——编程语言——通过语言选择和使用避免编程语言漏洞的指南。TR 24772-2013。ISO。2013 年 3 月。
-
[ISO/IEC TS 17961:2012]ISO/IEC TS 17961。信息技术——编程语言及其环境和系统软件接口——C 安全编程规则。ISO。2012.
-
[ISO/IEC 14882-2014]ISO/IEC 14882-2014。编程语言— C#,第四版。 2014 年。
-
鲍尔曼,2016 年。 SEI CERT C# 编码标准(2016 版) 435。
-
SEI CERT C# 编码标准:网上有:
https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=88046682
(4 . 9 . 20 访问)。 -
编码标准。在线可用:
https://wiki.sei.cmu.edu/confluence/display/c
(4 . 9 . 20 访问)。 -
Oracle Java 编码标准。在线可用:
https://wiki.sei.cmu.edu/confluence/display/java
(4 . 9 . 20 访问)。 -
CERT Perl 编码标准。在线可用:
https://wiki.sei.cmu.edu/confluence/display/perl
(4 . 9 . 20 访问)。 -
[IEC 60812 2006]国际电工委员会。系统可靠性分析技术——故障模式和影响分析程序(FMEA) ,第二版。(IEC 60812)。瑞士日内瓦:国际电工委员会,2006 年。
-
安全编程验证套件。网上有:
https://github.com/SEI-CERT/scvs
。 -
SEI CERT C 编码标准:开发安全、可靠、可靠系统的规则(2016 版),n.d. 534。
-
[ISO/IEC TS 17961:2012]ISO/IEC TS 17961。信息技术——编程语言及其环境和系统软件接口——C 安全编程规则。ISO。2012.
-
[ISO/IEC TR 24772:2013] ISO/IEC。信息技术——编程语言——通过语言选择和使用避免编程语言漏洞的指南。TR 24772-2013。ISO。2013 年 3 月。
-
[米斯拉 2008]米斯拉有限公司。 MISRA C# 2008 在关键系统中使用 C# 语言的指南。ISBN 978-906400-03-3(平装本);ISBN 978-906400-04-0 (PDF)。2008 年 6 月。
-
斜接。常见弱点列举,1.8 版。2010 年 2 月。网上有:
http://cwe.mitre.org/
。 -
"这个编码标准是如何组织的."网上有:
https://wiki.sei.cmu.edu/confluence/display/cplusplus/How+this+Coding+Standard+Is+Organized
。 -
“规则。”网上有:
https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=88046322
。 -
BinaryFormatter 类。网上有:
https://docs.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide
。 -
“寻找和开发 .NET Remoting over HTTP,使用反序列化。网上有:
www.nccgroup.com/uk/about-us/newsroom-and-events/blogs/2019/march/finding-and-exploiting-.net-remoting-over-http-using-deserialisation/
。
七、.NET 加密服务
在本章中,我们将讨论主要的服务和加密原语 .NET 框架和。提供给专业人士的核心网。了解开发技术必须提供哪些服务和加密原语非常重要,尤其是如果您不想从头开始开发加密算法和安全方案的话。
将涵盖以下主题:
-
密码原语
-
使用秘密密钥加密
-
使用公钥加密
-
数字签名
-
哈希值
-
随机数生成
-
支持套件 B
-
下一代加密技术(CHG)类别
通过互联网的通信本身并不安全,在这种情况下,需要使用加密来保证这种通信的安全性。这些网络实体之间的通信很可能被未授权的第三方读取,甚至被更改。正如您之前所看到的,加密的目的是保护数据不被未经授权的人查看或修改。在的帮助下。在. NET 框架中,我们有被设计用于System.Security.Cryptography
(见第九章)的加密类,一个处理加密函数及其操作的命名空间。此外,我们正在处理非托管 CryptoAPI(微软加密 API)的包装器。与此同时,其他的都得到了充分的实施和相应的测试。好的一面是,一旦我们创建了一个特定加密算法类的新实例,密钥就会自动生成,以便尽可能容易地使用,并使用非常安全的默认属性。
也就是说,在下面几节中,我们将简要介绍支持的最重要的加密算法 .NET Framework (ClickOnce、Suite B 和 CNG),从 .NET 框架 3.5。
使用秘密密钥加密
基于密钥的加密算法在加密和解密过程中使用单个密钥。保证密钥的安全性以防止未经授权的方或服务是非常重要的。任何有权使用该密钥的未授权方都可以使用它来解密数据或加密其他数据,声称并冒充真正的授权方。
使用秘密密钥的加密也被称为对称加密,因为相同的密钥用于两个过程:加密和解密操作(见图 7-1 )。与基于公钥的算法相比,基于秘密密钥的加密算法非常快。同时,它们最适合对大型数据流进行加密编码。另一方面,我们有基于非对称加密的算法,如 RSA,它们的数学限制是基于需要加密多少数据。
图 7-1
对称加密
那个 .NET Framework 包含以下类,用于帮助专业人员使用相同的密钥实现加密和解密操作:
图 7-2
AES 执行
-
AesManaged
[38 ](始于 .NET 框架 3.5)。参见清单 7-1 和图 7-2 中的实现。 -
RijndaelManaged
41。参见清单 7-2 中的实现。 -
DESCryptoServiceProvider
[39 。参见清单 7-3 中的实现。 -
RC2CryptoServiceProvider
40。实现与清单 7-3 中的类似。 -
TripleDESCryptoServiceProvider
42。
using System;
using System.IO;
using System.Security.Cryptography;
namespace AESExampleOfImplementation
{
class AESExampleOfImplementation
{
public static void Main()
{
string genuineMessage = "Welcome to Apress!";
//** Declare a new instance
//** of the class AESManaged.
//** With its help a new key
//** and initialization vector is generated
using (AesManaged aes_encryption =
new AesManaged())
{
//** the string is encrypted and
//** stored as an array of bytes
byte[] message_encrypted =
EncryptStringToBytes_Aes(genuineMessage,
aes_encryption.Key,
aes_encryption.IV);
//** the decryption will take place as
//** decrypting the bytes into a string
string tripRound =
DecryptStringFromBytes_Aes(
message_encrypted,
aes_encryption.Key,
aes_encryption.IV);
//** Shows in the console the original
//** message and the data decrypted
Console.WriteLine("The original message is:
{0}", genuineMessage);
Console.WriteLine("The trip round is: {0}",
tripRound);
Console.WriteLine("The encrypted message is
(byte-by-byte view): {0}", PrintByteArray(message_encrypted));
Console.WriteLine("The encrypted message is
(default view): {0}", Encoding.Default.GetString
(message_encrypted));
Console.WriteLine("The encrypted message is
(UTF8 view): {0}", Encoding.UTF8.GetString(message_encrypted));
Console.WriteLine("The encrypted message is
(UTF32 view): {0}",
Encoding.UTF32.GetString
(message_encrypted));
Console.ReadKey();
}
}
//** processing byte values to display them
static string PrintByteArray(byte[] encrypted_message)
{
var build_string =
new StringBuilder("new byte[] { ");
foreach (var each_byte in encrypted_message)
{
build_string.Append(each_byte + " ");
}
build_string.Append("}");
return build_string.ToString();
}
static byte[] EncryptStringToBytes_Aes
(string genuineText,
byte[] crypto_key,
byte[] initializationVector)
{
//** verify the arguments
if (genuineText == null ||
genuineText.Length <= 0)
throw new ArgumentNullException("genuineText");
if (crypto_key == null || crypto_key.Length <= 0)
throw new ArgumentNullException("crypto_key");
if (initializationVector == null ||
initializationVector.Length <= 0)
throw new ArgumentNullException("IV");
byte[] encryptionRepresentation;
//** declare an AesManaged instance
//** Create an AesManaged object
//** the declaration should include the specified
//** key and initialization vector.
using (Aes aes_algorithm = Aes.Create())
{
aes_algorithm.Key = crypto_key;
aes_algorithm.IV = initializationVector;
//** do the stream transformation
//** for this declare an ecnryptor
//** using ICryptoTransform
ICryptoTransform crypto_transformation =
aes_algorithm.CreateEncryptor
(aes_algorithm.Key, aes_algorithm.IV);
//** use the streams and work with the
//** encryption Create the streams
//** used for encryption
using (MemoryStream memoryStreamForEncryption
= new MemoryStream())
{
using (CryptoStream cryptoStreamEncryption
= new CryptoStream(memoryStreamForEncryption,
crypto_transformation, CryptoStreamMode.Write))
{
using (StreamWriter
streamWriterForEncryption = new
StreamWriter(cryptoStreamEncryption))
{
//** write the entire volume of
//** data with the stream
streamWriterForEncryption.
Write(genuineText);
}
encryptionRepresentation =
memoryStreamForEncryption.ToArray();
}
}
}
//** Return the encrypted bytes from
//** the memory stream.
return encryptionRepresentation;
}
static string DecryptStringToBytes_Aes
(byte[] encryptedText,
byte[] encryption_key,
byte[] initialization_vector)
{
//** verify the arguments
if (encryptedText == null ||
encryptedText.Length <= 0)
throw new
ArgumentNullException("encryptedText");
if (encryption_key == null ||
encryption_key.Length <= 0)
throw new ArgumentNullException("Key");
if (initialization_vector == null ||
initialization_vector.Length <= 0)
throw new ArgumentNullException("IV");
//** the string used to store
//** the original decrypted text
string original_text = null;
//** declare an AesManaged instance
//** using the encryption key
//** and initialization vector
using (Aes aes_algorithm = Aes.Create())
{
aes_algorithm.Key = encryption_key;
aes_algorithm.IV = initialization_vector;
//** do the stream transformation
//** for this declare an
//** encryptor using ICryptoTransform
ICryptoTransform decrypt_transformation =
aes_algorithm.CreateDecryptor(
aes_algorithm.Key,
aes_algorithm.IV);
//** use the streams and work
//** with the encryption
//** Create the streams used for encryption
using (MemoryStream memoryStreamDecryption =
new MemoryStream(encryptedText))
{
using (CryptoStream cryptoStreamDecryption
= new CryptoStream(
memoryStreamDecryption,
decrypt_transformation, CryptoStreamMode.Read))
{
using (StreamReader
streamReaderDecryption =
new StreamReader(
cryptoStreamDecryption))
{
//** read the decrypted bytes from
//** the stream reader
//** and save it in
//** original_text variable
original_text =
streamReaderDecryption.
ReadToEnd();
}
}
}
}
return original_text;
}
}
}
Listing 7-1
AES Implementation Using AesManaged
在清单 7-2 和图 7-3 中,您可以观察 Rijndael 是如何实现的。
图 7-3
RijndaelManaged 示例
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace RijndaelManagedImplementationExample
{
class RijndaelManagedImplementationExample
{
public static void Main()
{
try
{
string genuineMessage = "Folks, Welcome to Apress!";
//** declare a new instance of the
//** RijndaelManaged class with this
//** instance a new key and
//** initialization vector (IV)
using (RijndaelManaged rijndeal_crypto = new RijndaelManaged())
{
rijndeal_crypto.GenerateKey();
rijndeal_crypto.GenerateIV();
//** encrypt the message (string)
//** and store the content to an
//** array of bytes
byte[] encrypted = EncryptStringToBytes(genuineMessage, rijndeal_crypto.Key, rijndeal_crypto.IV);
//** Decrypt the bytes to a string
string tripRound = DecryptStringFromBytes(encrypted, rijndeal_crypto.Key, rijndeal_crypto.IV);
//** Display the original data
//** and the decrypted data
Console.WriteLine("Original Message:{0}", genuineMessage);
Console.WriteLine("Round Trip: {0}",tripRound);
Console.WriteLine("\nThe encrypted message is (byte - byt - byte view): {0}", PrintByteArray(encrypted));
Console.WriteLine("\nThe encrypted message is (default view): {0}", Encoding.Default.GetString(encrypted));
Console.WriteLine("\nThe encrypted message is (UTF8 view): {0}", Encoding.UTF8.GetString(encrypted));
Console.WriteLine("\nThe encrypted message is (UTF32 view): {0}", Encoding.UTF32.GetString(encrypted));
Console.ReadKey();
}
}
catch (Exception e)
{
Console.WriteLine("There is an error:{0}", e.Message);
}
}
//** processing byte values to display them
static string PrintByteArray(byte[] encrypted_message)
{
var build_string = new StringBuilder("new byte[] { ");
foreach (var each_byte in encrypted_message)
{
build_string.Append(each_byte + " ");
}
build_string.Append("}");
return build_string.ToString();
}
static byte[] EncryptStringToBytes(string genuineText, byte[] encryption_key, byte[] initialization_vector)
{
//** verify the arguments
if (genuineText == null ||
genuineText.Length <= 0)
throw new ArgumentNullException("genuineText");
if (encryption_key == null ||
encryption_key.Length <= 0)
throw new ArgumentNullException("encryption_key");
if (initialization_vector == null ||
initialization_vector.Length <= 0)
throw new ArgumentNullException("IV");
byte[] encryption_content;
//** Create an RijndaelManaged object
//** with the specified key and IV.
using (RijndaelManaged rijndaelAlgorithm = new RijndaelManaged())
{
rijndaelAlgorithm.Key = encryption_key;
rijndaelAlgorithm.IV = initialization_vector;
//** Create an encryptor to perform
//** the stream transform.
ICryptoTransform encryptorTransformation = rijndaelAlgorithm.CreateEncryptor(rijndaelAlgorithm.Key, rijndaelAlgorithm.IV);
//** Create the streams used for encryption
using (MemoryStream memoryStreamEncrypt = new MemoryStream())
{
using (CryptoStream cryptoStreamEncrypt = new CryptoStream(memoryStreamEncrypt, encryptorTransformation, CryptoStreamMode.Write))
{
using (StreamWriter streamWriterEncrypt = new StreamWriter(cryptoStreamEncrypt))
{
//** write the entire volume of
//** data to the stream
streamWriterEncrypt.Write(genuineText);
}
encryption_content = memoryStreamEncrypt.ToArray();
}
}
}
//** get the encrypted bytes
//** from the memory stream.
return encryption_content;
}
static string DecryptStringFromBytes(byte[] encrypted_text, byte[] encryption_key, byte[] initialization_vector)
{
//** verify the arguments
if (encrypted_text == null ||
encrypted_text.Length <= 0)
throw new ArgumentNullException("encrypted_text");
if (encryption_key == null ||
encryption_key.Length <= 0)
throw new ArgumentNullException("encryption_key");
if (initialization_vector == null ||
initialization_vector.Length <= 0)
throw new ArgumentNullException("initialization_vector");
//** Declare the string used to hold
//** the decrypted text.
string original_text = null;
//** Create an RijndaelManaged object
//** with the specified key and IV.
using (RijndaelManaged rijndael_algorithm = new RijndaelManaged())
{
rijndael_algorithm.Key = encryption_key;
rijndael_algorithm.IV = initialization_vector;
//** Create a decryptor to
//** perform the stream transform.
ICryptoTransform decryptionTransformation = rijndael_algorithm.CreateDecryptor(rijndael_algorithm.Key, rijndael_algorithm.IV);
//** Create the streams used for decryption.
using (MemoryStream memoryStreamDecryption = new MemoryStream(encrypted_text))
{
using (CryptoStream cryptoStreamDecrypt = new CryptoStream(memoryStreamDecryption, decryptionTransformation, CryptoStreamMode.Read))
{
using (StreamReader streamReaderDecryption = new StreamReader(cryptoStreamDecrypt))
{
//** Read the decrypted bytes
//** from the decrypting stream
//** and place them in a string.
original_text = streamReaderDecryption.ReadToEnd();
}
}
}
}
return original_text;
}
}
}
Listing 7-2
RijndaelManaged Implementation
图 7-6
存储器中的 DES 加密和解密
图 7-5
DES 加密结果
图 7-4
文件加密/解密的 DES 算法
- 在下面的例子中,我们将实现 DES 算法的两种情况。尽管这个事实被克服了,但它是一个很好的例子,说明了加密算法和原语应该如何被实际对待。这两个实现与实际中存在的其余实现的不同之处在于,第一个示例(参见图 7-4 、图 7-5 和清单 7-3 )在文件内进行加密/解密,第二个示例(参见图 7-6 和清单 7-4 )在内存中进行加密/解密。当我们处理使用文件的复杂系统时(例如 Hadoop 系统或大数据环境的 clear JSON 文件),解决方案之一是以这样的方式设计加密原语,即我们正确地对文件或内存进行加密/解密,而不暴露可能影响业务的东西。
using System;
using System.Security.Cryptography;
using System.Text;
using System.IO;
class DESSample
{
static void Main()
{
try
{
//** instance of the DES algorithm and
//** with help of Create() method
//** an initialization vector is generated
DES des_algorithm = DES.Create();
//** declare a string for being encrypted
string plaintext = "Welcome to Apress. Enjoy reading.";
string file_name = "encrypted_file.txt";
//** do the encryption to the file based on
//** the file name, key, and initialization vector
DesEncryptionToFile(plaintext, file_name, des_algorithm.Key, des_algorithm.IV);
//** Decrypt the text from a file using the file name, key, and IV.
string ciphertext = DesDecryptionFromFile(file_name, des_algorithm.Key, des_algorithm.IV);
//** Show in the console the results
Console.WriteLine("The message for encryption is: {0}", plaintext);
Console.WriteLine("The message has been encrypted with success.");
Console.WriteLine("\tCheck your file \"{0}\" at the following location: {1}", file_name, Path.GetFullPath("encrypted_file.txt"));
Console.ReadKey();
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
}
}
public static void DesEncryptionToFile(String text_to_encrypt, String file_name, byte[] encryption_key, byte[] initialization_vector)
{
try
{
//** create the file or open the file if exist
FileStream file_stream = File.Open(file_name, FileMode.OpenOrCreate);
//** declare a DES object
DES des_algorithm = DES.Create();
//** use the key and initialization vector.
//** pass them to the CryptoStream together with the file stream
CryptoStream crypto_stream = new CryptoStream(file_stream,
des_algorithm.CreateEncryptor(encryption_key, initialization_vector),
CryptoStreamMode.Write);
//** based on a crypto_stream create an instance of stream writer
StreamWriter stream_writer = new StreamWriter(crypto_stream);
//** take data and write it within the stream writer
stream_writer.WriteLine(text_to_encrypt);
//** make sure that the file stream, crypto stream and stream writer are closed
stream_writer.Close();
crypto_stream.Close();
file_stream.Close();
}
catch (CryptographicException exception){
Console.WriteLine("There is an error regarding your encryption cryptographic process: {0}", exception.Message);
}
catch (UnauthorizedAccessException exception){
Console.WriteLine("There is an error regarding creating/accessing the file: {0}", exception.Message);
}
}
public static string DesDecryptionFromFile(String file_name, byte[] decryption_key, byte[] initialization_vector)
{
try
{
//** create the file or open the file if exist
FileStream file_stream = File.Open(file_name, FileMode.OpenOrCreate);
//** declare a DES object
DES des_algorithm = DES.Create();
//** use the key and initialization vector.
//** pass them to the CryptoStream together with the file stream
CryptoStream crypto_stream = new CryptoStream(file_stream,
des_algorithm.CreateDecryptor(decryption_key, initialization_vector),
CryptoStreamMode.Read);
//** based on a crypto_stream create an instance of stream reader
StreamReader stream_reader = new StreamReader(crypto_stream);
//** before decryption take place
//** we need to read the data from the stream
string val = stream_reader.ReadLine();
//** make sure that the file stream, crypto stream and stream writer are closed
stream_reader.Close();
crypto_stream.Close();
file_stream.Close();
//** return the decryption value
return val;
}
catch (CryptographicException cryptoException)
{
Console.WriteLine("There was a cryptographic error. Please see: {0}. Correct the error and try again.", cryptoException.Message);
return null;
}
catch (UnauthorizedAccessException unauthorizedException)
{
Console.WriteLine("There was an error with the file (unauthorized/existance). Please, check: {0}", unauthorizedException.Message);
return null;
}
}
}
Listing 7-3Example of Implementation of the DES Algorithm Using DESCryptoServiceProvider
using System;
using System.Security.Cryptography;
using System.Text;
using System.IO;
class DESMemoryImplementationExample
{
static void Main()
{
try
{
//** declare a DES instance and with Create()
//** generate the key and initialization vector
DES des_algorithm = DES.Create();
//** declare a variable that contains the string to encrypt
string data_to_encrypt = "Welcome to Apress. Enjoy your adventure.";
//** use a memory buffer and proceed encrypting the text
byte[] encrypted_data = EncryptionOfTextInMemory(data_to_encrypt, des_algorithm.Key, des_algorithm.IV);
//** use the buffer and proceed with decryption and obtain the plaintext
string decrypted_data = DecryptionOfTheTextFromMemory(encrypted_data, des_algorithm.Key, des_algorithm.IV);
//** show in the console the encrypted and decrypted values
Console.WriteLine("The original message is: {0}", data_to_encrypt);
Console.WriteLine("\nThe encrypted message is (byte-by-byte view): {0}", PrintByteArray(encrypted_data));
Console.WriteLine("\nThe encrypted message is (default view): {0}", Encoding.Default.GetString(encrypted_data));
Console.WriteLine("\nThe encrypted message is (UTF8 view): {0}", Encoding.UTF8.GetString(encrypted_data));
Console.WriteLine("\nThe encrypted message is (UTF32 view): {0}", Encoding.UTF32.GetString(encrypted_data));
Console.WriteLine("\nThe original text is: {0}.", decrypted_data);
Console.ReadKey();
}
catch (Exception general_exception)
{
Console.WriteLine(general_exception.Message);
}
}
static string PrintByteArray(byte[] encrypted_message)
{
var build_string = new StringBuilder("new byte[] { ");
foreach (var each_byte in encrypted_message)
{
build_string.Append(each_byte + " ");
}
build_string.Append("}");
return build_string.ToString();
}
public static byte[] EncryptionOfTextInMemory(string data_to_encrypt, byte[] key_for_encryption, byte[] initialization_vector)
{
try
{
//** declare an instance for the memory buffer
MemoryStream memory_stream = new MemoryStream();
//** declare an instance of DES
DES DESalg = DES.Create();
//** declare an crypto stream instance and use
//** the key used for encryption and the initialization vector
CryptoStream crypto_stream = new CryptoStream(memory_stream,
DESalg.CreateEncryptor(key_for_encryption, initialization_vector),
CryptoStreamMode.Write);
//** the string that has been passed will be converted to a byte array
byte[] for_encryption = new ASCIIEncoding().GetBytes(data_to_encrypt);
//** take the byte array and write it in the crypto stream
crypto_stream.Write(for_encryption, 0, for_encryption.Length);
//** don't forget to flush it
crypto_stream.FlushFinalBlock();
//** take the memory stream and write
//** it as an array of bytes and store it accordingly
byte[] stream_content_byteArray = memory_stream.ToArray();
//** make sure to have the streams closed
crypto_stream.Close();
memory_stream.Close();
//** the buffer with the encrypted content
return stream_content_byteArray;
}
catch (CryptographicException cryptoException)
{
Console.WriteLine("There was an cryptographic error expected. Please see: {0}", cryptoException.Message);
return null;
}
}
public static string DecryptionOfTheTextFromMemory(byte[] data_for_decryption, byte[] decryption_key, byte[] initialization_vector)
{
try
{
//** declare an memory stream instance used for decryption
//** and as parameter pass the encrypted data
MemoryStream memory_stream_decryption = new MemoryStream(data_for_decryption);
//** create an instance of DES object
DES DESalg = DES.Create();
//** declare an crypto stream instance based on
//** the memory stream instance and pass as
//** parameters the decryption and initialization vector
CryptoStream crypto_stream_decryption = new CryptoStream(memory_stream_decryption,
DESalg.CreateDecryptor(decryption_key, initialization_vector),
CryptoStreamMode.Read);
//** declare a buffer and we will use it
//** to store the decrypted data
byte[] from_encryption = new byte[data_for_decryption.Length];
//** proceed reading the decrypted data from the crypto stream
//** and store its content in a temporary buffer
crypto_stream_decryption.Read(from_encryption, 0, from_encryption.Length);
//** do the conversion of the buffer in a string
//** and return its value
return new ASCIIEncoding().GetString(from_encryption);
}
catch (CryptographicException cryptoException)
{
Console.WriteLine("There was an cryptographic error. Please see: {0}", cryptoException.Message);
return null;
}
}
}
Listing 7-4Implementation of DES Encryption and Decryption in Memory
使用公钥加密
公钥加密使用私钥,私钥必须以未经授权方或恶意用户无法访问的方式秘密存储。此外,它使用一个任何人都可以使用的公钥。公钥和私钥之间的关系是基于数学的。使用公钥加密的数据只能使用私钥解密。在这种情况下,已经使用私钥签名的数据可以仅使用公钥来验证。
让我们考虑两方(经典的称为 Alice 和 Bob,如图 7-7 所示)将使用所述的公钥加密:Alice 将生成一个由公钥和私钥组成的对。当 Bob 决定向 Alice 发送加密消息时,他将需要 Alice 的公钥,因此他会向 Alice 索要公钥。Alice 将通过不安全的网络通信信道向 Bob 发送她的公钥。鲍勃会用它来加密信息。Bob 将把加密的消息发送给 Alice,Alice 将使用她的私钥解密它。
图 7-7
公钥加密
以下列表显示了公钥和私钥算法之间的比较:
-
基于公钥的算法使用固定的缓冲区大小。值得一提的是,密钥和基于它的算法使用一个表示为变量的缓冲区,其大小是可变的。
-
关于数据链,公钥算法和对称密钥算法是有区别的。对称密钥算法将数据链接成流,而公钥算法没有这种能力。非对称操作和对称操作的流模型不同。
-
使用公钥的加密使用更大的空间来表示密钥。与公钥加密相比,对称密钥加密使用的空间要小得多。
-
公钥非常容易分发,因为不需要有安全机制。如有必要,有验证和检查发送者身份的机制。
-
为了创建数字签名以验证和检查发送者的身份,可以成功地使用 RSA 和 DSA 等公钥算法。
-
关于速度,如果我们与对称算法相比,公钥算法是非常慢的。它们被设计成不能加密大量数据。因此,只建议对少量数据使用公钥算法。
那个 .NET Framework 包含以下类,这些类已经包含了公钥加密算法和操作的实现:
图 7-8
RSA 示例加密
-
DSACryptoServiceProvider
[1 。实现是相似的,它们可以用与清单 7-1 、 7-2 或 7-3 相同的方式来完成。 -
RSACryptoServiceProvider
2。有关实施的示例,请参见清单 7-5 和图 7-8 。 -
ECDiffieHellman
3。实现是相似的,它们可以用与清单 7-1 、 7-2 或 7-3 相同的方式来完成。 -
ECDiffieHellmanCng
4。实现是相似的,它们可以用与清单 7-1 、 7-2 或 7-3 相同的方式来完成。 -
ECDiffieHellamnCngPublicKey
5。实现是相似的,它们可以用与清单 7-1 、 7-2 或 7-3 相同的方式来完成。 -
ECDiffieHellmanKeyDerivationFunction
6。实现是相似的,它们可以用与清单 7-1 、 7-2 或 7-3 相同的方式来完成。 -
ECDsaCng
7。有关 ECDSA(椭圆曲线数字签名算法)的实现示例,请参见清单 7-5 。
using System;
using System.ComponentModel;
using System.Security.Cryptography;
using System.Text;
class RSAExampleOfImplementation
{
static void Main()
{
try
{
//** the object will be used for
//** conversion between the bytes
//** of the array and string
UnicodeEncoding byte_converter = new UnicodeEncoding();
//** Create byte arrays to hold original,
//** encrypted, and decrypted data.
string plaintext = "Hi, Apress!";
byte[] data_to_be_encrypted = byte_converter.GetBytes(plaintext);
byte[] encrypted_data;
byte[] decrypted_data;
//** declare a new object of
//** RSACryptoServiceProvider
//** to generate the public
//** and private key data.
using (RSACryptoServiceProvider rsa_algorithm = new RSACryptoServiceProvider())
{
//** the data to be encrypted
//** will be passed
//** to EncryptionWithRSA along with the
//** informations related to public key
encrypted_data = EncryptionWithRSA(data_to_be_encrypted, rsa_algorithm.ExportParameters(false), false);
//** the data to be encrypted
//** will be passed to DecryptionWithRSA
//** along with the informations related //** to public key
decrypted_data = DecryptionWithRSA(encrypted_data, rsa_algorithm.ExportParameters(true), false);
//** shows in the console the
//** decryption of the plaintext
Console.WriteLine("The plaintext for encryption is: {0}", plaintext);
Console.WriteLine("\nPlaintext for encryption is: {0}", PrintByteArray(data_to_be_encrypted));
Console.WriteLine("\nDecrypted plaintext: {0}", byte_converter.GetString(decrypted_data));
Console.ReadKey();
}
}
catch (ArgumentNullException)
{
//** in case that something is going wrong with
//** the encryption, catch the exception
Console.WriteLine("Encryption has failed.");
}
}
static string PrintByteArray(byte[] encrypted_message)
{
var build_string = new StringBuilder("new byte[] { ");
foreach (var each_byte in encrypted_message)
{
build_string.Append(each_byte + " ");
}
build_string.Append("}");
return build_string.ToString();
}
public static byte[] EncryptionWithRSA(byte[] data_to_be_encrypted, RSAParameters rsa_key_info, bool oaep_padding)
{
try
{
byte[] encrypted_data;
//** declare a new instance of
//** RSACryptoServiceProvider.
using (RSACryptoServiceProvider rsa_csp = new RSACryptoServiceProvider())
{
//** do the import for RSA Key information
rsa_csp.ImportParameters(rsa_key_info);
//** the byte passed array
//** will be encrypted
//** and mention OAEP padding
encrypted_data = rsa_csp.Encrypt(data_to_be_encrypted, oaep_padding);
}
return encrypted_data;
}
//** in case that something is going wrong with
//** the encryption, catch the exception
catch (CryptographicException exception)
{
Console.WriteLine(exception.Message);
return null;
}
}
public static byte[] DecryptionWithRSA(
byte[] data_to_be_decrypted,
RSAParameters rsa_key_info,
bool oaep_padding)
{
try
{
byte[] decrypted_data;
//** declare a new instance of
//** RSACryptoServiceProvider.
using (RSACryptoServiceProvider rsa_csp = new RSACryptoServiceProvider())
{
//** do the import for RSA Key information
rsa_csp.ImportParameters(rsa_key_info);
//** the byte passed array will be decrypted
//** and mention OAEP padding
decrypted_data = rsa_csp.Decrypt(data_to_be_decrypted, oaep_padding);
}
return decrypted_data;
}
//** in case that something is going wrong with
//** the encryption, catch the exception
catch (CryptographicException exception)
{
Console.WriteLine(exception.ToString());
return null;
}
}
}
Listing 7-5Implementation of RSA Using RSACryptoServiceProvider
数字签名
公钥算法用于形成数字签名。数字签名的作用是验证发送者的真实身份,并提供有关数据完整性的保护。数字签名路径见图 7-9 。
图 7-9
ECDsaCng 示例
.NET Framework 为实现数字签名算法提供了以下类:
图 7-10
数字签名
-
DSACryptoServiceProvider
[8 -
RSACryptoServiceProvider
[9 -
ECDsa
10。实施细节见清单 7-6 和图 7-10 。 -
ECDsaCng [11 ]。具体实施见清单 7-6 和图 7-10 。
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
class AliceUser
{
public static void Main(string[] args)
{
BobUser bob = new BobUser();
using (ECDsaCng dsaAlgorithm = new ECDsaCng())
{
dsaAlgorithm.HashAlgorithm = CngAlgorithm.Sha256;
bob.theKey = dsaAlgorithm.Key.Export(CngKeyBlobFormat.EccPublicBlob);
byte[] dataStructure = new byte[] { 21, 5, 8, 12, 207 };
byte[] the_signature = dsaAlgorithm.SignData(dataStructure);
bob.Receive(dataStructure, the_signature);
}
}
}
public class BobUser
{
public byte[] theKey;
public void Receive(byte[] data, byte[] the_signature)
{
using (ECDsaCng ecsdKey = new ECDsaCng(CngKey.Import(theKey, CngKeyBlobFormat.EccPublicBlob)))
{
if (ecsdKey.VerifyData(data, the_signature))
Console.WriteLine("Data is good");
else
Console.WriteLine("Data is bad");
}
}
}
Listing 7-6Implementation of ECDsaCng
哈希值
哈希算法的目的是将表示为具有任意长度的二进制值映射到具有固定长度的较小二进制值。哈希值表示为代表特定数据的数值。
那个 .NET Framework 已经实现了以下类,这有助于我们快速实现哈希算法:
-
HMACSHA1
[12 -
MACTripleDES
[13 -
MD5CryptoServiceProvider
[14 -
RIPEMD160
[15 -
SHA1Managed
[16 -
SHA256Managed
[17 -
SHA384Managed
[18 -
SHA512Managed
[19
实现的例子,我们来看看RIPEMD160
(见清单 7-7 和图 7-11 )和SHA256Managed
(见清单 7-8 和图 7-12 )。
图 7-12
SHA256 实施示例
图 7-11
带有*哈希值的 RIPEMD160 示例。iso 文件
using System;
using System.IO;
using System.Security.Cryptography;
using System.Windows.Forms;
public class RIPEMD160Example
{
[STAThreadAttribute]
public static void Main(String[] args)
{
string directory_path = "";
if (args.Length < 1)
{
FolderBrowserDialog folder_browser_dialog = new FolderBrowserDialog();
DialogResult dialog_result = folder_browser_dialog.ShowDialog();
if (dialog_result == DialogResult.OK)
{
directory_path = folder_browser_dialog.SelectedPath;
}
else
{
Console.WriteLine("There is no directory selected.");
return;
}
}
else
{
directory_path = args[0];
}
try
{
//** store the selected directory
DirectoryInfo directoryInfo = new DirectoryInfo(directory_path);
//** select the informations of
//** each file from the folder
FileInfo[] files = directoryInfo.GetFiles();
//** declare a RIPEMD160 object
RIPEMD160 myRIPEMD160 = RIPEMD160Managed.Create();
byte[] hash_value;
//** get the hash value of each file
//** from the selected directory
foreach (FileInfo infoOfTheFile in files)
{
//** declare a file stream for the file
FileStream stream_file = infoOfTheFile.Open(FileMode.Open);
//** put its position at the
//** beginning of the stream
stream_file.Position = 0;
//** for the declared stream
//** calculate the hash value
hash_value = myRIPEMD160.ComputeHash(stream_file);
//** show the name of the value in the console
Console.Write(infoOfTheFile.Name + ": ");
//** show the hash value in the console
ShowReadableByteArray(hash_value);
//** make sure that the file will
//** closed properly
stream_file.Close();
}
return;
Console.ReadKey();
}
catch (DirectoryNotFoundException)
{
Console.WriteLine("Error: The selected directory could not be found.Try again!.");
}
catch (IOException)
{
Console.WriteLine("Error: There is a problem with the access of the file.");
}
}
//** show the byte array in a possible readable format
public static void ShowReadableByteArray(byte[] byteArray)
{
for (int k = 0; k < byteArray.Length; k++)
{
Console.Write(String.Format("{0:X2}",byteArray[k]));
if ((k % 4) == 3)
Console.Write(" ");
}
Console.WriteLine();
}
}
Listing 7-7RIPEMD160 Implementation
using System;
using System.IO;
using System.Security.Cryptography;
public class HASH256ManagedImplementation
{
public static void Main(String[] args)
{
string path_to_folder = @"D:\SHA256FileExample";
//if (args.Length < 1)
//{
// Console.WriteLine("There is no directory selected.");
// return;
//}
string directoryPathSelected = path_to_folder;
if (Directory.Exists(directoryPathSelected))
{
//** the directory that has been selected
var theDirectory = new DirectoryInfo(directoryPathSelected);
//** get details about every file
//**in the directory
FileInfo[] theFiles = theDirectory.GetFiles();
//** declare an instance of SHA256
using (SHA256 theSha256 = SHA256.Create())
{
//** calculate the hash value
//** of each of the files
foreach (FileInfo informationOfTheFile in theFiles)
{
try
{
//** declare a stream for the file
FileStream file_stream = informationOfTheFile.Open(FileMode.Open);
//** set the cursor at the
//** beginning of the stream
file_stream.Position = 0;
//** get the hash of the stream
byte[] the_hash_value =
theSha256.ComputeHash(file_stream);
//** show in the console the
//** name and hash of the file
Console.Write($"{informationOfTheFile.Name}: ");
ShowReadableByteArray(the_hash_value);
//** make sure that once is
//** done the file is closed
file_stream.Close();
}
catch (IOException e)
{
Console.WriteLine($"There is an Input / Output Exception:{ e.Message}");
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine($"There is an error with the access: { e.Message}");
}
}
}
}
else
{
Console.WriteLine("The directory cannot be found. Select another one.");
}
}
//** show the byte array in a
//** possible readable format
public static void ShowReadableByteArray(byte[] byteArray)
{
for (int k = 0; k < byteArray.Length; k++)
{
Console.Write($"{byteArray[k]:X2}");
if ((k % 4) == 3) Console.Write(" ");
}
Console.WriteLine();
}
}
Listing 7-8SHA256Managed Implementation
随机数生成
许多加密操作都是基于随机数的生成。作为一个例子,让我们考虑要求尽可能随机的密钥,这样就不可能复制它们。
RNGCryptoServiceProvider
类 [20 ]基于加密服务提供商(CSP)提供的实现,提供了加密随机数生成(RNG)的专业实现。
ClickOnce 清单
ClickOnce [21 ]是一种部署技术,它提供了创建自我更新的基于 windows 的应用的能力。它提供了最少的用户交互,并且尽可能易于使用。
ClickOnce 技术仍然在中小型企业中使用。由于签名的安全性和签名的收集存在挑战。消费者应用主要在 UWP 或 Win32 中开发,并在部署过程中使用不同的技术。如今,一些内部业务应用使用不同的部署技术。
.NET Framework 3.5 提供了加密类,允许专业人员获取并验证分配给已部署的 ClickOnce 清单的签名信息。这些类别如下:
-
ManifestSignatureInformation
班 [22 ]。用于获取与清单签名相关的信息。这可以借助于VerifyMethod
方法来实现。清单 7-9 给出了一个验证清单签名的示例方法。 -
ManifestKinds
枚举 [23 ]。此枚举用于指出应该检查哪些清单。检查过程的输出由SignatureVerificationResult
枚举类型 [24 ]内的枚举值表示。 -
ManifestSignatureInformationCollection
班 [25 ]。该类提供了与用于验证签名的ManifestSignatureInformation
类 [26 ]的对象的只读集合相关的指导。
public static System.Security.Cryptography.
ManifestSignatureInformationCollection
VerifyingTheSignature
(ActivationContext nameOfApplication,
System.Security.ManifestKinds listOfManifests,
System.Security.Cryptography.X509Certificates.
X509RevocationFlag theRevocationFlag,
System.Security.Cryptography.X509Certificates.
X509RevocationMode theRevocationMode);
Listing 7-9Verifying the Signature with X509
以下类提供了有关签名的信息:
-
StrongNameSignatureInformation
班 27 。该类保存签名的(强)名称及其特定清单的信息。 -
AuthenticodeSignatureInformation
级 28 。表示一种特殊类型的签名,称为 authenticode,它与特定清单的签名信息包含在一起。 -
TimestampInformation
级 29 。获取 authenticode 签名上的时间戳。 -
TrustStatus
枚举。检查验证码签名是否可信。
套件 B 支持
.NET Framework 3.5 提供了对 Suite B 的支持,Suite B 是美国国家安全局(NSA)发布的一组重要的加密算法。2018 年,NSA 用商用的国家安全算法套件(CNSA) [31 ]替换了套件 B。
以下是一些被包含和认可的算法:
-
高级加密标准(AES)算法。支持密钥大小 128、192 和 256。
-
安全散列算法 SHA-1、SHA-256、SHA-384 和 SHA-512
-
椭圆曲线数字签名算法。支持 256 位、384 位和 521 位曲线。
-
椭圆曲线迪菲-赫尔曼(ECDH)算法。支持 256 位、384 位和 521 位曲线。这用于密钥交换和秘密协议。
下一代密码学课程
CNG 类为与本地 CNG 操作和函数相关的托管包装器提供支持。值得一提的是,CNG 是 CryptoAPI 的替代品。所有类的名称中都包含“CNG”。主类或 CNG 包装器是CngKey
[32 ],它是一个容器类,包含存储和 CNG 密钥使用方式的抽象。
在这个类的帮助下,我们可以以一种安全的方式存储一个公钥或密钥对,并且有可能基于一个字符串名称来引用它。与椭圆曲线相关的类,比如用于签名的ECDsaCng
,用于加密运算的ECDiffieHellmanCng
[33 ,可以使用CngKey
实例 [34 ]。
.NET Framework 3.5 支持不同的 CNG 类,例如
-
CngProvider
级 35 。与密钥存储提供商进行交易。 -
CngAlgorithm
班 [36 ]。处理 CNG 算法。 -
CngProperty
类 37 。包含经常使用的关键属性。
结论
本章介绍了中最重要的加密服务 .NET 框架,从 3.5 版开始。专业人员可以执行不同加密算法和提供商的复杂实现任务。
本章就如何实现中可用的加密服务和提供程序提供了简短而全面的指南 .NET,而不必从头开始实现它们。涵盖的加密服务和提供者有
-
对称加密密钥算法
-
非对称加密密钥算法
-
数字签名
-
哈希算法及其值的使用
-
随机数生成
-
ClickOnce 清单和用于检查其身份的解决方案
-
套件 B 支持及其主要作用
文献学
-
DSACryptoServiceProvider
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.dsacryptoserviceprovider?view=netcore-3.1
。 -
RSACryptoServiceProvider
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsacryptoserviceprovider?view=netcore-3.1
。 -
ECDiffieHellman
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.ecdiffiehellman?view=netcore-3.1
。 -
ECDiffieHellmanCng
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.ecdiffiehellmancng?view=dotnet-plat-ext-3.1
。 -
ECDiffieHellmanCngPublicKey
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.ecdiffiehellmancngpublickey?view=dotnet-plat-ext-3.1
。 -
ECDiffieHellmanKeyDerivation
函数枚举。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.ecdiffiehellmankeyderivationfunction?view=dotnet-plat-ext-3.1
。 -
ECDsaCng
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.ecdsacng?view=dotnet-plat-ext-3.1
。 -
DSACryptoServiceProvider
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.dsacryptoserviceprovider?view=netcore-3.1
。 -
RSACryptoServiceProvider
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsacryptoserviceprovider?view=netcore-3.1
。 -
ECDsa
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.ecdsa?view=netcore-3.1
。 -
ECDsaCng
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.ecdsacng?view=dotnet-plat-ext-3.1
。 -
HMACSHA1
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.hmacsha1?view=netcore-3.1
。 -
MACTripleDES
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.mactripledes?view=netframework-4.8
。 -
MD5CryptoServiceProvider
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.md5cryptoserviceprovider?view=netcore-3.1
。 -
RIPEMD160
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.ripemd160?view=netframework-4.8
。 -
SHA1Managed
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.sha1managed?view=netcore-3.1
。 -
SHA256ManagedClass
。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.sha256managed?view=netcore-3.1
。 -
SHA384Managed
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.sha384managed?view=netcore-3.1
。 -
SHA512Managed
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.sha512managed?view=netcore-3.1
。 -
RNGCryptoServiceProvider
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.rngcryptoserviceprovider?view=netcore-3.1
。 -
ClickOnce 安全和部署。网上有:
https://docs.microsoft.com/en-us/visualstudio/deployment/clickonce-security-and-deployment?view=vs-2019
。 -
ManifestSignatureInformation
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.manifestsignatureinformation?view=netframework-4.8
。 -
ManifestKinds
枚举。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.manifestkinds?view=netframework-4.8
。 -
SignatureVerificationResult
枚举。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.signatureverificationresult?view=netframework-4.8
。 -
ManifestSignatureInformationCollection
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.manifestsignatureinformationcollection?view=netframework-4.8
。 -
ManifestSignatureInformation
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.manifestsignatureinformation?view=netframework-4.8
。 -
StrongNameSignaureInformation
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.strongnamesignatureinformation?view=netframework-4.8
。 -
AuthenticodeSignatureInformation
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.authenticodesignatureinformation?view=netframework-4.8
。 -
TimestampInformation
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.timestampinformation?view=netframework-4.8
。 -
TrustStatus
枚举。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.truststatus?view=netframework-4.8
。 -
商业国家安全算法。网上有:
https://apps.nsa.gov/iaarchive/programs/iad-initiatives/cnsa-suite.cfm
。 -
CngKey
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cngkey?view=dotnet-plat-ext-3.1
。 -
ECDiffieHellmanCng
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.ecdiffiehellmancng?view=dotnet-plat-ext-3.1
。 -
CngKey
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cngkey?view=dotnet-plat-ext-3.1
。 -
CngProvider
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cngprovider?view=dotnet-plat-ext-3.1
。 -
CngAlgorithm
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cngalgorithm?view=dotnet-plat-ext-3.1
。 -
CngProperty
结构。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cngproperty?view=dotnet-plat-ext-3.1
。 -
AesManaged
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.aesmanaged?view=netcore-3.1
。 -
DesCryptoServiceProvider
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.aesmanaged?view=netcore-3.1
。 -
RC2CryptoServiceProvider
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.aesmanaged?view=netcore-3.1
。 -
RijndelManaged
阶级。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.rijndaelmanaged?view=netcore-3.1
。 -
TripleDESCryptoService
提供者类别。网上有:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.tripledescryptoserviceprovider?view=netcore-3.1
。
八、Security.Cryptography
命名空间概述
本章简要概述了System.Security.Cryptography
名称空间的主要类、结构和枚举,包括加密服务、数据的安全编程和解码过程以及许多其他操作,如哈希、随机数生成和消息认证。关于密码服务及其实现的更多细节可以在章节 7 中看到。
本章的目标是为专业人员提供一个全面的路线图,清晰地概述 the.NET 框架中的加密服务。
班级
表 8-1 列出了处理加密算法的实现和封装过程的主要类,在下一代密码学(CNG)中。
表 8-1
系统。Security.Cryptography 命名空间
|班级
|
描述
|
| — | — |
| AesCng
| 实现对高级加密(AES)算法的 CNG 支持 |
| CngAlgorithm
| 封装加密算法的名称 |
| CngAlgorithmGroup
| 与 cngal 算法相同,但封装是针对一组加密算法完成的 |
| CngKey
| 定义 CNG 使用的加密密钥的功能 |
| CngKeyBlobFormat
| 该密钥被声明为 BLOB 格式,用于 CNG 对象。 |
| CngKeyCreationParameters
| 对有关密钥创建的属性的高级支持 |
| CngPropertyCollection
| 为与 CNG 属性相关的类型化集合提供强有力的支持 |
| CngProvider
| 密钥存储提供者(KSP)的名称被封装,以便进一步用于 CNG 对象。 |
| CngUIPolicy
| 用户界面(UI) CNG 的可选配置参数在访问受保护的密钥时显示。它还为配置提供封装。 |
| DSACng
| 支持数字签名算法(DSA)的 CNG 实现 |
| DSAOpenSsl
| 利用 OpenSSL 支持实现 DSA |
| ECDiffieHellmanCng
| 为椭圆曲线 Diffie-Hellman (ECDH)算法的 CNG 实现提供支持。该类的目的是执行加密操作。 |
| ECDiffieHellmanCngPublicKey
| 为 ECDH 创建一个公钥,它可以与ECDiffieHellmanCng
类一起使用 |
| ECDiffieHellmanOpenSsl
| 为 OpenSSL 支持的椭圆曲线 Diffie-Hellman (ECDH)算法的实现提供支持 |
| ECDsaCng
| 为 ECDSA 提供 CNG 支持和实施 |
| ECDsaOpenSsl
| 为基于 OpenSSL 的椭圆曲线数字签名算法(ECDSA)的实现提供支持 |
| ProtectedData
| 提供加密和解密数据的方法。该类不能被继承。 |
| RSACng
| 为 RSA 算法提供 CNG 支持和实施 |
| RSAOpenSsl
| 通过 openssl 支持为 rsa 提供支持和实施 |
| SafeEvpPKeyHandle
| 代表 OpenSSL 的特殊指针(EVP_PKEY*
) |
| TripleDESCng
| 为三重数据加密标准(3DES)算法提供 CNG 支持和实施 |
结构
表 8-2 显示了处理 CNG 密钥提供者属性封装的最重要的结构之一。
表 8-2
系统中的结构。Security.Cryptography 命名空间
|结构体
|
描述
|
| — | — |
| CngProperty
| 它为 CNG 密钥或提供者的属性提供封装。 |
枚举数
.NET Framework 从 3.5 开始,与加密和安全机制相关,它有一组枚举,为处理策略、密钥创建和加密操作提供了重要的选项。表 8-3 提到了可以在System.Security.Cryptography namespace
中找到的主要重要枚举。
表 8-3
系统中的枚举。Security.Cryptography 命名空间
|列举型别
|
描述
|
| — | — |
| CngExportPolicies
| 加密密钥的密钥导出策略 |
| CngKeyCreationOptions
| 关键创意的选择 |
| CngKeyHandleOpenOptions
| 打开钥匙把手的选项 |
| CngKeyOpenOptions
| 打开钥匙的选项 |
| CngKeyUsages
| 与其一起使用的 CNG 密钥的加密操作 |
| CngPropertyOptions
| CNG 关键属性选项 |
| CngUIProtectionLevels
| 与用户界面及其场景中使用的密钥相关的保护级别 |
| DataProtectionScope
| 使用Protect(Byte[], Byte[], DataProtectionScope)
方法获取可应用的数据保护范围 |
| ECKeyXmlFormat
| 定义并支持椭圆曲线密钥的 XML 序列化格式 |
中的安全模型 .NET 框架
图 8-1 显示了一个通用的安全模型,该模型提供了基于角色的安全如何在认证过程中发挥重要作用的快速概述。由于安全架构中最重要组件的分布,如应用域、验证流程和代码访问安全性,认证流程扮演着至关重要的角色。
图 8-1
。具有基于角色的安全性的. NET Framework 安全体系结构
看看在实现加密机制的过程中构成安全体系结构的组件,理解每个组件的定义和边界是很重要的。应用域提供了一定程度的隔离进程,对于确保应用中运行的代码不受对手影响是必要的。基于这个方面,应用域中的隔离边界为安全性、可靠性和版本控制提供了隔离边界。大多数应用域都是在运行时宿主期间创建的。
在图 8-2 中,您可以看到基于角色的安全所关注的主要组件,如*认证、*授权、主体和身份。
图 8-2
。具有基于角色的安全性的. NET Framework 安全体系结构
在图 8-3 中,应用域的组件应该在实现过程中以最大责任来对待。在实现过程中,有一个布尔属性叫做IsFullyTrusted
。
图 8-3
。具有应用域组件的. NET Framework 安全体系结构
实现第一个组件的目标,完全信任和部分信任,列表 8-1 展示了这样一个过程如何在一个真实案例中实现和转置。同样的方法和过程可以用来证明是异构、同构还是*应用域沙箱化。*输出见图 8-4 。
图 8-4
应用域的输出
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FullyOrPartiallyTrustedExample
{
class Program
{
public class InstanceWorker : MarshalByRefObject
{
static void Main()
{
InstanceWorker iw = new InstanceWorker();
iw.RunningTestIfItIsFullyTrusted();
AppDomain adSandbox = GetInternetSandbox();
iw = (InstanceWorker)
adSandbox.CreateInstanceAndUnwrap(
typeof(InstanceWorker).Assembly.FullName,
typeof(InstanceWorker).FullName);
iw.RunningTestIfItIsFullyTrusted();
}
public void RunningTestIfItIsFullyTrusted()
{
AppDomain app_domain = AppDomain.CurrentDomain;
Console.WriteLine("\r\nApplication domain '{0}': IsFullyTrusted = {1}", app_domain.FriendlyName, app_domain.IsFullyTrusted);
Console.WriteLine("IsFullyTrusted = {0} for the current assembly", typeof(InstanceWorker).Assembly.IsFullyTrusted);
Console.WriteLine("IsFullyTrusted = {0} for mscorlib", typeof(int).Assembly.IsFullyTrusted);
}
static AppDomain GetInternetSandbox()
{
System.Security.Policy.Evidence theEvidenceOfHost = new System.Security.Policy.Evidence();
theEvidenceOfHost.AddHostEvidence(new System.Security.Policy.Zone(System.Security.SecurityZone.Internet));
System.Security.PermissionSet thePermissionSet = System.Security.SecurityManager.GetStandardSandbox(theEvidenceOfHost);
AppDomainSetup appDomainSetup = new AppDomainSetup
{
ApplicationBase = System.IO.Directory.GetCurrentDirectory()
};
return AppDomain.CreateDomain("Sandbox", theEvidenceOfHost, appDomainSetup, thePermissionSet, null);
}
}
}
}
Listing 8-1Fully Trusted or Partially Trusted Example
在图 8-5 中,您可以看到哪些组件和模块涉及验证过程。你可以看到验证过程看起来像一个步骤的集合, C# 编译,JIT 编译,原生映像生成器和 *PEVerify 工具。*在接下来的章节中,我们将分析上面列出的最重要的组件。
图 8-5
.NET 框架安全体系结构与验证过程组件
JIT 编译(见图 8-6 )是一个复杂的过程,其中的关键点可以被用来访问应用的不同部分。JIT 编译的目标是加速代码的执行,并为多个平台提供支持。这种支持是棘手的,因为它会暴露和破坏系统的安全。
图 8-6
JIT 编译过程
许多安全专家和专业人员认为这是确保应用安全性的过程中的一个缺口,因为攻击者可以利用多个点,例如使用适当的工具来反汇编.exe
或.dll
文件,并继续进行软件混淆攻击。
一旦 JIT 过程完成,我们可以进一步移动到本地生成器映像(NGEN) 。 NGEN 是提高托管应用性能的强大工具。NGEN(可以通过Ngen.exe,
找到)创建(编译)本机映像(例如,ISO 文件),其中包含以特定于处理器的方式编译为机器代码的文件,并将它们部署在最终用户本地计算机的本机映像缓存中。这是一个危险的点,因为我们永远不知道谁和什么用户会在他们的计算机上,如果一个恶意用户正在等待适当的时机来利用,以找到他们的漏洞点。
最后一步是基于 PE 验证工具或Peverify.exe. It's a
非常有用的工具,它为正在生成微软中间语言(MSIL)的开发人员(如编译器编写者)提供了大量的帮助,目的是确定他们的 MSIL 代码是否符合安全要求。
图 8-7 显示我们在的最后一个组件上 .NET 框架安全架构,名为*代码访问安全。*它的所有组件都很重要,每个专业开发人员都应该将它们纳入开发过程中的应用以及安全分析和设计中。
图 8-7
那个 .NET 框架安全体系结构与代码访问安全组件
策略 、 权限 、和实施应该被视为一个整体单元,作为运行在同一应用中的不同代码上的每个信任级别的“心脏”。
熟悉以下代码访问安全概念和机制将为专业人员提供关于代码访问安全(CAS)内部机制的深厚知识背景。也就是说,为了编写以公共语言运行时为目标的有效应用,我们有以下几点:
-
类型安全代码:一种特殊类型的代码,只访问那些定义明确的类型
-
命令式和声明式语法:面向公共语言运行时的代码能够与安全系统进行交互。交互是请求权限并覆盖某些安全设置。
-
安全类库(Secure class libraries):类库有安全需求的用法,它们的主要目标是确保那些使用和调用类库的人有适当的权限访问类库及其资源。
-
透明码:带 .NET Framework 4 专业人员有能力确定代码是否能够以安全透明的方式运行。此外,确定权限是需要通过确定透明许可来完成的任务之一。
声明性安全
声明性安全所使用的语法基于用于在专业开发人员编写的代码的元数据中插入安全信息的属性。
要使用声明性安全调用,第一步是初始化权限对象中的数据状态。清单 8-2 展示了一个声明性语法的例子。这个例子很有启发性,如果你有一个名为UserPermission.
的许可,你可以提供代码调用者
using System.Security.Permissions;
[UserPermission(SecurityAction.Demand, Unrestricted = true)]
public class Example
{
public Example()
{
//** is beging protected by the security call
}
public void SomeMethodForUserA()
{
//** is beging protected by the security call
}
public void SomeOtherMethodForUserB()
{
//** is beging protected by the security call
}
}
Listing 8-2Example of Declarative Security
SecurityAction.Demand
指定调用程序需要运行代码的权限。
必要的安全
命令式安全性提供的语法调用安全性调用来创建持有权限的对象的新实例。强制安全性可用于调用要求和重写。不要做任何请求,这一点非常重要。这在绝对安全的情况下是不可能实现的。
清单 8-3 展示了命令式语法如何请求代码的调用者拥有某个名为UserPermission
的权限。
public class Example
{
public Example()
{
//** the constructor of the class
}
public void SomeMethodForUserA()
{
//** UserPermission has been demanded
//** to use imperative syntax.
UserPermission user_permission = new UserPermission();
user_permission.Demand();
//** here is being protected by the security call
}
public void SomeOtherMethodForUserB()
{
//** is not being protected by the security call
}
}
Listing 8-3Example of Imperative Security
结论
在这一章中,我们讨论了System.Security.Cryptography
名称空间,并通过指出它们的主要名称给出了它的类、结构和枚举的概述。我们继续我们的旅程,解释为什么专业人士应该根据 .NET Framework 安全模型,尊重它们的组件并遵循工作流作为必要的准则。
在本章结束时,您将拥有
-
System.Security.Cryptography
名称空间的概貌 -
理解类、结构和枚举的主要名称
-
了解内部安全模型的要点。这可能会导致安全灾难
-
主要组件如何工作和通信的清晰图像,例如应用域、验证过程和代码访问安全性
-
对 JIT 编译及其过程的深入理解
-
了解验证过程如何暴露安全漏洞
-
了解如何、何时以及为什么使用声明性和命令性安全性,并概述它们在实现过程中的重要性
文献学
-
讽刺瓦斯卡兰。交互式 C#:基础、核心概念和模式。Apress,2018。
-
安德鲁·特罗尔森和菲利普·贾皮克塞。Pro C# 7 与 .NET 和。网芯。Apress,2017。
-
乔希·比平。从 C# 开始 XML 7:面向 C# 开发人员的 XML 处理和数据访问。Apress,2017。