第七章、八章学习笔记

第七章——安全信道

安全信道是所有实际问题最普遍的部分

一、安全信道的性质

可以将安全信道定义为在Alice和Bob之间建立安全的连接,但这并不是正式的定义。在弄清楚所讨论的问题之前,首先要对问题进行—定的形式化。

(1)角色

多数的连接都是双向的,Alice给 Bob发送消息,Bob也同样会给 Alice 发送消息。通常不想混淆这两个消息流,因此在协议中必然有某种不对称。在实际系统中,可能一方是客户端另一个则是服务器,或者更简单地采用发起者(发起安全连接的一方)和响应者。采用哪一种说法都可以,但必须将 Alice 和Bob的角色分配给通信的两方,确保他们知道自己的角色。
然而Eve总是存在的,他会用任何可能的手段对安全信道进行攻击。Eve可以读取Alice 和Bob之间的所有通信,并且可以任意操纵这些通信的内容,尤其是 Eve可以对正在发送的数据进行删除、插入、修改等操作。
我们可以把 Alice向 Bob发送消息想象为两台计算机通过某种网络互相发送消息。另一个有趣的应用是安全地存储数据,如果认为存储数据是向未来发送数据,那么这里的讨论同样适用。Alice和Bob可能是同一个人,传输介质可能是备份磁带或者 USB 盘,仍然需要保护这些传输介质的数据安全,防止被窃取和操纵。当然,如果需要发送消息给未来,就无须交互协议,因为未来不会向过去发送消息。

(2)密钥

实现安全的信道,必须有一个共享的密钥。
我们假定密钥已经建立,对密钥有如下要求∶
1、只有 Alice 和 Bob 知道密钥 K。
2、每次安全信道被初始化时,就会更新密钥 K。
以上第二条要求十分重要。如果相同的密钥被反复使用,之前会话的消息就能向 Alice或 Bob重放,由此引起各种混淆。因此,即使以固定的口令为密钥,在Alice和Bob之间仍然需要密钥协商协议来建立一个合适的唯一密钥K,在每次建立安全信道时都必须重新运行这个协议。每次会话所用的密钥K称为会话密钥。

(3)消息或字节流

另一个问题是,我们是将 Alice 和 Bob之间的通信看作离散消息序列还是连续的字节流(如流媒体)。我们仅仅考虑处理离散消息的系统,它可以很容易地用来处字节流,这只需将数据流分割为离散的消息,在接收端再组合成字节流就可以了。实际应用上,几乎所有的系统在密码层都使用离散消息系统。
我们还假设实现 Alice 和 Bob之间传递消息的底层传输系统是不可靠的。从密码学的角度来看,即使是一个可靠的通信协议,如TCP/IP,也不能构成一个可靠的通信信道,因为攻击者很容易在不打乱数据流的情况下在TCP流中修改、移除或插入数据。仅对数据包丢失这样的随机事件来说,TCP是可靠的,但它不能抵御主动攻击。从攻击对手的角度来看,根本没有可靠的通信协议

(4)安全性质

现在我们可以提出信道的安全性质。Alice发送消息序列m1,m2…,经由安全信道算法处理后发送给 Bob,Bob 用安全信道算法处理接收到的消息,得到消息序列m1',m2',…。
必须满足以下性质:

  • 除了消息m,的长度和发送时间外,Eve无法得到有关消息m,的任何其他信息。
  • 即使 Eve通过操纵正在传输的数据对信道进行攻击,Bob接收到的消息序列m1',m2',…,必须是序列m1,m2,…的子序列。并且 Bob 很清楚地获悉他收到的是哪一个子序列(子序列就是从原序列中去掉0个或更多的元素后得到的序列)。
    一个性质是关于隐私性。在理想状态下,Eve不应该得到关于这些消息的任何信息。在现实中,这很难达到,要隐藏消息的长度和时间等信息非常困难。已知的解决方法是要求Alice用她所使用的最大带宽发送连续的消息流,如果她没有消息要发送,就必须生成一些琐碎的消息进行发送。对军事应用来说,这个方案是可接受的,但不适合对大多数的民用应用。既然 Eve可以获取在通信信道传输的消息的长度和时间,她就可以获悉通信的对象、通信量和通信的时间,这些统称为流量分析。流量分析可以产生很多信息,而且很难阻止。这也是在其他安全信道常见的问题,包括SSLTLS、IPsec 和SSH。本书不解决该问题,因此TO1 Eve 可以在我们的安全信道上进行流量分析。
    第二个性质确保 Bob 能够获取以正确的顺序排列的准确消息。在理想状态下,我们希望Bob收到的消息序列恰好是Alice发送的,但现实中没有在密码学意义上可靠的通信协议,Eve总是可以删除传输中的某个消息。由于我们不能防止消息的丢失,Bob就必须能处理仅得到一个消息子序列的情形。注意,Bob收到的消息序列的顺序是正确的,没有重复的消息,没有修改过的消息,也没有除 Alice以外的人发送的伪造消息。更进一步说,要求Bob清楚地知道他错过了哪些消息。这对某些应用来说很重要,因为在这些应用中对于消息的解释依赖于收到这些消息的顺序。

