学习笔记4
第7章 安全信道
安全信道是所有问题最普遍的部分。
7.1 安全信道的性质
可以将安全信道定义为在Alice和Bob之间建立安全的连接,首先对问题进行一定的形式化。
7.1.1 角色
首先,多数的连接都是双向的, Alice给Bob发送消息,Bob也同样会给 Alice发送消息。
通常不想混淆这两个消息流,因此在协议中必然有某种不对称。在实际系统中,可能一方是客户端另一个则是服务器,或者更简单地采用发起者(发起安全连接的一方)和响应者。采用哪一种说法都可以,但必须将 Alice和Bob的角色分配给通信的两方,确保他们知道自己的角色。
当然Eve总是存在的,他会用任何可能的手段对安全信道进行攻击。Eve可以读取Alice和Bob之间的所有通信,并且可以任意操纵这些通信的内容,尤其是Eve可以对正在发送的数据进行删除、插入、修改等操作。
我们可以把 Alice向Bob发送消息想象为两台计算机通过某种网络互相发送消息。另一个有趣的应用是安全地存储数据,如果认为存储数据是向未来发送数据,那么这里的讨论同样适用。 Alice和Bob可能是同一个人,传输介质可能是备份磁带或者USB盘,仍然需要保护这些传输介质的数据安全,防止被窃取和操纵。当然,如果需要发送消息给未来,就无须交互协议,因为未来不会向过去发送消息。
7.1.2 密钥
要实现安全的信道,必须有一个共享的密钥。我们假设Alice和Bob有一个共享的密钥\(K\),其他任何人都不知道这个密钥,这是基本的性质。密码学原语永远不把Aice当作一个人来识别,他们只能识别这个密钥,因此Bob的验证算法只能告诉他这样的事实:这个消息是由知道密钥\(K\)并充当 Alice这个角色的某个人发送的。只有当Bob知道这个密钥\(K\)限于他和Alice知道时,这个结论对Bob才有意义。
我们假设密钥已经建立,并且密钥满足如下要求:
- 只有Alice和Bob知道密钥\(K\)
- 每次安全信道被初始化时,就会更新密钥\(K\)
第二条要求十分重要。如果相同的密钥被反复使用,可能遭受重放攻击。即使以固定的口令作为密钥,在Alice和Bob之间仍然需要密钥协商协议来建立一个合适的唯一密钥\(K\),称为会话密钥,在每次建立安全信道时都必须重新运行这个协议。
安全信道的设计要达到128位的安全性,根据之前的设计准则,需要256位长度的密钥,因此这里密钥\(K\)长度为256位。
7.1.3 消息或字节流
另一个问题是,我们是将Alice和Bob之间的通信看作离散消息序列(如电子邮件)还是连续的字节流(如流媒体)。我们仅仅考虑处理离散消息的系统,它可以很容易地用来处理字节流,这只需将数据流分割为离散的消息,在接收端再组合成字节流就可以了。实际应用上,几乎所有的系统在密码层都使用离散消息系统。
我们还假设实现 Alice和Bob之间传递消息的底层传输系统是不可靠的。从密码学的角度来看,即使是一个可靠的通信协议,如 TCP/IP,也不能构成一个可靠的通信信道,因为攻击者很容易在不打乱数据流的情况下在TCP流中修改、移除或插入数据。仅对数据包丢失这样的随机事件来说,TCP是可靠的,但它不能抵御主动攻击。从攻击对手的角度来看,根本没有可靠的通信协议(这是一个密码学家如何看待世界的很好的例子)。
7.1.4 安全性质
我们可以提出信道的安全性质。Alice发送消息序列\(m_1,m_2,\cdots\),经由安全信道算法处理后发送给Bob。Bob使用安全信道算法处理接收到的信息,得到消息序列\(m_1^{'},m_2^{'},\cdots\)。
必须满足以下性质:
- 除了消息\(m_i\)的长度和发送时间外,Eve无法得到关于消息\(m_i\)的任何其他信息
- 即使Eve通过操纵正在传输的数据对信道进行攻击,Bob接收到的序列\(m_1^{'},m_2^{'},\cdots\)必须是序列\(m_1,m_2,\cdots\)的子序列。并且Bob能够知道收到的是哪一个子序列。
第一个性质是关于隐私性。在理想状态下,Eve不应该得到关于这些消息的任何信息;但是在现实中很难做到。已知的解决方案是要求Alice使用最大带宽发送连续的消息流。
第二个性质确保Bob能够获取正确的顺序排列的准确信息。在理想状态下,我们希望Bob收到的消息序列恰好是 Alice发送的,但现实中没有在密码学意义上可靠的通信协议,Eve总是可以删除传输中的某个消息。由于我们不能防止消息的丢失,Bob就必须能处理仅得到一个消息子序列的情形。
安全信道不会提议重发某条消息,这会使得安全信道的描述变得复杂。消息的确认和重发是标准的通信协议技术,可以在安全信道的上层来实现。
7.2 认证与加密的顺序
我们对消息进行加密与认证,一般有三种方法:先加密,然后再对密文进行认证(加密然后认证):先认证,然后再对消息和MAC值进行加密(认证然后加密);或者同时加密消息和认证数据,然后将两个结果组合(如连接)起来(加密同时认证)。何种方法最优没有定论。
支持采用第一种方法的主要原因有两个。首先,理论结果表明,根据安全加密和认证的某些特定定义,先加密的方案是安全的,而其他方法是不安全的。如果追究这些研究的细节,会发现当加密方案有某个特殊的缺陷时,先认证的方案才是不安全的。在实际系统中,我们从不会使用有这种缺陷的加密方案。
先进行加密的另外一个理由是,它能更有效地丢弃伪造消息。对正常的消息,不管是哪种顺序,Bob都必须解密消息和检查认证。如果消息是伪造的(即有错误的MAC域),Bob将丢弃它。在先加密的方案中,接收端后解密,Bob水远无须解密一个伪造消息,因为在解密之前就可以识别这个伪造消息,从而将它丢弃。但在先认证的方案中,Bob首先必须对消息进行解密,然后再进行认证检查,这样对伪造消息的处理就需要做更多的工作。
支持加密同时认证的主要理由是加密和认证过程可以并行地进行,在某些情况下该方法可以提高性能。采用加密同时认证方法时,攻击者可以获取原始消息的MAC标签,这是因为MAC值没有被加密(不同于先认证后加密模式),而且MAC值也不是从密文计算来的(不同于先加密后认证模式)。MAC是用于认证消息而不是提供隐私性。这意味着应用加密同时认证方法时MAC有可能泄露消息的隐私信息,因而破坏安全信道的隐私性。如同先认证方法,当用在加密同时认证方法中时,某些加密方案同样是不安全的。如果审慎地选用MAC和加密方案,并且将瞬时值等额外数据也作为MAC的输入,加密同时认证方法也是安全的。
支持先认证的主要理由也有两个。在先加密构造中,Eve可以看到MAC的输入和MAC值:而在先认证的构造中,MAC的输入(即明文)和真正的MAC值被隐藏了,Eve只能得到密文和加密后的MAC值,使得对MAC的攻击要比先加密的情况困难得多。实际的选择是两个函数(加密函数和认证函数)中最后应用哪一个。如果最后进行的是加密,那么Eve就试图攻击加密函数;如果最后进行的是认证,Eve就设法攻击认证函数。在许多情况下,认证比加密更重要,因此我们倾向于让加密函数处在Eve直接攻击中,而尽可能地保护MAC。
支持先认证的第二个理由是 Horton原则,即要对消息的含义进行认证,而不是对消息本身进行认证。对密文进行认证破坏了这个原则,从而就产生了缺陷。这个潜在的危害来自于密文可能通过了Bob的认证检测,但他用于解密消息的密钥可能与Alce加密消息所用的密钥不同。于是,尽管通过了认证检测,Bob仍将得到一个与 Alice所发送的消息不同的明文。
7.3 安全信道设计概述
我们的解决方案由三部分组成:消息编号、认证和加密。
7.3.1 消息编号
消息编号对于安全信道的设计十分重要。消息编号可用于为加密算法提供需要的IV;无须存储大量数据即可让Bob拒绝重放的消息;Bob可用来判断在通信过程中丢失了哪些消息;也能保证Bob以正确的顺序接收消息。为此,消息编号必须单调增加(后面的消息必须有较大的消息编号)而且是唯一的(没有两个消息的编号是相同的)。
我们的安全信道设计中采用32位长度的消息编号,第一个消息的编号为1.消息的最大编号为\(2^{32}-1\)。如果消息编号溢出,Aice就必须停止使用现在的密钥,并且再一次运行密钥协商协议生成新的密钥。消息编号必须是唯一的,所以不允许将它重置为0。
我们也可以使用64位的消息编号,但这会增加开销。印为32位的消息编号已经可以满足大多数应用,而且密钥也应该经常更换。
在后面的讨论中,我们用\(i\)来代表消息编号。
7.3.2 认证
我们需要一个MAC作为认证函数,采用HMAC-SHA-256,使用全部256位输出。MAC的输入由消息\(m_i\)和额外的认证数据\(x_i\)组成。Bob将用这些数据解释这个消息的含义,这些数据通常包括协议的标识、协议的版本号、所协商的域的长度等。这里我们只规定安全信道,\(x_i\)的实际值由应用的其他部分来提供,\(x_i\)就像一个位串,而且Alice和Bob应该拥有同样的\(x_i\)。
设\(l(.)\)是一个返回数据串长度的函数(以字节为单位),MAC值\(a\)可以计算如下:
其中\(i\)与\(l(x_i)\)都是32位的无符号整数。\(l(x_i)\)确保可以将\(i||l(x_i)||x_i||m_i\)唯一地分解成各个对应的域;如果没有\(l(x_i)\),会导致认证出现歧义。
7.3.3 加密
安全信道设计的加密算法使用CTR模式下的AES,根据之前的内容,CTR模式因为使用瞬时值会带来风险。然而,我们的安全信道将瞬时值的处理内嵌,从不将生成瞬时值的控制交给任何其他方。我们采用消息编码作为CTR模式需要的唯一瞬时值,所以我们的安全信道使用CTR模式。
我们将每个消息的长度限制为不超过\(16\times2^{32}\)个字节,即分组计数器限制为32位,因为32位的计数器在很多平台上都更容易实现。
密钥流由\(k_0,k_1,\cdots\)组成,对瞬时值为\(i\)的消息,密钥流定义为:
这里每个明文分组由32位的分组编号、32位的消息编号以及64位的0组成。密钥流很长,我们仅仅使用最初的\(l(m_i)+32\)个字节,将\(m_i\)与\(a_i\)串联起来后与\(k_0,k_{l(m_i)+31}\)进行异或运算。
7.3.4 组织格式
我们不能将\(m_i||a_i\)加密后就发送,因为Bob也需要知道消息编号。最后发送的消息将由编码为32位整数的\(i\)(其中最低有效字节在前)与加密后的\(m_i\)和\(a_i\)组成。
7.4 详细设计
现在来讨论安全信道设计的具体细节,方便起见,我们将信道定义为双向的,因此相同的密钥将双向使用。
下面使用伪代码来描述算法。
7.4.1 初始化
信道数据初始化算法包含两个主函数:建立密钥和建立消息编号。我们从信道密钥导出4个附属密钥:Alice给Bob发送消息所用的加密密钥和认证密钥,Bob给Alice发送消息所用的加密密钥和认证密钥。
还有一个用于清除状态信息\(S\)的函数,它的功能就是清除存储状态信息\(S\)的内存。由于密钥在这里存储,清除这个区域的信息是至关重要的。在许多系统中,仅仅释放内存空间并不意味着清除了状态信息,因此在使用完后必须删除\(S\)。
7.4.2 发送消息
现在来讨论发送消息的处理过程。这个算法以会话状态、要发送的消息以及用于认证的附加数据为输入,输出准备发送的已加密和认证的消息。接收者必须拥有相同的附加数据以进行认证检查。
7.4.3 接收消息
接收消息算法的输入有 SendMessage算法计算出来的经加密和认证后的消息,以及用于认证的附加数据x。我们假定接收者通过某种带外(out-of-band)的方式已获取了x,例如如果x包含协议版本号,那么如果Bob参与了协议,他必然知道该值。
这里使用操作的标准顺序。你可以在解密之前对消息编号进行检查,但是如果\(i\)在传输过程中被破坏了,这个函数将会返回一个错误的出错信息。
另外,在认证码被验证之前,ReceiveMessage函数不能泄露关于密钥流和明文消息的任何信息。如果认证失败,返回一个出错提示,但密钥流和明文不能被泄露。在实际实现中应清除存储这些元素的内存区域。
7.4.4 消息的顺序
与发送者一样,接收者通过修改MsgCntRec变量来更新状态\(S\)。接收者要确保接收的消息编号严格地递增,这可以保证任何一个消息都不会被接收两次,但如果消息流在传输时改变了顺序,一些有效的消息将会丢失。
在大多数情况下,我们选择修复传输层来防止消息顺序颠倒的情况发生,因此安全信道不必处理这些事情。
当使用安全协议IPsec时,可以允许消息以任意的顺序到达,因为IPsec不仅仅保存收到的最后一个消息的计数器值,也保留了一个重放保护的窗口。
还有另一种选项是,当发生丢包时,自动终止通信。这尤其适合于当安全信道运行于可靠传输协议之上时,例如TCP。除非受到恶意攻击,消息都会按顺序到达并且没有丢包。所以在这种方法中,一旦出现丢包和到达顺序颠倒,就自动终止通信。
7.5 备选方案
在嵌入式硬件中实现安全通信时,使用SHA-256的开销非常高,所以也需要其他一些可以同时提供隐私性和可认证性的密码模式作为备选。
类似于CBC模式和 CBC-MAC,这些专用于隐私性和认证性的分组密码工作模式只有一个密钥作为输入。这些模式一般都是以消息作为输入,加上用于认证的附加消息以及瞬时值,但是这些模式不像使用相同密钥的CBC模式和 CBC-MAC一样简单,因为在常规加密和常规MAC中采用相同密钥会带来安全问题。
最知名、最早的组合模式为OCB模式。这个模式的效率非常高,每一个明文分组都并行地进行处理,这对高速硬件是非常有吸引力的。但是由于一些专利的存在限制了OCB模式的应用。
CCM模式是一个将CTR模式加密和CBC-MAC认证结合起来的方案,通过仔细设计使得在CTR模式和 CBC-MAC中使用相同密钥。与OCB相比,CCM需要两倍的计算量去加密和认证一个消息。目前CCM的安全性已经得到证明,而且NIST已经将CCM模式作为一个分组密码模式进行标准化。
为改善CCM的效率,后来由提出了CWC模式。CWC基于CTR模式来提供加密功能,在底层采用全域散列函数来实现认证。CWC的改进模式称为GCM,NST已经将GCM模式作为分组密码进行标准化。
第8章 实现上的问题Ⅰ
本章将讨论有关实现的问题。
最大的问题,仍然是木桶原理的最脆弱环节,在实现的层面很容易就会将安全性破坏或者复杂化。事实上,实现上的错误,比如缓冲区溢出,仍然是目前为止在实际系统中最大的安全问题之一。除了少数例外,很少会听说一个加密系统被攻破,这并不是因为大多数的加密系统已经足够安全,而仅仅是因为在大多数的情况中,寻找与实现有关的漏洞比寻找加密系统的弱点更容易。
本章我们将关注加密系统的运行环境。系统的每一部分都会影响它的安全性,如果要很好地完成一项工作,整个系统必须从最底层开始设计,并将安全性作为主要目标,而不仅仅是在设计时注意安全性。我们这里所说的系统是一个广泛的概念,任何有可能损害系统安全性的因素都将包括在内。
操作系统是最主要的部分,但是到目前为止,已经被广泛使用的操作系统都不是以安全性为主要目标的。操作系统具有很明显的多样性,包括与桌面计算机交互的操作系统到嵌入式设备和手机上的操作系统。由此可以得出的逻辑上的结论是,要实现安全的系统是不可能的。
尽管要实现我们所需的安全等级是几乎不可能的,但当我们设计加密系统时,仍然要尽最大努力保证我们设计的部分是安全的。另外一个重要原因也是之前所提到的,对加密系统的攻击具有巨大的危害性,因为这种攻击可以是不可见的。
8.1 创建正确的程序
8.1.1 规范
在程序规范的过程中,有以下三个阶段:
- 需求规范: 需求规范是关于程序应当完成的功能的非正式描述。是回答“我可以做什么”的文档,而不是描述“我如何做某件事”的文档。需求规范更注重对宏观结构的规范,而不注重于具体的实现细节,因此在细节方面有些含糊。
- 功能规范: 功能规范对程序的行为进行详尽的细节定义。功能规范只对那些可以在程序外部度量的部分进行规范。功能规范应该是完整的,也就是说,每一个功能都要进行规范说明,所有未在功能规范中说明的功能不必实现。从另外一个角度看,功能规范是测试已经完成的程序的基础,规范中的任意一项都可以并且也应该被测试。
- 实现设计: 它指定了程序内部的工作方式,包括了所有无法从外部进行测试的部分。一个好的实现设计通常将程序分成几个模块,并对各个模块的功能进行描述。反过来,对这些模块的描述可以看作是模块的需求规范,将模块自身分裂为多个子模块并且重复这三个阶段的工作。
在这三个阶段中,功能规范是最重要的一个,当程序完成后,就需要根据功能规范对程序进行测试。
8.1.2 测试和修复
编写出正确程序第二个需要注意的是“测试一修复”这个几乎通用的开发方法。程序员首先写出一个程序,然后测试它是否能正确运行,如果不能,则他们修复程序错误,然后再重新测试。
关于程序中的错误有一些简单的规则:
- 如果发现一个程序中的错误,首先实现一个检测这个错误的测试,并验证它能够检测出这个错误。然后修正这个错误,并确保测试程序不能再检测到这个错误。最后,继续在每一个后续版本上运行这个测试程序,以确保这个错误不再出现。
- 一旦发现了一个错误,找出引发这个错误的原因,同时全面检查程序,看在程序的其他地方是否还有类似的错误
- 对发现的每一个错误进行跟踪,对错误进行简单的统计分析可以显示程序的哪一部分容易有错误,或哪一类错误会经常发生等等,这种反馈对一个质量控制系统来说是必要的。
8.1.3 不严谨的态度
所面临的第三个问题是计算机行业里,人们对程序中的错误有着不可置信的不严谨的态度,程序中的错误被认为是自然而然的事情。
8.1.4 如何着手
软件的设计非常复杂,不能被简简单单地用一些过程和规则完整描述。编写正确的程序的成本也许比我们现在要多得多,但与现在全社会为软件中的潜在错误所付出的代价相比,可以肯定地说长期来看这样的成本是划算的。
8.2 制作安全的软件
上面的讨论都是关于正确的软件,但是正确的软件和安全的软件是有差别的。安全的软件还有一个额外的需求:缺失某种功能,无论攻击者采取什么措施,它都不可能做某件事情。由于没有个有效的方法可以检测软件的安全性,这使得编写安全的软件比编写正确的软件更加困难。我们也可以得到如下结论:
8.3 保守秘密
对于这些安全信道而言,我们有两类秘密信息:密钥和数据。这两类秘密信息都是瞬时的,不需要长期存储。数据只有在处理每个消息的时候存储,而密钥也仅仅在安全信道的持续期间存储。这里我们将讨论瞬时私密的存储。
8.3.1 清除状态
编写安全软件的一个基本规则是,立即将不再使用的数据清除。数据保存的时间越长,其他人获取数据的机会就越大。进一步说,在对存储介质失去控制之前,应该清除里面存储的信息。对于瞬时秘密,这就要求清除内存的特定区域。
秘密的数据还可能在其他的存储单元中出现,所有的数据最后都进入CPU的寄存器。清除寄存器的内容在大多数编程语言中是不可能的,但对x86这样寄存器饥饿的CPU来说,任何数据都不可能在寄存器中存在很长时间。
8.3.2 交换文件
为了能并发执行更多程序,多数操作系统都使用虚拟内存系统。当然,并非所有虚拟内存系统会采取有效措施来保护数据的保密性,或者在将数据写入磁盘前对其进行加密。多数软件是为了一种合作性的运行环境而设计的,而不是为密码学家所考虑的那种存在攻击的运行环境。
如何解决这个问题?在有些操作系统上,我们可以使用系统调用,通知虚拟内存系统使指定的部分内存不被交换出去。一些操作系统支持安全交换数据,交换的数据受到密码保护,但是这些系统可能需要用户去修改一些系统配置选项。
所有的内存空间都可以保存秘密数据,假定我们可以锁定内存以阻止其进行交换,那么应该锁定哪些内存呢?这就带来了第二个问题。在许多编程环境里,获悉数据的精确存储位置很困难,对象常常分配在堆里,数据可以静态分配存储空间,而局部变量存放在栈里。详细的处理很复杂,也非常容易出错。最好的解决方案也许是锁定占用的所有内存,但其实并非易事。因为这样就可能会丧失操作系统提供的很多服务,如自动分配栈,而且此时虚拟内存系统不再有效。
更合适的解决方案是创建能保护数据保密性的虚拟内存系统,这需要改变操作系统,因此这并不在我们的能力范围之内。
8.3.3 高速缓冲存储器
高速缓冲存储器保存了一些数据的副本,其中包括秘密数据的副本,这对安全性来说很重要。问题是当我们试图清除秘密数据时,清除操作可能不会正常地发生。在有些系统里,对数据的修改仅仅写在高速缓冲存储器中,而没有写入主内存,只有当高速缓冲存储器需要更多的空间存储其他数据时,数据才最终写入主内存。我们对于这些细节是不了解的,因此清除数据也比较困难。
高速缓冲存储器的第二个风险是,在某些环境下,高速缓冲存储器知道特定的内存单元已经被修改,可能是被多CPU系统中其他CPU修改的。然后,高速缓冲存储器将其为这个内存单元准备的数据标记为“无效”,实际数据则没有被清除,于是没有被清除的秘密数据的副本就又一次有存在的可能。
8.3.4 内存保留数据
在内存里简单地重写数据并不能删除数据,这令很多人感到惊奇。具体情况在一定程度上与内存的类型有关,但一般来说如果将数据存储到内存的某一存储单元,这个存储单元会逐渐地“学习”这个数据,当进行重写或关机后,旧数据并没有完全丢失。根据具体环境仅靠先断电再通电就可以恢复部分或者全部旧数据。
这是非常严重的威胁。目前已经有一种被称为冷启动攻击的方法出现,研究者可以在重新启动计算机后将内存中的密钥恢复。当然,也可以物理拆除一台计算机上的内存条,装入另一台计算机,然后进行密钥恢复。
对于上面的问题,可以考虑Boojum方案,这适用于相对较少数据量的清除,例如密钥。
8.3.5 其他程序的访问
在计算机上保存秘密的数据还有另外一个问题:计算机里的其他程序也可能会访问这些数据。有些操作系统允许不同的程序共享内存。如果其他程序读取了你的密钥,这会产生十分严重的后果。通常来说共享的内存通常必须由两个程序设立,这样降低了风险。在其他情况下,共享的内存可能由于调用共享程序库而被自动设置。
另外一个危险来自于有特权的用户,称为超级用户或管理员。这些用户可以在计算机上访问一般用户不能访问的东西。例如,在UNIX下,超级用户可以读取任意内存里的数据。
8.3.6 数据完整性
除了要保证数据的保密性外,我们还要保护所存储的数据的完整性。在传输期间我们用MAC来保护数据的完整性,但如果数据可以在内存里进行修改,仍然会有问题。因为调试器有时可以修改程序的内存,超级用户也可以直接修改内存。
8.4 代码质量
8.4.1 简洁性
复杂性是安全性的一大敌人,因此,任何安全性设计都要力争做到简洁。
安全信道就是一个典型的例子。它没有选项,不允许只对数据进行加密而不认证,也不允许对未加密的数据进行认证。人们总是希望得到这些特性,但他们并不知道使用这些特性导致的安全性方面的后果。大多数用户对安全性的理解并不足以使他们有能力对安全性选项做出正确的选择,最好的方法是不要任何选项,确保默认的安全性。如果一定要有选项,就只提供单项选择:安全的或不安全的。
许多系统也有多个密码套件,用户可以选择使用加密函数或认证函数,如果有可能的话,就消除这种复杂性,选择对所有可能的应用都足够安全的单一模式。各种不同的加密模式在计算方面的差异不大,而且对现代计算机来说密码系统也很少成为瓶颈。
8.4.2 模块化
即使已经消除了大量的选项和特性,得到的系统仍然会很复杂。有一种使得这样的复杂系统易于管理的技术就是模块化。模块化就是将系统分成一些模块,然后分别设计、分析实现每一个模块。
在保证密码系统的正确性方面,正确的模块化起到了非常重要的作用。以前我们把加密原语作为模块,模块的接口应尽量简单易懂,它的行为应该和用户合理的预期一致。
模块化是我们处理复杂系统的唯一有效的方法,因此它很重要。如果一个特殊的选项仅限于单个模块,那么它就可以在这个模块的上下文范围内分析。然而,如果选项改变了这个模块的外在行为,它就可以影响其他的模块。
8.4.3 断言
断言是改善代码质量的有用工具。
一般的规则是:任何时候,在可以对系统内部的一致性进行检查时,就应该增加一项断言。捕捉尽可能多的可以捕捉到的错误,不论是你自己的还是其他程序员的。由断言发现的错误不会导致安全漏洞。
8.4.4 缓冲区溢出
缓冲区溢出的问题已经持续了几十年,对于解决这个问题的方案也有同样长的历史。当然也存在着除了缓冲区溢出以外的其他软件攻击,例如格式字符串攻击、整数溢出攻击等。
这些都不是我们所能改变的,但我们可以提出一些建议:不要使用允许缓冲区溢出的编程语言;无论使用哪种语言,都不要忘记对数组边界的检查。
8.4.5 测试
广泛的测试总是好的开发过程的一部分,测试可以帮助我们发现程序中的隐含错误,但对发现安全漏洞却没什么作用。千万不要将测试与安全性分析混淆,这两者是互补的,但是不同。
应该进行的测试共有两种类型。第一类是由模块功能规范产生的一组通用测试集;第二类测试集是由模块程序员自己开发出来的测试程序,用来测试对程序实现的限制。
8.5 侧信道攻击
有一类攻击我们称之为侧信道攻击。当攻击者有另外一个关于这个系统的信息通道时,就会发生这类攻击。例如,攻击者可以对系统加密一条消息所需的时间进行详细的测量。根据整个系统的实现方式,攻击者可以通过这样的时间信息来推断消息本身的隐私信息以及底层的密钥信息。对嵌入智能卡的密码系统,攻击者还可以测量智能卡需要的电流随时间的变化。磁场、RF辐射、功耗与时间以及对其他数据信道上的干扰都可以用于侧信道攻击。
毫无疑问,对那些设计时没有考虑侧信道攻击的系统,侧信道攻击成功的可能性很大。对智能卡进行功率分析非常容易获得成功。
在现实生活中,情况并没有那么糟,因为大多数侧信道攻击都难以执行。侧信道攻击对智能卡构成了真正的威胁,因为智能卡完全在对手的控制下,但是仅有很少几种侧信道攻击对于大多数其他计算机是有效的。实际上,最重要的侧信道攻击是时间攻击和RF辐射攻击(智能卡也容易受到关于功耗测量的攻击)。
8.6 其他
安全性不只与密码系统的设计有关,而是和系统的各个方面都有相关,它们各尽其责以实现系统的安全性。
加密系统的实现本身是一门艺术,最关键的问题是代码质量问题。低质量的代码是现实中受到攻击的最常见的原因,但这也相当容易避免。