安全之道:加密与数字签名
钟峰·2004年10月
[版本:1.0.0]
摘要本指南介绍为基于 Microsoft® .NET 的多层应用程序设计和编写由应用程序管理的安全和数字签名的指导原则,主要讨论常见的安全任务和方案,并提供相应的信息帮助您选择最佳方法和技术。本指南适用于体系结构设计人员和开发人员。
本指南假定读者已经了解 XML Web Service 以及 Web Service Enhancement 等主题的基本知识。有关 XML Web Service 安全的相关资料可以参考 MSDN 的 http://www.microsoft.com/china/MSDN/library/archives/library/dnwssecur/html/XMLwssec.asp,有关 Web Service Enhancement 的基本情况请参阅 MSDN Library 中的 http://www.microsoft.com/china/MSDN/library/WebServices/WebServices/IntroducingtheWebServicesEnhanCEments2.0MessagingAPI.mspx。
目录
简介安全始终是可信赖的企业应用的基石。
在多层应用程序的设计中通常需要跨机器或网络来访问中间层或数据层的服务,无论这些网络是内部还是外部,我们始终都假设其是不安全(不可信赖)的通道,因为黑客或者系统漏洞总是出其不意的出现在我们不希望的地方,所以我们必须为需要经过这些通道的数据进行加密和签名。最后,我将给出一个基于.NET XML Web Service 的安全方案。
加密加密将防止数据被查看或修改,并在原本不安全的信道上提供安全的通信信道。加密的功能是将明文通过某种算法转换成一段无法识别的密文,在古老的加密方法中,加密的方法(算法)和加密的密钥都必须保密,否则就会被攻击者破译出密文。例如:古人将一段羊皮条缠绕在一根圆木上,然后在其上写下要传送书信的内容,展开羊皮条后这些书信内容将变成一堆杂乱的图文,那么这种将羊皮条缠绕在圆木上的做法可视为加密算法,而圆木棍的粗细、皮条的缠绕方向就是密钥。在现代加密体系中,算法的私密性已经不需要了,信息的安全依赖于密钥的保密性。在现代加密体系中分两种加密方法:私钥加密法(又称对称加密);共钥加密法(又称非对称加密)。
私钥加密私钥加密算法使用单个私钥来加密和解密数据。由于具有密钥的任意一方都可以使用该密钥解密数据,因此必须保护密钥不被未经授权的代理得到。私钥加密又称为对称加密,因为同一密钥既用于加密又用于解密。
可以危及用此类型密码加密的数据的一个方法是,对每个可能的密钥执行穷举搜索。根据用于执行加密的密钥大小,即使使用最快的计算机执行这种搜索,也极其耗时,因此难以实施。使用较大的密钥大小将使解密更加困难。虽然从理论上说加密不会使对手无法检索加密的数据,但它确实极大增加了这样做的成本。如果执行彻底搜索来检索只在几天内有意义的数据需要花费三个月的时间,那么穷举搜索的方法是不实用的。
私钥加密并不能实现数据完整性,即防止数据被更改;也无法完成对身份的验证,即确保数据发自特定的一方。
.NET Framework 提供以下实现私钥加密算法的类:
DESCryptoServiceProvider
RC2CryptoServiceProvider
RijndaelManaged
TripleDESCryptoServiceProvider
共钥加密公钥加密使用一个必须对未经授权的用户保密的私钥和一个可以对任何人公开的公钥。公钥和私钥都在数学上相关联;用公钥加密的数据只能用私钥解密,而用私钥签名的数据只能用公钥验证。公钥可以被任何人使用;该密钥用于加密要发送到私钥持有者的数据。两个密钥对于通信会话都是唯一的。公钥加密算法也称为不对称算法,原因是需要用一个密钥加密数据而需要用另一个密钥来解密数据。
通常将相关的共钥和私钥称为一个密钥对,在 .NET 中可以通过相关的共钥加密类来生成这样的密钥对。
.NET Framework 提供以下实现公钥加密算法的类:
DSACryptoServiceProvider
RSACryptoServiceProvider
哈希值哈希算法将任意长度的二进制值映射为固定长度的较小二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。如果散列一段明文而且哪怕只更改该段落的一个字母,随后的哈希都将产生不同的值。要找到散列为同一个值的两个不同的输入,在计算上是不可能的。
.NET Framework 提供以下实现数字签名算法的类:
HMACSHA1
MACTripleDES
MD5CryptoServiceProvider
SHA1Managed
SHA256Managed
SHA384Managed
SHA512Managed
数字签名数字签名验证发送方的标识并保护数据的完整性。
事实上数字签名不是一种具体的技术实现,它是基于以上各种加密技术组合的解决方案。通常一个基本的数字签名方案可能有如下步骤:
双方各自生成一个公钥/私钥对。
双方交换他们的公钥。
双方生成一个用于对称加密法的私钥,并使用该对称私钥加密要发送的消息。
生成加密后消息的哈希值。
用对方的共钥加密对称私钥和哈希值(该组合密文即为签名),并将该签名附加在消息主体的密文后发送给对方。
方案案例基于以上数字签名的技术方案,问题的策略点在于双方何时生成密钥对(共钥/私钥),并如何交换他们的共钥。
在编译或发布的时候生成固定的密钥对。正因为我们整个方案的安全是基于私有密钥的强度和私隐性,因此,将私钥保存在应用程序中,显然不是明智之举,那么该如何将私钥存放在安全的地方并在运行时能获取它?将它存放在加密锁中,是个不错办法,当然你也可以将它保存在注册表或者某个奇怪的文件中,但是不建议这么做,因为至少目前看来加密锁的抗攻击能力较之其他要强。
在运行时生成临时的密钥对。在某个初始化过程中将先请求获取对方的共钥,并在以后的通讯中使用该共钥加密签名。在该方案中,如何证明获取的共钥的确是真实通讯者的共钥以及保管众多联接者的共钥就成为了问题的重点。
当然,还有其他的方案来分发和管理密钥,譬如KDC(密钥分发中心)等,但本文将不作讨论。下面将分别谈谈这两个方案在.NET中的实现,首先假设一个基本的三层架构的应用,它包括一个中间层服务程序(WebService、EJB/CORBA/COM+组件),一个运行在与服务程序相连的某个网络中未知处的客户端程序,当然还有一个提供数据存储的层(数据库),不管这些应用层之间通过何种网络协议进行数据通讯,对它们的数据安全处理的机制总是一样的,为了叙述方便,我们将服务提供者称作“服务器”,而将使用这些服务的程序称为“客户端”。
方案一:
首先生成双方各自的密钥对,并交换彼此的共钥。服务器将自己的密钥对和客户端的共钥的加密文件写入硬件加密锁内(该加密锁称为“服务器锁”),客户端将自己的密钥对和服务器的共钥的加密文件写入硬件加密锁内(该加密锁称为“客户端锁”)。在每次发送数据时,先从自己的加密锁内取得对方的共钥,并用该共钥签名发送的数据;接收到数据时,利用加密锁内保存的自己的私钥来验证数据包。当然,你的加密锁供应商可能会有更好的获取加密锁内信息的建议和方法。
使用硬件加密锁是基于“加密锁的硬件具有不可复制性、并能反代码跟踪和暴力破解”的前提下作出的决定,这也正是各硬件加密锁开发商所申称的。
优点:
在使用硬件加密锁来做密钥容器的情况下,相对具有更高的安全性。 缺点:
必须妥善保管密钥。
依赖密钥容器(如 加密锁、密钥的存储器)和它们的安全性。
使用硬件加密锁会增加应用成本。事实上有些系统正是利用硬件加密锁来防止盗版。
方案二:
服务器在运行时初始化过程中生成密钥对,并将该密钥对保存在全局变量中,并提供一个方法来发布所生成的共钥,需要与之通讯者必须先通过方法获得服务器共钥,然后使用该共钥来签名发送的数据;服务器接收到数据时将使用自己的私钥对数据签名进行验证。
客户端在运行时初始化过程中生成密钥对,并将该密钥对保存在全局变量中,并通过服务器的公共方法取得服务器的共钥,然后向服务器注册并提交自己的共钥,以供服务器反馈响应时作签名之用;客户端接收到响应时将使用自己的私钥对数据签名进行验证。
服务器需要维护一个在线列表,该在线表可能包含记录客户端实例的标识(TicketID)和它的共钥(PK)这两个列,当然,具体的应用还会增加一些别的列。客户端通过向服务器注册以生成一条在线记录,而服务器将返回给注册客户端它的实例标识(TicketID),当然这个返回的数据包是用该客户端注册时提供的(并在在线列表中保存)共钥来签过名的。在以后每次客户端向服务器发送请求时,必须附带该客户端自己的实例标识(TicketID),服务器将根据此实例标识(TicketID)来查找对应客户端的共钥,并在对它的响应中使用该共钥来签名。通常在客户端退出时,会向服务器发送一个注销的请求,该动作将删除该客户端实例的在线记录,如果客户端异常退出,则会在服务器的在线列表中遗留一条作废的记录,这时,可以采用连接时限的办法来定时清理这些无效的在线记录。
优点:
不需要维护和保管密钥。 缺点:
在交换共钥的过程中可能出现的安全隐患。
需要维护在线列表。事实上我不认为这是个缺点,通常在多层架构的应用中可能已经有这样的功能。
以上两种方案各有利弊,如果能结合这两者的优点而消除其缺点就好了,幸运的是,我们可以做到这点。只要在服务器端使用『方案一』,客户端使用『方案二』就可达此目的,这样就可以不用在每个客户端部署硬件加密锁,而客户端又避免了获取服务器共钥时的安全隐患。
链接资源钟峰(Popeye Zhong)目前是 深圳恒泰丰科技公司 的.NET项目组的架构设计人员。他曾经使用 C 语言做过图形程序设计,在相当长的一段时期内从事 COM/COM+ 组件的开发和设计工作,并且短暂的做过 Lotus/Notes 和 Dialogic 语音卡程序的开发,从2003年初开始使用.NET这个充满趣味和挑战的开发平台。感兴趣的除了企业应用架构设计、组件开发、安全、图像处理外还对汽车和枪械模型有浓厚的兴趣。如果希望与他联系,可访问 http://blog.csdn.net/SW515 或者EMail SW515@21cn.com 。