为什么不在安全信道内实现重发功能使得安全信道是可靠的?因为这将会使得安全信道的描述变得复杂,而我们希望关键安全模块应该尽量简单。消息的确认和重发是标准的通信协议技术,可以在安全信道的上层来实现。本书关注的重点是密码学方面,而不是基本通信协议技术方面。

二、认证与加密的顺序

显然,我们将同时对消息进行加密和认证。有三种方法可供选择∶先加密,然后再对密文进行认证(加密然后认证);先认证,然后再对消息和MAC值进行加密(认证然后加密);或者同时加密消息和认证数据,然后将两个结果组合(如连接)起来(加密同时认证)。何种方法最优没有定论。
支持采用第一种方法的主要原因有两个。首先,理论结果表明,根据安全加密和认证的某些特定定义。先加密的方案是安全的,而其他方法是不安全的。如果追究这些研究的细节,会发现当加密方案有某个特殊的缺陷时,先认证的方案才是不安全的。在实际系统中,我们从不会使用有这种缺陷的加密方案。然而,这些较弱的加密方案满足某个特殊的安全性[102]的正式定义。对这个弱的加密方案的密文应用MAC就修复了这个缺陷,从而使其变得安全了。这些理论结果是有价值的,但并不是可以应用于实际加密方案。事实上,有类似的证明表明,这些问题在流密码(如 CTR模式)和 CBC模式的加密方案中(其中对瞬时值或IV进行了认证)不可能出现。
先进行加密的另外一个理由是,它能更有效地丢弃伪造消息。对正常的消息,不管是哪种顺序。Bob 都必须解密消息和检查认证。如果消息是伪造的(即有错误的MAC域),Bob将丢弃它。在先加密的方案中,接收端后解密,Bob 永远无须解密一个伪造消息,因为在解密之前就可以识别这个伪造消息,从而将它丢弃。但在先认证的方案中,Bob 首先必须对消息进行解密,然后再进行认证检查,这样对伪造消息的处理就需要做更多的工作。当Eve 给Bob 发送大量的伪造消息时,那么需要做的工作量非常大。在先加密的方案中,Bob 节省了解密的时间,从而减轻了CPU 的负载。在一些特殊环境里,这可以使拒绝服务(Denia-ofService,DoS)攻击变得更加困难,虽然也就提高了大约2 倍。现实中的情况是,更有效的DOS 攻击是通过通信信道饱和来进行攻击的,而不是消耗 Bob 的 CPU。支持加密同时认证的主要理由是加密和认证过程可以并行地进行,在某些情况下该方法可以提高性能。采用加密同时认证方法时,攻击者可以获取原始消息的 MAC标签,这是因为 MAC 值没有被加密(不同于先认证后加密模式),而且MAC值也不是从密文计算来的(不同于先加密后认证模式)。MAC 是用于认证消息而不是提供隐私性。这意味着应用加密同时认证方法时 MAC有可能泄露消息的隐私信息,因而破坏安全信道的隐私性。如同先认方法,当用在加密同时认证方法中时,某些加密方案同样是不安全的。如果审慎地选用MAC 和加密方案,并且将瞬时值等额外数据也作为MAC的输入,加密同时认证方法也是安全的。
支持先认证的主要理由也有两个。在先加密构造中,Eve可以看到 MAC的输入和MAC值; 而在先认证的构造中、MAC的输入(即明文)和真正的MAC值被隐藏了,Eve 只能得到密文和加密后的 MAC 值,使得对 MAC的攻击要比先加密的情况困难得多。实际的选择是两个函数(加密函数和认证函数)中最后应用哪一个。如果最后进行的是加密、那么Eve就试图攻击加密函数; 如果最后进行的是认证,Eve 就设法攻击认证函数。在许多情况下,认证比加密更重要,因此我们倾向于让加密函数处在Eve直接攻击中,而尽可能地保护MAC。当然,如果加密方案和MAC都足够安全的话,这些问题都无所谓,但是我们有职业的偏执,希望不基于该假设仍能提供一个健壮的安全信道。

