分类: 网络与安全
2008-03-23 14:04:07
单向函数
散列算法是单向函数。也就是说,它们接收一个明文字符串,将它转换成一小段无法用来重建原始明文的密文。显然,要使这种函数起作用,转换中必需丢失一些数据。
乍听上去,单向函数似乎没有用,因为您无法从单向计算的密文中找回明文。为什么要计算一个无法解 开的密码呢?当然,几乎是单向的函数是非常有用的,因为从本质上讲,所有的公钥函数都是带“天窗”的单向函数。公钥密码术的良好候选函数,是那些在一个方向上容易计算,而在另一个方向上除非您知道某些秘密否则极难计算的函数。因此,我们发现公钥算法是基于因式分解和其它较难的数学窍门的。
散列函数正如结果所表明,真正的单向函数也是有用的。这些函数通常叫做 散列函数,其结果通常称为 密码散列值、 密码校验和、 密码指纹或 消息摘要。此类函数在许多密码协议中起着重要作用。
其构思就是接收一段明文,然后以一种不可逆的方式将它转换成一段(通常更小)密文。理论上,所有可能的明文将散列成一个唯一的密文,但实际上通常发生的不是那样。大多数时候,几乎有无穷多个不同的字符串可以产生完全相同的散列值。但是,对于一个好的密码散列函数来说,在实践中应该很难有两个可理解的字符串散列相同的值。好的散列函数的另一个特性是输出不以任何可辨认的方式反映输入。
散列函数通常产生恒定大小的摘要。许多算法产生很小的摘要,但是,算法的安全性很大程度上取决于结果摘要的大小。我们推荐选择那些提供不小于 128 位摘要的算法。SHA-1 提供 160 位散列,是一种可以使用的好散列函数。
可以使用散列函数来确保数据完整性,这很象传统的校验和。如果您公开发布一个文档的有规律密码散列,则任何人都可以检验该散列,假设他们知道散列算法的话。人们在实践中使用的大多数散列算法是公开发布和为人熟知的。再次提醒您,使用专用密码算法,包括散列函数,通常是一个坏主意。
因特网分发考虑一下分发在因特网上的软件包的情况。在不远的过去,通过 ftp 得到的软件包是与校验和关联的。其思想是下载软件,然后运行一个程序来计算您的校验和版本。然后可以将自行计算的校验和与 ftp 网站上得到的校验和相比较,以确定两者匹配并确保传输连接上(over the wire)的数据完整性(各种各样)。问题是这种过时的方法根本就不加密。首先,有许多校验和技术可以恶意修改下载程序,并可能导致修改过的程序产生完全相同的校验和。其次,带有其相关联(保护极差)校验和的软件包的“特洛伊”版本可以轻易地在 ftp 网站上发布。密码散列函数可以用做老式校验和算法的随便替代物。它们具有一个优点,就是使篡改投递代码变得极其困难。
预先警告您 ― 这种分发方案还有一个问题。如果作为软件消费者,不知何故下载了错误的校验和,您会怎么办?例如,假设我们分发了“xyzzy”软件包。一天夜里,一些黑客闯入了分发机器,并将 xyzzy 软件换成了一个稍作修改的版本,其中包含恶意的特洛伊木马。攻击者也将我们公开分发的散列替换成带有特洛伊副本的散列发行版。此时,当某个无辜的用户下载目标软件包时,将得到恶意的副本。受害者也下载了密码校验和,并针对软件包测试它。它进行检测,而恶意代码看起来安全可供使用。显然,如果我们不能确保散列本身不被修改,仅仅散列不能成为完整的解决方案。简而言之,我们需要一种认证散列的方法。
单向函数
散列算法是单向函数。也就是说,它们接收一个明文字符串,将它转换成一小段无法用来重建原始明文的密文。显然,要使这种函数起作用,转换中必需丢失一些数据。
乍听上去,单向函数似乎没有用,因为您无法从单向计算的密文中找回明文。为什么要计算一个无法解 开的密码呢?当然,几乎是单向的函数是非常有用的,因为从本质上讲,所有的公钥函数都是带“天窗”的单向函数。公钥密码术的良好候选函数,是那些在一个方向上容易计算,而在另一个方向上除非您知道某些秘密否则极难计算的函数。因此,我们发现公钥算法是基于因式分解和其它较难的数学窍门的。
散列函数
正如结果所表明,真正的单向函数也是有用的。这些函数通常叫做 散列函数,其结果通常称为 密码散列值、 密码校验和、 密码指纹或 消息摘要。此类函数在许多密码协议中起着重要作用。
其构思就是接收一段明文,然后以一种不可逆的方式将它转换成一段(通常更小)密文。理论上,所有可能的明文将散列成一个唯一的密文,但实际上通常发生的不是那样。大多数时候,几乎有无穷多个不同的字符串可以产生完全相同的散列值。但是,对于一个好的密码散列函数来说,在实践中应该很难有两个可理解的字符串散列相同的值。好的散列函数的另一个特性是输出不以任何可辨认的方式反映输入。
散列函数通常产生恒定大小的摘要。许多算法产生很小的摘要,但是,算法的安全性很大程度上取决于结果摘要的大小。我们推荐选择那些提供不小于 128 位摘要的算法。SHA-1 提供 160 位散列,是一种可以使用的好散列函数。
可以使用散列函数来确保数据完整性,这很象传统的校验和。如果您公开发布一个文档的有规律密码散列,则任何人都可以检验该散列,假设他们知道散列算法的话。人们在实践中使用的大多数散列算法是公开发布和为人熟知的。再次提醒您,使用专用密码算法,包括散列函数,通常是一个坏主意。
因特网分发
考虑一下分发在因特网上的软件包的情况。在不远的过去,通过 ftp 得到的软件包是与校验和关联的。其思想是下载软件,然后运行一个程序来计算您的校验和版本。然后可以将自行计算的校验和与 ftp 网站上得到的校验和相比较,以确定两者匹配并确保传输连接上(over the wire)的数据完整性(各种各样)。问题是这种过时的方法根本就不加密。首先,有许多校验和技术可以恶意修改下载程序,并可能导致修改过的程序产生完全相同的校验和。其次,带有其相关联(保护极差)校验和的软件包的“特洛伊”版本可以轻易地在 ftp 网站上发布。密码散列函数可以用做老式校验和算法的随便替代物。它们具有一个优点,就是使篡改投递代码变得极其困难。
预先警告您 ― 这种分发方案还有一个问题。如果作为软件消费者,不知何故下载了错误的校验和,您会怎么办?例如,假设我们分发了“xyzzy”软件包。一天夜里,一些黑客闯入了分发机器,并将 xyzzy 软件换成了一个稍作修改的版本,其中包含恶意的特洛伊木马。攻击者也将我们公开分发的散列替换成带有特洛伊副本的散列发行版。此时,当某个无辜的用户下载目标软件包时,将得到恶意的副本。受害者也下载了密码校验和,并针对软件包测试它。它进行检测,而恶意代码看起来安全可供使用。显然,如果我们不能确保散列本身不被修改,仅仅散列不能成为完整的解决方案。简而言之,我们需要一种认证散列的方法。
单向函数
散列算法是单向函数。也就是说,它们接收一个明文字符串,将它转换成一小段无法用来重建原始明文的密文。显然,要使这种函数起作用,转换中必需丢失一些数据。
乍听上去,单向函数似乎没有用,因为您无法从单向计算的密文中找回明文。为什么要计算一个无法解 开的密码呢?当然,几乎是单向的函数是非常有用的,因为从本质上讲,所有的公钥函数都是带“天窗”的单向函数。公钥密码术的良好候选函数,是那些在一个方向上容易计算,而在另一个方向上除非您知道某些秘密否则极难计算的函数。因此,我们发现公钥算法是基于因式分解和其它较难的数学窍门的。
散列函数
正如结果所表明,真正的单向函数也是有用的。这些函数通常叫做 散列函数,其结果通常称为 密码散列值、 密码校验和、 密码指纹或 消息摘要。此类函数在许多密码协议中起着重要作用。
其构思就是接收一段明文,然后以一种不可逆的方式将它转换成一段(通常更小)密文。理论上,所有可能的明文将散列成一个唯一的密文,但实际上通常发生的不是那样。大多数时候,几乎有无穷多个不同的字符串可以产生完全相同的散列值。但是,对于一个好的密码散列函数来说,在实践中应该很难有两个可理解的字符串散列相同的值。好的散列函数的另一个特性是输出不以任何可辨认的方式反映输入。
散列函数通常产生恒定大小的摘要。许多算法产生很小的摘要,但是,算法的安全性很大程度上取决于结果摘要的大小。我们推荐选择那些提供不小于 128 位摘要的算法。SHA-1 提供 160 位散列,是一种可以使用的好散列函数。
可以使用散列函数来确保数据完整性,这很象传统的校验和。如果您公开发布一个文档的有规律密码散列,则任何人都可以检验该散列,假设他们知道散列算法的话。人们在实践中使用的大多数散列算法是公开发布和为人熟知的。再次提醒您,使用专用密码算法,包括散列函数,通常是一个坏主意。
因特网分发
考虑一下分发在因特网上的软件包的情况。在不远的过去,通过 ftp 得到的软件包是与校验和关联的。其思想是下载软件,然后运行一个程序来计算您的校验和版本。然后可以将自行计算的校验和与 ftp 网站上得到的校验和相比较,以确定两者匹配并确保传输连接上(over the wire)的数据完整性(各种各样)。问题是这种过时的方法根本就不加密。首先,有许多校验和技术可以恶意修改下载程序,并可能导致修改过的程序产生完全相同的校验和。其次,带有其相关联(保护极差)校验和的软件包的“特洛伊”版本可以轻易地在 ftp 网站上发布。密码散列函数可以用做老式校验和算法的随便替代物。它们具有一个优点,就是使篡改投递代码变得极其困难。
预先警告您 ― 这种分发方案还有一个问题。如果作为软件消费者,不知何故下载了错误的校验和,您会怎么办?例如,假设我们分发了“xyzzy”软件包。一天夜里,一些黑客闯入了分发机器,并将 xyzzy 软件换成了一个稍作修改的版本,其中包含恶意的特洛伊木马。攻击者也将我们公开分发的散列替换成带有特洛伊副本的散列发行版。此时,当某个无辜的用户下载目标软件包时,将得到恶意的副本。受害者也下载了密码校验和,并针对软件包测试它。它进行检测,而恶意代码看起来安全可供使用。显然,如果我们不能确保散列本身不被修改,仅仅散列不能成为完整的解决方案。简而言之,我们需要一种认证散列的方法。
认证问题
在我们考虑认证问题时,可能出现两种情况。我们可能希望每种情况都可以验证散列。如果是这样,我们可以使用基于 PKI 的数字签名,这将在下面讨论。或者,我们希望限定谁能验证散列。例如,假设我们向 sci.crypt 新闻组发送了一封匿名信,其中投递了一种专用加密算法的全部源代码,但是希望只有我们最亲密的朋友才能验证我们投递了消息。可以使用消息认证代码(MAC)来达到这个目的。
消息认证代码
MAC 通过使用一种共享秘钥起作用,接收方端使用它的一个副本。该密钥可以用于认证可疑数据。发送方必须拥有秘钥的另一个副本。MAC 有几种工作方式。第一种方式是在计算摘要之前,将秘钥并置到数据末尾。如果没有秘钥,则无法确认数据未经改动。另一种计算更复杂的方式是照常计算散列,然后再使用对称算法(如 DES)加密散列。要认证散列,必须首先对它解密。
MAC 在许多其它环境中也很有用。如果您希望不使用加密而实现基本消息认证(也许是由于效率原因),MAC 是完成该任务的合适工具。即使您已经使用了加密,MAC 也是一种确保加密位流在传输中免遭恶意修改的极佳方法。
如果仔细设计,好的 MAC 可以帮助解决其它公共协议问题。许多协议在遭受所谓的 回放攻击(或者 捕获-回放攻击)期间,明显存在一个普遍问题。假设我们向银行发送一个请求,要求从我们的帐户划拨 50 美元到 John Doe 的银行帐户。如果 John Doe 拦截了通信,他可以稍后向银行发送一个相同的消息副本!有时银行会认为我们发送了两个请求。
回放攻击
回放攻击被证实是许多真实世界系统中的普遍问题。幸好,我们可以使用 MAC 的巧妙用法来缓和这种情况。在银行划拨示例中,假设我们使用了一种随秘钥散列请求的原始 MAC。为了对付回放,我们可以确保散列永远不同。做到这一点的一个显而易见的方法是使用时间戳记。
如果服务器发现一个请求的时间戳记过期(比如,超过 60 秒),它将拒绝请求。这或许足够了,也可能还不够,因为它还是导致了一个 60 秒的窗口,其中回放攻击可能 发生。您可能会考虑禁止在同一时间单元发生两次请求,并缓存关于过去 60 秒以内到达的有效请求的信息。如果您能够处理这种特殊情况:在同一时间单元里两次执行了同一交易,则这种解决方案也许可行。但是存在更简单的解决方案。当您计算 MAC 时,不仅散列数据和秘钥,而且散列一个唯一、有序的序列号。远程主机只需要了解它所处理的最后一个序列号,并确保不处理比下一个预期序列号更旧的请求。这是普遍使用的方法。
在许多情况下,认证其实不是问题。例如,考虑使用密码散列来认证从控制台登录到机器的用户。在许多系统中,当用户第一次输入密码时,实际上并没有存储密码本身。相反,存储了密码的密码散列。因为大多数用户觉得如果系统管理员不能随意检索他们的密码会更好。假设操作系统是可信的(这是个可笑的大前提),我们可以假设我们的加密散列密码的数据库是正确的。当用户试图登录,并输入密码时,登录程序散列它,并将新散列的密码与存储的散列比较。如果两者相等,我们假设用户输入了正确密码,则登录继续。
Telnet 协议
可惜,架构设计师和开发人员有时假设认证机制的安全性实际上不是问题,但实际上它是。例如,考虑一下 telnet 协议。大多数 telnet 服务器接收用户名密码作为输入。然后散列密码,或执行一些类似的转换,然后将结果与本地数据库中的比较。问题在于使用 telnet 协议时,密码在网络上以明文传输。任何能够使用包嗅探器在网络线路上侦听的人都可以发现密码。telnet 认证提供了很差的保护,潜在攻击者可以轻易地清除保护。许多著名的协议(包括 FTP、POP3 和 IMAP 的多数版本)都具有类似的破绽百出的认证机制。
其它攻击
任何好的密码散列算法应该是这样的,即使给定一个已知的消息和与它关联的消息散列,也很难找到替代明文的重复散列。对冲突的故意搜索意味着蛮力攻击,这通常很难。当攻击者希望用第二份明文文档产生除了杂乱无意义的字符串之外的东西时,就尤其困难。
另一种对密码散列的攻击比平均蛮力攻击容易实施得多。考虑下列情况:Alice 向 Bob 显示了一份文档和验证文档的密码散列,文档内容是 Alice 同意为每个小饰品付给 Bob 5 美元。Bob 不想在他的服务器中存储该文档,所以他只存储了密码散列。Alice 希望只为每个小饰品付 1 美元,所以她想创建第二份文档,所产生的散列值和 5 美元的那份相同,然后告上法庭,控诉 Bob 多收了她的钱。当她出庭时,Bob 将出示散列值,相信 Alice 的文档不能散列出那个值,因为这不是她显示给他的原始文档。如果其攻击是成功的,Alice 将能够证明她所伪造的文档确实散列出 Bob 存储的值,法庭将判她胜诉。
但她是如何做到这一点的呢?Alice 使用了所谓的 生日攻击。在这种攻击中,她创建了两份文档,一份写着每个小饰品 5 美元,另一份则写着每个小饰品 1 美元。然后,在每份文档中,她标识出 n处可以进行表面更改的地方(例如,那些可以用制表符取代空格的地方)。好的 n值通常是最终散列输出的位长度的一半加 1(所以如果我们指定散列输出的位长度为 m,则 n= m/2+1)。对于 64 位散列算法,她将在每份文档中选择 33 个地方。然后她反复尝试每份文档不同的排列,创建和存储散列值。一般来说,预计她将在散列了大约 2 m/2条消息之后找到散列出相同值的两份文档。这比蛮力攻击要有效得多,如果使用蛮力攻击,预计她必须散列的消息数为 2 m-1。如果 Alice 执行一次成功的蛮力攻击需要一百万年,那么她也许一周以内就可以完成一次成功的生日攻击。因此,Bob 应该要求 Alice 使用一种算法,它所产生的摘要大小使得她不能在任何合理的时间之内完成生日攻击。
如果希望针对生日攻击获得和针对蛮力攻击一样的安全性,给定一个密钥长度为 p的对称密码,您应该选择提供大小为 p*2的摘要的散列算法。因此,对于具有很高安全性需求级别的应用程序,要求散列算法产生 256 位甚或 512 位的消息摘要是个好主意。
什么是适用的好散列算法呢?
我们特别喜欢 SHA-1。Bruce Schneier 也推荐这个算法。但是如果散列长度必需超过 160 位,SHA-1 还不够。对于大位数散列,尝试使用适合于执行散列法的对称加密术。GOST 散列算法是个好示例,它从 GOST 加密术派生而来,带有 256 位的散列长度。更长的散列长度很可能要求进行一些编码来适应对称加密术。Schneier 在 Applied Cryptography中概述了构建此类算法的构造。SHA-1 或 GOST 散列算法都没有任何知识产权限制。
数字签名
数字签名背后的思想是模仿传统手写签名。该思想是能够以某种方式“签署”一份数字文档,该签名具有和物理签名一样的法律效力。数字签名至少必须和手写签名一样好地满足以下主要目的:
即使对于手写签名,这些目标也只是概念上的,并不能真正地反映现实。例如,伪造签名是可能的,尽管很少有人技艺高超,真的能伪造。然而,签名罕有滥用的倾向,这很好地保持了它在法庭的地位。总之,墨水签名已经是足够好的解决方案。
电子签名至少可以做得和物理签名一样好。这个事实经常使人们吃惊,因为他们将这种签名当成是类似于人们经常放置在电子邮件消息末尾的那种签名文件(一串 ASCII)。如果数字签名就是象这样的,那么它们根本就没什么用。很容易从一个文件复制签名,并将它直接添加到另一个文件上以形成一个赝品。也可能轻易地修改一个经过签署的文档,并且谁也不能发现。谢天谢地,数字签名完全不是这样。
大多数数字签名系统将公钥密码术和密码散列算法结合使用。正如我们所解释的,公钥密码系统经常使用接收方公钥来加密消息,然后接收方使用相应的专用密钥解密。专用密钥也能用来对只能用相应公钥解密的消息进行解密。如果某人将他的专用密钥完全保持私有,(您最近没有被黑,不是吗?)能够使用相应公钥解密消息,则构成可疑的人对原始消息进行加密的证明。
数字签名不仅在签署文档的时候有用 — 它们几乎可用于任何认证需求。例如,它们经常与加密联合使用,以便保持数据私有性和数据认证。
用于文档的数字签名经常由对文档的加密散列构成,然后用专用密钥加密散列。结果密文称为签名。任何人都可以通过自行散列文档,然后解密签名(使用公钥或共享秘钥),并比较两个散列来确认签名。如果两个散列相等,则认为签名有效(假设进行确认的人相信他使用的公钥确实属于您)。
签名不必随文档一起存储。同样,签名适用于文档的任何相同的数字副本。签名也可以复制,但是无法使它适用于其它文档,因为最后得到的散列与解密散列不匹配。
数字签名的问题
数字签名的一个问题是认可。人们总是可以声明他们的密钥被盗。但是,数字签名还是作为物理签名的合法替代广泛地得到接受,因为它们至少和物理签名一样接近上面提到的目标。目前至少有 30 个州有数字签名法律,并且更多州很可能立法(如果美国国会没有抢先通过一项国家法律的话)。
大多数公钥算法,包括 RSA 和 ElGamal,可以容易地扩展以支持数字签名。实际上,一个支持这些算法之一的好软件包应该也支持数字签名。我们推荐您将自己喜欢的公钥密码术算法用于数字签名。尝试使用内置原语而不是抛开加密算法和散列函数去构建自己的构造。
下一步是什么?
在未来最重要的主题是密钥管理:如何安全地生成、存储、更改、销毁或传递加密密钥?密码术是一个巨大的领域,但又只是软件安全性的一个方面。甚至我们也不能谎称自己完全了解它。