为什么认证要比加密更重要呢?
设想正在使用的是安全信道,分别考虑在知道信道内容和可以对通信数据进行修改这两种情况下,Eve 的攻击所带来的最大破坏。在大多数情况下,相比看到通信内容所造成的损害,修改通信数据是灾难性的攻击。

支持先认证的第二个理由是Horton 原则,即要对消息的含义进行认证,而不是对消息本身进行认证。对密文进行认证破坏了这个原则,从而就产生了缺陷。这个潜在的危害来自于密文可能通过了Bob的认证检测,但他用于解密消息的密钥可能与 Alice加密消息所用的密钥不同。于是,尽管通过了认证检测,Bob 仍将得到一个与 Alice所发送的消息不同的明文。这不应该发生,但可能会发生。某个具有特殊的(不寻常)IPsec配置就存在这样的问题",这个缺陷必须被修复。人们可能会将加密密钥包括在需认证的附加数据中,但是除了正常用途外不应这样来使用密钥。这会引发额外的风险,不应该让一个有缺陷的 MAC函数来泄露加密密钥的信息。标准的解决方案是从单个安全信道密钥来为安全信道生成加密密钥和认证密钥。这种方法可以克服这个缺陷,但会导致一个交叉依赖关系,认证会依赖于密钥生成系统。

三、安全信道设计概述

我们的解决方案由三部分组成∶消息编号、认证和加密。接下来我们就详细介绍一个可能的安全信道的设计,在此过程中,将解释如何去考虑相关问题。

(1)消息编号

消息编号对于安全信道的设计十分重要。消息编号可用于为加密算法提供需要的IV;无须存储大量数据即可让 Bob拒绝重放的消息;Bob可用来判断在通信过程中丢失了哪些消息;也能保证Bob以正确的顺序接收消息。为此,消息编号必须单调增加(后面的消息必须有较大的消息编号)而且是唯一的(没有两个消息的编号是相同的)。
指定消息编号非常容易,Alice将第一个消息编号为1,将第二个消息编号为2,依次类推。Bob 记录他收到的最后一个消息的编号。任意—个新消息对应的消息编号要大于之前消息的消息编号,只接受逐渐增大的消息编号,Bob 能确保 Eve无法向重放旧消息。
我们的安全信道设计中采用32 位长度的消息编号,第一个消息的编号为1,消息的最大编号为2-1。如果消息编号溢出,Alice就必须停止使用现在的密钥,并且再一次运行密钥协商协议生成新的密钥。消息编号必须是唯一的,所以不允许将它重置为0。

(2)认证

我们需要一个MAC作为认证函数。
设l(.)为一个返回数据串长度(以字节为单位)的函数,MAC值a可以计算如下∶
ai:=MAC(i||&(xi)||xi||mi)
其中i与l(xi)都是32 位的无符号整数(即最低有效字节在前的格式)。l(xi)确保可以将i||&(xi)||xi||mi,唯一地分解成各个对应的域。如果没有l(xi),将会有很多方式将它分割为i、x和m,这将导致认证会出现歧义。当然,对x,进行编码的方式应使得无须更多上下文信息即可解析为不同的域,但这并不是在这个层面需要的要求,使用安全信道的应用必须保证这一点。

(3)加密

安全信道设计的加密算法使用CTR模式下的AES。但是我们提到过CTR 模式因为使用瞬时值是危险的。我们提到过将瞬时值的控制交给开发者会有风险,也见识过太多的应用因为未正确地生成瞬时值而引发的安全问题。然而,我们的安全信道将瞬时值的处理内嵌,从不将生成瞬时值的控制交给任何其他方。我们采用消息编码作为CTR模式需要的唯一瞬时值,所以我们的安全信道使用CTR模式。但是我们仍然不会将生成瞬时值的生成交外部系统,建议不要直接使用 CTR 模式。
我们将每个消息的长度限制为不超过 16×2°个字节,即分组计数器限制为32 位。当然,我们也可以使用64位的计数器、但是32位的计数器在许多平台上都更易于实现,而且对大多数应用来说也不需要处理那么大的信息。
密钥流由k0,k1,…组成,对瞬时值为i的消息,密钥流定义为∶
k0,....,k236-1:=E(K,0||i||0)||E(K,1||i||0)||···||E(K,232-1||i||0)
这里每个明文分组由32 位的分组编号、32位的消息编号以及64 位的0组成。密钥流很长,我们仅仅使用最初的l(mi)+32个字节(不必计算密钥流的其他字节),将mi,与ai,串联起来后与k,…,ki(mi)+31进行异或运算。

(4)组织格式

我们不能将mi||ai,加密后就发送,因为Bob也需要知道消息编号。最后发送的消息将由编码为 32 位整数的i(其中最低有效字节在前)与加密后的mi,和 a,组成。

四、详细设计

现在来讨论安全信道设计的具体细节,再次强调这不是实现安全信道唯一方式。而是借这个机会探讨建立安全信道所面临的挑战和精妙之处。方便起见,我们将信道定义为双向的,因此相同的密钥将双向使用。如果我们将信道定义为单向的,可以肯定有人在两个方向使用相同的密钥,最终会破坏安全性。采用双向信道可以降低这样的风险。另一方面,如果你正在使用他人定义的安全信道,那要格外谨慎不要在双向使用相同的密钥。

(1)初始化

第一个算法是信道数据的初始化算法,它包含两个主函数∶建立密钥和建立消息编号。我们从信道密钥导出4个附属密钥∶ Alice给 Bob发送消息所用的加密密钥和认证密钥,Bob 给 Alice 发送消息所用的加密密钥和认证密钥。


还有一个用于清除状态信息S的函数,对于这个函数我们不做详细说明,它的功能就是清除存储状态信息S的内存。由于密钥在这里存储,清除这个区域的信息是至关重要的。在许多系统中,仅仅释放内存空间并不意味着清除了状态信息,因此在使用完后必须删除 S。

(2)发送消息

现在来讨论发送消息的处理过程。这个算法以会话状态、要发送的消息以及用于认证的附加数据为输入,输出准备发送的已加密和认证的消息。接收者必须拥有相同的附加数据以进行认证检查。

基于我们之前的讨论,这个算法是非常直观的。我们要检查消息计数器是否已达最大值,这个检查非常必要。如果计数器发生轮回,那么整个安全性都会受到威胁,这是常见的错误。认证和加密正如我们前面所讨论的。最后,我们将i与加密和认证的消息一起发送,以便接收者获取消息编号。
要注意由于MsgCntSend被修改了,因此会话状态要更新。需要再次强调的是∶这是至关重要的,因为消息编号必须唯一。事实上,这些算法中的每部分对于安全性都是至关重要的。
我们的安全信道设计采用CTR加密模式,如果加密方案需要进行填充,那么在解密时必须验证填充内容。

(3)接收消息

接收消息算法的输入有SendMessage 算法计算出来的经加密和认证后的消息,以及用于认证的附加数据x。我们假定接收者通过某种带外(out-of-band)的方式已获取了x,例如如果x包含协议版本号,那么如果 Bob 参与了协议,他必然知道该值。

这里使用操作的标准顺序。你可以在解密之前对消息编号进行检查,但是如果i在传输过程中被破坏了,这个函数将会返回一个错误的出错信息。本应返回的是消息被破坏的出错信息,却返回了消息顺序错误这样的出错信息。由于调用者对两种情况可能采用不同的处理方式,该例程不应返回错误的信息。有人倾向于先进行检查,因为这样可以更快地丢弃虚假消息。但是我们认为这并不重要,如果收到了太多的错误包以至于丢弃这些包的速度都很重要,这说明系统已经遇到了更大的问题。

对于接收者还有个很重要的问题,在认证码被验证之前,ReceiveMessage 函数不能泄露关于密钥流和明文消息的任何信息。如果认证失败。返回一个出错提示,但密钥流和明文不能被泄露。在实际实现中应清除存储这些元素的内存区域。为什么一定要清除内存信息呢?一般假设攻击者可以获取密文,因此明文消息会泄露密钥流。危险在于攻击者可以发送一个伪造的消息(带有不正确的MAC 值),但他可以从接收者发布出来的数据中得到密钥流。这里还是基于多疑症模型,总假定该例程发布或泄露的任何数据都会被攻击者获得。在返回错误信息之前销毁数据k和m,这就可以保证永远不会泄露这些数据。

(4)消息的顺序

与发送者一样,接收者通过修改 MsgCntRec变量来更新状态S。接收者要确保接收的消息编号严格地递增,这可以保证任何一个消息都不会被接收两次,但如果消息流在传输时改变了顺序,一些有效的消息将会丢失。
这个缺陷很容易修复,但是需要一定代价。如果让接收者接收顺序颠倒的消息,那么使用安全信道的应用必须能处理顺序颠倒的消息。很多应用都不能处理这个问题。而有些应用可以处理,但是当消息顺序颠倒时,会出现一些细微的错误(与安全性有关)。在大多数情况下,我们选择修复传输层来防止消息顺序颠倒的情况发生,因此安全信道不必处理这些事情。

(5)备选方案

我们所给出的安全信道的定义并非总是实用,特别是在嵌入式硬件中实现安全信道时,实现 SHA-256的开销非常高。作为备选,最近研究的一些专用分组密码模式可以同时提供隐私性和可认证性。
类似于CBC模式和CBC-MAC,这些专用于隐私性和认证性的分组密码工作模式只有一个密钥作为输入。这些模式一般都是以消息作为输入,加上用于认证的附加消息以及瞬时值。但是这些模式不像使用相同密钥的 CBC 模式和 CBC-MAC一样简单,因为在常规加密和常规 MAC 中采用相同密钥会带来安全问题。

第八章——实现上的问题I

本章我们将讨论有关实现的问题。实现加密系统与实现普通的程序不同,应该有一些不同的处理方法和步骤。
最大的问题,一如既往,仍然是木桶原理的最脆弱环节,在实现的层面很容易就会将安全性破坏或者复杂化。事实上,实现上的错误,比如缓冲区溢出,仍然是目前为止在实际系统中最大的安全问题之一。除了少数例外,很少会听说一个加密系统被攻破,这并不是因为大多数的加密系统已经足够安全(我们的分析足以证明这一点),而仅仅是因为在大多数的情况中,寻找与实现有关的漏洞比寻找加密系统的弱点更容易。所以聪明的攻击者并不会在密码攻击方面花心思,因为有比这更简单的途径。

一、创建正确的程序

加密系统实现的核心问题是IT行业不知道如何写出正确的程序或模块(所谓"正确"的程序就是它的运行和规范完全一致),有些原因使得我们在编写正确的程序时会遇到一些困难。

(1)规范

1)需求规范∶需求规范是关于程序应当完成的功能的非正式描述。是回答"我可以做什么"的文档,而不是描述"我如何做某件事"的文档。需求规范更注重对宏观结构的规范,而不注重于具体的实现细节,因此在细节方面有些含糊。
2)功能规范∶功能规范对程序的行为进行详尽的细节定义。功能规范只对那些可以在程序外部度量的部分进行规范。
3)实现设计∶ 这个文件有很多命名,它指定了程序内部的工作方式,包括了所有无法从外部进行测试的部分。一个好的实现设计通常将程序分成几个模块,并对各个模块的功能 I17进行描述。反过来,对这些模块的描述可以看作是模块的需求规范,将模块自身分裂为多个子模块并且重复这三个阶段的工作。

(2)测试和修复

编写出正确程序第二个需要注意的是"测试-修复"这个几乎通用的开发方法。程序员首先写出一个程序,然后测试它是否能正确运行,如果不能。则他们修复程序错误,然后再重新测试。我们都知道,这样也并不能保证一定能得到正确的程序。但可以获得在通常情况下运行正确的程序。

  • 如果发现一个程序中的错误,首先实现一个检测这个错误的测试,并验证它能够检测出这个错误。然后修正这个错误,并确保测试程序不能再检测到这个错误。最后,继续在每一个后续版本上运行这个测试程序,以确保这个错误不再出现。
  • 一旦发现了一个错误,找出引发这个错误的原因,同时全面检查程序。看在程序的其他地方是否还有类似的错误。
  • 对发现的每一个错误进行跟踪。对错误进行简单的统计分析可以显示程序的哪—部分容易有错误,或哪一类错误会经常发生等等,这种反馈对一个质量控制系统来说是必要的。

(3)不严谨的态度

(4)如何着手

现实情况非常复杂,千万不要以为你只需要一个好的程序员或很多次代码审查工作,或是经过ISO 9001认证的开发过程,或者是完全的测试,甚至是以上描述的这些加起来,就足够了。软件的设计非常复杂,不能被简简单单地用一些过程和规则完整描述。我们发现研究航空工业这个现实中最好的工程质量控制系统有一定的指导意义。航空工业的安全系统涉及这个行业里的每一个人,对几乎每一步操作都有非常严格的规则和过程,而且在出现故障的情况下有多种备用的方案。飞机上的每一个螺母和螺钉都必须在使用之前经过飞行质量的考核认定。任何时候只要机修工带螺丝刀上了飞机。他的工作都将由管理员进行监督检查。每一次维修都有详细小心的记录,对任何事故都要进行非常谨慎的调查研究,找出事故发生的原因,然后进行修正。这种质量追求会有很高的成本,但也带来了令人瞩目的效果。今天的飞行工作完全是常规性操作、而每一个故障都可能是致命的、当问题出现时,飞机不可能用刹车停下来,安全返回地面的唯一途径是经过精密的操作降落在世界各地的很少的机场上。航空工业在有效保证飞行安全方面获得了令人瞩目的成绩,我们应尽量从中学到更多的东西。编写正确的程序的成本也许比我们现在要多得多,但与现在全社会为软件中的潜在错误所付出的代价相比,可以肯定地说长期来看这样的成本是划算的。

二、制作安全的软件

标准的实现技术完全不适合于编写安全的代码。

三、保守秘密

你的工作涉及密码学时,你都在和秘密的信息打交道,而这些秘密信息还必须保密。
对于这些安全信道而言,我们有两类秘密信息∶密钥和数据。这两类秘密信息都是瞬时的,不需要长期存储。数据只有在处理每个消息的时候存储,而密钥也仅仅在安全信道的持续期间存储。这就意味着要保证处理秘密信息的软件不能泄露这些秘密。

(1)清除状态

编写安全软件的一个基本规则是,立即将不再使用的数据清除。数据保存的时间越长,其他人获取数据的机会就越大。进一步说,在对存储介质失去控制之前,应该清除里面存储的信息。对于瞬时秘密,这就要求清除内存的特定区域。

(2)交换文件

为了能并行执行更多的程序,多数操作系统(包括当前的 Windows 和UNIX的所有版本)都使用虚拟内存系统。当运行一个程序时,并不是所有的数据都保存在内存里,其中有 122些被存在一个交换文件中,当试图访问那些不在内存中的数据时,程序就被中断,虚拟内存系统从交换文件中读出所需要的数据并放入内存,然后这个程序继续运行。另外,当虚拟内存系统需要更多的可用内存空间时,它将会从被某个程序占用的内存空间中任取一块,并将这个程序写人交换文件。

(3)高速缓冲存储器

高速缓冲存储器保存了一些数据的副本,其中包括秘密数据的副本。这对安全性来说很重要。问题是当我们试图清除秘密数据时,清除操作可能不会正常地发生。在有些系统里,对数据的修改仅仅写在高速缓冲存储器中。而没有写入主内存,只有当高速缓冲存储器需要更多的空间存储其他数据时,数据才最终写入主内存。我们并不知道这些系统的所有细节,它们会随CPU 的不同而变化。我们无法知道在内存分配单元和高速缓冲存储器系统之间是否存在一些交互,可能会导致内存在缓冲存储器被刷新前,内存被释放时,某些清除操作忘了向主内存写入数据。制造商从来不对如何确保数据被清除的问题进行规范,至少我们没有看到这样的规范。只要没有这方面的规范,我们就不能够相信它。

(4)内存保留数据

在内存里简单地重写数据并不能删除数据,这令很多人感到惊奇。具体情况在一定程度上与内存的类型有关,但一般来说如果将数据存储到内存的某一存储单元。这个存储单元会逐渐地"学习"这个数据,当进行重写或关机后,旧数据并没有完全丢失。根据具体环境,仅靠先断电再通电就可以恢复部分或者全部旧数据。如果使用(通常是无记录的)测试模式访问内存,其他内存可以"记住"旧的数据 。
有几种机制可以引发这种现象。如果相同的数据存人 SRAM(静态RAM)内存的同一单元一段时间,那么该数据将变成这个内存单元所优先的通电状态。我们的一个朋友在很久以前使用家用计算机时就遇到了这种现象1。他写了一个BIOS,在一个特殊的内存单元使用了一个特定值以决定重置时是冷启动还是热启动。一段时间后机器在通电后拒绝启动,因为内存已经"学习"到了这个特定值,启动过程将每次的复位都认为是热启动,这样就不对变量进行初始化了,所以启动失败。这种情况的解决方案是交换内存芯片,让SRAM认识不规则的特定值。这个事实告诫人们∶内存保留的数据远比想象的要多得多。

(5)其他程序的访问

在计算机上保存秘密的数据还有另外一个问题∶计算机里的其他程序也可能会访问这些数据。有些操作系统允许不同的程序共享内存。如果其他程序读取了你的密钥,这会产生十分严重的后果。通常来说共享的内存通常必须由两个程序设立,这样降低了风险。在其他情况下,共享的内存可能由于调用共享程序库而被自动设置。
现在操作系统都具有一些可由调试器使用的特性,于是调试器是一个很危险的工具。各种 Windows的版本都允许将一个调试器附加在正在运行的程序上,这样调试器就可以做很多工作,包括从内存中读取数据。在UNIX下,有时可以强迫对程序进行核心转储。核心转储是一个包含程序数据的内存映像的文件,包括了所有的秘密数据

(6)数据完整性

除了要保证数据的保密性外,我们还要保护所存储的数据的完整性。在传输期间我们用MAC来保护数据的完整性,但如果数据可以在内存里进行修改,仍然会有问题。
当然,这是硬件问题,你一般不能为运行应用的计算机指定内存类型。
有些问题既威胁数据的保密性,也威胁数据的完整性。调试器有时可以修改程序的内存,超级用户也可以直接修改内存,对此我们似乎无能为力,但是关注这些问题还是很有意义的。

(7)需要做的工作

在现代计算机上保证数据的保密性并不像听起来那么简单、有很多渠道都可以将秘密消息泄露出去。要使得数据能够完全有效地保密,你必须阻止所有这些泄露途径。很遗憾,现在的操作系统和编程语言都不支持完全阻止这些泄露所需要的功能,使用者必须尽力而为,这要涉及很多工作,而且这些工作都与具体的工作环境有关。
这些问题也使得创建包括密码函数的程序库非常困难。保护秘密数据的安全要涉及对主程序的修改,当然,主程序也处理保密信息,否则它就不需要密码库了。这是我们所熟悉的问题,在考虑与系统每个部分相关的安全性问题时都会遇到

四、代码质量

如果要具体实现一个密码系统,你必将花费大量的时间在代码质量上。本书不是关于编程方面的书,所以我们简单介绍一些代码质量方面的内容。

(1)简洁性

复杂性是安全性的一大敌人。因此,任何安全性设计都要力争做到简洁。在这一点上我们毫不妥协,即使这使得我们不被理解也要如此。去掉所有可去掉的花哨特性,不要采用委员会设计,委员会设计过程为了达到一致性通常会加入额外的特性或选项。在安全性中,简洁性占有首要位置。

(2)模块化

即使已经消除了大量的选项和特性,得到的系统仍然会很复杂。有一种使得这样的复杂系统易于管理的技术就是模块化。模块化就是将系统分成一些模块,然后分别设计、分析、实现每一个模块。
模块化是我们处理复杂系统的唯一有效的方法,因此它很重要。如果一个特殊的选项仅限于单个模块,那么它就可以在这个模块的上下文范围内分析。然而,如果选项改变了这个模块的外在行为,它就可以影响其他的模块。如你有 20个模块。每一个模块有一个二元选项来改变模块的行为,那么就有超过百万种可能的选项组合。为了保证安全性,必须分析每一种组合,这是一个不可能完成的任务。

(3)断言

断言是改善代码质量的有用工具。
当编写密码系统的代码时,要有一种近乎狂热的专业态度。每个模块都不能信任其他模块,并且始终检测参数的有效性,强迫限制调用顺序,并且拒绝执行不安全的操作。大多数时候,这些都是直接的断言。如果模块规范要求在使用一个对象前必须进行初始化,那么在初始化之前使用对象就会产生一个断言错误。断言失败总是导致程序的异常中断,并带有一个文档,用于说明是哪一个断言失败及对应的失败原因。
一般的规则是∶任何时候,在可以对系统内部的一致性进行检查时,就应该增加一项断言。捕捉尽可能多的可以捕捉到的错误,不论是你自己的还是其他程序员的。由断言发现的错误不会导致安全漏洞。

(4)缓冲区溢出

缓冲区溢出这个问题已经持续了几十年,对于彻底解决这个问题的可用方案也具有同样长的历史。一些早期的高级编程语言,如 Algol 60,通过采用强制的数组边界检查就完全解决了这个问题。即使这样,互联网上大量的安全问题都是由缓冲区溢出引发的。当然,也仍然存在着除了缓冲区溢出以外的其他软件攻击,例如格式字符串攻击、整数溢出攻击等。

(5)测试

广泛的测试总是好的开发过程的一部分,测试可以帮助我们发现程序中的隐含错误,但对发现安全漏洞却没什么作用。千万不要将测试与安全性分析混淆,这两者是互补的,但是不同。
应该进行的测试共有两种类型。第一类是由模块功能规范产生的一组通用测试集。理想的方法是让一个程序员实现模块,而让另一个程序员实现测试,两者都按照功能规范工作,如果他们之间出现任何误解,就表明必须对功能规范进行进一步的阐明。通用测试应尽量覆盖模块的所有功能。对有些模块。测试比较简单;对另外一些模块,测试程序必须模拟整个运行环境。在我们自已的很多代码中,测试代码差不多与功能代码一样长,而我们还没有找到一个有效的改进办法。
第二类测试集是由模块程序员自己开发出来的测试程序,用来测试对程序实现的限制。 I31例如,如果一个模块内部使用一个4KB大小的缓冲区,对这个缓冲区的起点和终点进行边界条件的附加测试将有助于捕捉任何缓冲区管理错误。这类测的设计有时需要对模块内部有所了解。

五、侧信道攻击

有一类攻击我们称之为侧信道攻击P2。当攻击者有另外一个关于这个系统的信息通道时,就会发生这类攻击。例如,攻击者可以对系统加密一条消息所需的时间进行详细的测量。根据整个系统的实现方式,攻击者可以通过这样的时间信息来推断消息本身的隐私信息以及底层的密钥信息。对嵌入智能卡的密码系统,攻击者还可以测量智能卡需要的电流随时间的变化。磁场、RF 辐射、功耗与时间以及对其他数据信道上的干扰都可以用于侧信道攻击。
毫无疑问,对那些设计时没有考虑侧信道攻击的系统,侧信道攻击成功的可能性很大。对智能卡进行功率分析非常容易获得成功。
防御各种形式的侧信道攻击即使不是不可能,也是非常困难的,但可以采取一些简单的预防措施。多年以前,当 Niets致力于在智能卡上实现密码系统时,设计规则之一就是让CPU执行的指令序列仅仅依赖于攻击者可能得到的消息。这可以防止时间攻击,并且由于正在执行的指令序列不再泄露任何信息。功率分析攻击也变得更加复杂。这种方法并不能彻底解决 底解决问题,现在的功率分析技术完全可以破解过去的智能卡,尽管如此,这样的修复是目前智能卡所能采取的最好手段。抵抗侧信道攻击的方法是各种应对措施的结合,这些应对措施有些在实现密码系统的软件系统中,有些在硬件系统中。

posted @ 2021-04-04 15:53  Li_Ang  阅读(343)  评论(0编辑  收藏  举报