第七八章学习笔记

一、知识点归纳

7.1安全信道的性质

7.1.1角色

首先,多数的连接都是双向的,Alice给 Bob发送消息,Bob也同样会给 Alice 发送消息。通常不想混淆这两个消息流,因此在协议中必然有某种不对称。在实际系统中,可能一方是客户端另一个则是服务器,或者更简单地采用发起者(发起安全连接的一方)和响应者。采用哪一种说法都可以,但必须将 Alice 和Bob的角色分配给通信的两方,确保他们知道自己的角色。

7.1.2密钥

实现安全的信道,必须有一个共享的密钥。对密钥有如下要求∶

  • 只有 Alice 和 Bob 知道密钥 K。

  • 每次安全信道被初始化时,就会更新密钥 K。

以上第二条要求十分重要。如果相同的密钥被反复使用,之前会话的消息就能向 Alice

或 Bob重放,由此引起各种混淆。因此,即使以固定的口令为密钥,在Alice和Bob之间仍然需要密钥协商协议来建立一个合适的唯一密钥K,在每次建立安全信道时都必须重新运行这个协议。

7.1.3消息或字节流

另一个问题是,我们是将 Alice 和 Bob之间的通信看作离散消息序列(如电子邮件)还是连续的字节流(如流媒体)。我们仅仅考虑处理离散消息的系统,它可以很容易地用来处理字节流,这只需将数据流分割为离散的消息,在接收端再组合成字节流就可以了。实际应用上,几乎所有的系统在密码层都使用离散消息系统。

我们还假设实现 Alice 和 Bob之间传递消息的底层传输系统是不可靠的。从密码学的角度来看,使是一个可靠的通信协议,如TCP/IP,也不能构成一个可靠的通信信道,因为攻击者很容易在不打乱数据流的情况下在TCP流中修改、移除或插入数据。仅对数据包丢失这样的随机事件来说,TCP是可靠的,但它不能抵御主动攻击。从攻击对手的角度来看,根本没有可靠的通信协议(这是一个密码学家如何看待世界的很好的例子)。

7.1.4安全性质

Alice发送消息序列m;,mz。…,经由安全信道算法处理后发送给 Bob,Bob 用安全信道算法处理接收到的消息,得到消息序列m',m',…。必须满足以下性质;

  • 除了消息m,的长度和发送时间外,Eve无法得到有关消息m,的任何其他信息

  • 即使 Eve通过操纵正在传输的数据对信道进行攻击,Bob接收到的消息序列m',m',…必须是序列m,m2,…的子序列。并且 Bob 很清楚地获悉他收到的是哪一个子序列(子序列就是从原序列中去掉0个或更多的元素后得到的序列)。

第一个性质是关于隐私性。

第二个性质确保 Bob 能够获取以正确的顺序排列的准确消息。

7.2认证与加密的顺序

显然,我们将同时对消息进行加密和认证。有三种方法可供选择∶先加密,然后再对密文进行认证(加密然后认证);先认证,然后再对消息和MAC值进行加密(认证然后加密);或者同时加密消息和认证数据,然后将两个结果组合(如连接)起来(加密同时认证)。何种方法最优没有定论。

支持采用第一种方法的主要原因有两个。首先,理论结果表明,根据安全加密和认证的某些特定定义。先加密的方案是安全的,而其他方法是不安全的。如果追究这些研究的细节,会发现当加密方案有某个特殊的缺陷时,先认证的方案才是不安全的。在实际系统中,我们从不会使用有这种缺陷的加密方案。然而,这些较弱的加密方案满足某个特殊的安全性[102]的正式定义。对这个弱的加密方案的密文应用MAC就修复了这个缺陷,从而使其变得安全了。

先进行加密的另外一个理由是,它能更有效地丢弃伪造消息。对正常的消息,不管是哪种顺序。Bob 都必须解密消息和检查认证。如果消息是伪造的(即有错误的MAC域),Bob将丢弃它。在先加密的方案中,接收端后解密,Bob 永远无须解密一个伪造消息,因为在解密之前就可以识别这个伪造消息,从而将它丢弃。但在先认证的方案中,Bob 首先必须对消息进行解密,然后再进行认证检查,这样对伪造消息的处理就需要做更多的工作

支持加密同时认证的主要理由是加密和认证过程可以并行地进行,在某些情况下该方法可以提高性能。采用加密同时认证方法时,攻击者可以获取原始消息的 MAC标签,这是因为 MAC 值没有被加密(不同于先认证后加密模式),而且MAC值也不是从密文计算来的(不同于先加密后认证模式)。MAC 是用于认证消息而不是提供隐私性。这意味着应用加密同时认证方法时 MAC有可能泄露消息的隐私信息,因而破坏安全信道的隐私性。

7.3安全信道设计概述

7.3.1消息编号

息编号对于安全信道的设计十分重要。消息编号可用于为加密算法提供需要的IV;无须存储大量数据即可让 Bob拒绝重放的消息;Bob可用来判断在通信过程中丢失了哪些消息;也能保证Bob以正确的顺序接收消息。为此,消息编号必须单调增加(后面的消息必须有较大的消息编号)而且是唯一的(没有两个消息的编号是相同的)。

指定消息编号非常容易,Alice将第一个消息编号为1,将第二个消息编号为2,依次类推。Bob 记录他收到的最后一个消息的编号。任意—个新消息对应的消息编号要大于之前消息的消息编号,只接受逐渐增大的消息编号,Bob 能确保 Eve无法向他重放旧消息。

7.3.2认证

我们需要一个MAC作为认证函数。可能与读者所想的一样,我们将采用HMAC~SHA-256,使用它全部256 位输出。MAC的输入由清息m,和额外的认证数据x,组成,正如我们在第6章所解释的,一些上下文数据必须包含在认证中。Bob将用这些数据解释这个消息的含义,这些数据通常包括协议的标识、协议的版本号、所协商的域的长度等。这里我们只规定安全信道,x的实际值必须由应用的其余部分来提供。在我们看来x;就是一个位串,而且 Alice和 Bob 应有相同的x,。

设/)为一个返回数据串长度(以字节为单位)的函数,MAC值a可以计算如下∶

a:=MAC(i(x)l.m)

其中i与l(x)都是32 位的无符号整数(即最低有效字节在前的格式)。(x)确保可以将非(x)m,唯一地分解成各个对应的域。如果没有l(x),将会有很多方式将它分割为i、x和m,这将导致认证会出现歧义。当然,对x,进行编码的方式应使得无须更多上下文信息即可解析为不同的域,但这并不是在这个层面需要的要求,使用安全信道的应用必须保证这一点。

7.3.3加密

安全信道设计的加密算法使用CTR模式下的AES。我们采用消息编码作为CTR模式需要的唯一瞬时值,所以我们的安全信道使用CTR模式。但是我们仍然不会将生成瞬时值的生成交外部系统,建议不要直接使用 CTR 模式。

我们将每个消息的长度限制为不超过 16×2°个字节,即分组计数器限制为32 位。当然,我们也可以使用64位的计数器、但是32位的计数器在许多平台上都更易于实现,而且对大多数应用来说也不需要处理那么大的信息。

密钥流由k,k,…组成,对瞬时值为i的消息,密钥流定义为∶

k,k-_

= E(K,0lliO)E(K,1iO)I…IE(K,2-1liO)

这里每个明文分组由32 位的分组编号、32位的消息编号以及64 位的0组成。密钥流很长,我们仅仅使用最初的/m)+32个字节(不必计算密钥流的其他字节),将m,与a,串联起来后与k,…,k0mon5进行异或运算。

7.3.4组织格式

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

7.4详细设计

我们将信道定义为双向的,因此相同的密钥将双向使用。如果我们将信道定义为单向的,可以肯定有人在两个方向使用相同的密钥,最终会破坏安全性。采用双向信道可以降低这样的风险。另一方面,如果你正在使用他人定义的安全信道,那要格外谨慎不要在双向使用相同的密钥。

7.4.1初始化

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

函数 InitializeSecureChannel

输入∶ K 256 位的信道密钥

R 角色说明,确定参与方是 Alice 还是 Bob

输出∶ S 安全信道的状态

首先计算所需要的4个密钥。这4个密钥是没有长度或以零终止的ASCII字符串。

KeySendEnc - SHA-256(K"Enc Alice to Bob")

KeyRecEnc-SHA-256(K"Enc Bob to Alice")

KeySendAuth -SHAF256(K"Auth Alice to Bob")

KeyRecAuth←SHA-256(K"Enc Bob to Alice")

如果参与方是 Bob,交换加密密钥和解密密钥。

if R= "Bob" then

swap(KeySendEng, KeyRecEnc)

swap (KeySendAuth, KeyRecAuth)

fi

将发送和接收的计数器置0。发送计数器是发送的最后一个消息的编号,接收计数

器是接收的最后一个消息的编号。

(MsgCntSend,MsgCntRece) -(0,0)

将状态打包

-(KeySendEne,

KeyRecEnc,

KeySendAuth,

KeyReAuth,

MsgCntSend,

MsgCntRec)

return S

7.4.2发送消息

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

函数 SendMessage

输入∶S 安全会话状态

m 要发送的消息

x 用 于认证的附加数据

输出∶ r 发送给接收者的数据

首先检查消息编号并更新。

assert MsgCntSend < 212- 1

MsgCntSend +- MsgCotSend + 1

7 - MsgCntSend

计算认证码,l(x)和i 都用 4 个字节编码,最低有效字节在前。

at-HMAC-SHA-256(KeySendAuth, il(x)xlm)

1-ma

生成密钥流。分组密码的每个明文分组由4个字节的计数器、4个字节的i以及8

个全0字节组成,整数的最低有效字节在前。E是使用 256 位密钥的AES 加密

算法。

K -KeySendEnc

k ←Ee(0i0)【Ee(1i1l0)·…

形成最终的文本。i用 4个字节编码,最低有效字节在前。

←- il(( ① First-((r)-Bytes(k))

return r

注意由于MsgCntSend被修改了,因此会话状态要更新。需要再次强调的是∶这是至关重要的,因为消息编号必须唯一。事实上,这些算法中的每部分对于安全性都是至关重要的。

7.4.3接收消息

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

函数 ReceiveMessage

输入∶S 安全会话状态

接收者收到的数据

x 用于认证的附加数据

输出∶ m 实际发送的消息

接收者收到的信息必须至少包含有4个字节的消息编号和 32 个字节的 MAC域,这

个检查保证以后对消息的分割可正常进行。

assert (1) ≥ 36

将t分割为i、加密的消息和认证码。分割是确定的,因为i总是占用4个字节

i1--1

生成密钥流,过程如同发送者。

K--KeyRecEne

k-EA((OilO)Ex(1i0) -

解密消息和 MAC域并进行分割。分割是确定的,因为a总是占用 32个字节。

ma t- ④ First-(r)-Bytes(k)

重新计算认证码。(x)和i都用 4个字节编码,最低有效字节在前。

a'-HMAC-SHA-256(KeyRecAuth,ill(x)xm)

if a'≠ a then

destroy k,m

return AuthenticationFailure

else if i ≤MsgCntRec then

destroy k, m

return MessageOrderError

fi

MsgCntRec -i

return

这里使用操作的标准顺序。你可以在解密之前对消息编号进行检查,但是如果i在传输

过程中被破坏了,这个函数将会返回一个错误的出错信息。

7.4.4消息的顺序

与发送者一样,接收者通过修改 MsgCntRec变量来更新状态S。接收者要确保接收的消息编号严格地递增,这可以保证任何一个消息都不会被接收两次,但如果消息流在传输时改变了顺序,一些有效的消息将会丢失。

还有另外一种情况是,由于某种原因接收者允许消息以任意的顺序到达。对 IP包进行加密和认证的IP安全协议IPsee51就是这种情况。因为在传输中IP包可以重新排列,而且所有使用IP的应用也了解这个性质,IPsee 不仅仅保存收到的最后一个消息的计数器值,而且要保留一个重放保护的窗口。如果c是收到的最后一个消息的编号,那么,IPsee 就为消息编号c-31,c-30,c-29,…,c-1,c维护一个位图,其中的每一位都表示与这个消息编号所对应的消息是否已被收到。编号比c-31小的消息总是被拒绝,而对编号在c-31与c-1之间的消息,只要对应的位为0就被接收(接收后对应位置为1)。如果消息的编号比c大,更新c,同时位图移位来维持不变性。这样的位图构造允许消息的顺序在有限范围内发 内发生颠倒,接收者无须增加过多状态。

7.5备选方案

类似于CBC模式和CBC-MAC,这些专用于隐私性和认证性的分组密码工作模式只有一个密钥作为输入。这些模式一般都是以消息作为输入,加上用于认证的附加消息以及瞬时值。但是这些模式不像使用相同密钥的 CBC 模式和 CBC-MAC一样简单,因为在常规加密和常规 MAC 中采用相同密钥会带来安全问题。

最知名、最早的组合模式为 OCB模式。由于 OCB 模式的专利问题,也因为需要一种专用的用于加密和认证的单密钥分组模式,

Doug Whiting、Russ Housley 和Niels提出了CCM模式。这是一个将 CTR模式加密和CBC-MAC认证结合起来的方案,通过仔细设计使得在 CTR 模式和 CBC-MAC中使用相同密钥。与 OCB 相比,CCM需要两倍的计算量去加密和认证一个消息,但是据我们所知目前CCM还没有发布专利,而且设计者也知道没有涉及 CCM的专利,他们也不会去申请专利。Jakob Jonsson 给出了CCM安全性的一个证明啊,而且NIST已经将CCM模式作为一个分组密码模式进行标准化 为了改善CCM的效率,Doug Whiting、John Viega 和 Yoshi提出了另一种模式,称为WC。CWC基于 CTR模式来提供加密功能,在底层采用全域散列函数来实现认证12,在我们第6章中讨论 GMAC时曾经提到了全域散列函数但并没有深入讨论。类似于OCB 模式,CWC 模式对全域散列函数的使用使得 CWC可以完全并行化,但是不存在专利问题。采用一个在硬件实现更加有效的全域散列函数,David MeGrew 和John Viega 改进了CWC,这个改进模式被称为 GCM4】、NIST已经将 GCM模式作为分组密码进行标准化。

另一个要点是∶在OCB、CCM、CWC、GCM 以及其他一些类似模式本身无法实现完全的安全信道,它们只提供了加密/认证功能,并且每个包需要一个密钥和唯一的瞬时值。我们曾在4.7节中讨论依赖外部系统生成瞬时值的风险,然而很容易做到不采用单独的MAC 和加密函数,就能让我们的安全信道算法使用其中一个分组密码模式,这时只需要两个密钥,而不是在安全信道初始化函数 InitializeSecureChannel中生成的四个辅助密钥。每个方向有一个密钥,而瞬时值可以通过对消息编号进行填充来构造。

安全信道是密码学最有用的应用之一,用于几乎所有的密码系统中。可以从性能良好的加密和认证原语来构造安全信道,同时还有专用的隐私性-认证性分组密码模式可供使用,但是有很多细节需要注意并要加以正确处理。我们后面还会考虑另一个挑战,即对称密钥的创建。

8.1创建正确的程序

8.1.1规范

在程序规范的过程中,有以下三个阶段;

1)需求规范∶需求规范是关于程序应当完成的功能的非正式描述。是回答"我可以做什么"的文档,而不是描述"我如何做某件事"的文档。需求规范更注重对宏观结构的规范,而不注重于具体的实现细节,因此在细节方面有些含糊。

2)功能规范∶功能规范对程序的行为进行详尽的细节定义。功能规范只对那些可以在程序外部度量的部分进行规范。对功能规范中的每一项内容,首先要自问能否对已完成的程序建立一个测试,以确定该项内容成立与否。这个测试可以仅使用程序的外部行为,而与内部的运行无关。如果不能为规范中的某一项进行这样的测试,那么该项就不应该属于功能规范。功能规范应该是完整的。也就是说,每一个功能都要进行规范说明,所有未在功能规范中说明的功能不必实现。

从另外一个角度看,功能规范是测试已经完成的程序的基础,规范中的任意一项都可以并且也应该被测试。

3)实现设计∶ 这个文件有很多命名,它指定了程序内部的工作方式,包括了所有无法从外部进行测试的部分。一个好的实现设计通常将程序分成几个模块,并对各个模块的功能 进行描述。反过来,对这些模块的描述可以看作是模块的需求规范,将模块自身分裂为多个子模块并且重复这三个阶段的工作。

8.1.2测试和修复

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

关于程序中的错误有一些简单的规则,这些规则可以在任意一本软件工程方面的书中找到∶

  • 如果发现一个程序中的错误,首先实现一个检测这个错误的测试,并验证它能够检

    测出这个错误。然后修正这个错误,并确保测试程序不能再检测到这个错误。最后,

    继续在每一个后续版本上运行这个测试程序,以确保这个错误不再出现。

  • 一旦发现了一个错误,找出引发这个错误的原因,同时全面检查程序。看在程序的

    其他地方是否还有类似的错误。

  • 对发现的每一个错误进行跟踪。对错误进行简单的统计分析可以显示程序的哪—部

    分容易有错误,或哪一类错误会经常发生等等,这种反馈对一个质量控制系统来说

    是必要的。

8.1.3不严谨的态度

所面临的第三个问题是计算机行业里,人们对程序中的错误有着不可置信的不严谨的态度,程序中的错误被认为是自然而然的事情。如果你的文字处理系统突然崩溃,一天的工作全部丢失,大家都认为这是相当正常的,也是可以接受的。通常他们都会指责用户说∶"你应当随时保存所有的工作。"软件公司也会发行很多带有潜在的错误的产品,如果他们卖的仅仅是计算机游戏软件、那也无所谓,而现在我们的日常生活,包括工作、经济,以及越来越多的方面都依赖于软件。如果汽车制造商发现卖出去的汽车有瑕疵,他们会将这些汽车召回并进行修正。软件公司逃避了这样的责任,如果他们生产其他的产品也是这样,这是不允许的。这种不严谨的态度意味着在生产正确的软件产品上,还有很多严肃的工作要去做。

8.1.4如何着手

现实情况非常复杂,千万不要以为你只需要一个好的程序员或很多次代码审查工作,或是经过ISO 9001认证的开发过程,或者是完全的测试,甚至是以上描述的这些加起来,就足够了。软件的设计非常复杂,不能被简简单单地用一些过程和规则完整描述。我们发现研究航空工业这个现实中最好的工程质量控制系统有一定的指导意义。

8.2制作安全的软件

到现在为止,我们只讨论了正确的软件。仅仅编写正确的软件对安全系统来说是不够的,软件也必须还是安全的。

两者之间有什么差别呢?正确的软件有一个特定的功能。如果按下按钮4,就会发生B。安全的软件还有一个额外的要求∶缺失某些功能。也就是攻击者无论采取什么措施,它都不可能做X。这是一个非常根本的区别,你可以测试功能,但不能缺失功能。由于没有一个有效的方法可以检测软件的安全性,这使得编写安全的软件比编写正确的软件更加困难。

可以得到如下结论∶

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

8.3保守秘密

对于这些安全信道而言,我们有两类秘密信息∶密钥和数据。这两类秘密信息都是瞬时的,不需要长期存储。数据只有在处理每个消息的时候存储,而密钥也仅仅在安全信道的持续期间存储。

瞬时秘密存储在计算机的内存里,很遗憾的是,大多数计算机上的内存都是不安全的。

8.3.1清除状态

在面向对象的语言中,问题会变得更简单一些。在C++中,每一个对象都有一个析构函数,这个析构函数可以清除状态。这当然是C++ 中安全性相关编码的标准实践。只要主程序功能合理并销毁了所有不再需要的对象,内存就将被清除。C+ 语言保证那些栈分配的对象在异常处理过程中栈展开时一定被正确销毁,但程序不得不自行确保销毁所有堆分配的对象。调用操作系统的函数退出程序可能不会展开调用栈。即使程序即将退出,也必须确保清除所有敏感数据。操作系统毕竟不能保证立即清除这些数据,有些操作系统甚至在进行下一个应用之前不清除内存。

8.3.2 交换文件

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

所有的内存空间都可以保存秘密数据,假定我们可以锁定内存以阻止其进行交换,那么应该锁定哪些内存呢?这就带来了第二个问题。在许多编程环境里,获悉数据的精确存储位置很困难,对象常常分配在堆里,数据可以静态分配存储空间,而局部变量存放在栈里。详细的处理很复杂,也非常容易出错。最好的解决方案也许是锁定占用的所有内存,但其实并非易事。因为这样就可能会丧失操作系统提供的很多服务,如自动分配栈,而且此时虚拟内存系统不再有效。

8.3.3高速缓冲存储器

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

高速缓冲存储器的第二个风险是,在某些环境下,高速缓冲存储器知道特定的内存单元已经被修改,可能是被多CPU 系统中其他 CPU 修改的。然后,高速缓冲存储器将其为这个内存单元准备的数据标记为"无效",实际数据则没有被清除,于是没有被清除的秘密数据的副本就又一次有存在的可能。

8.3.4 内存保留数据

在内存里简单地重写数据并不能删除数据,这令很多人感到惊奇。具体情况在一定程度上与内存的类型有关,但一般来说如果将数据存储到内存的某一存储单元。这个存储单元会逐渐地"学习"这个数据,当进行重写或关机后,旧数据并没有完全丢失。根据具体环境,仅靠先断电再通电就可以恢复部分或者全部旧数据。如果使用(通常是无记录的)测试模式访问内存,其他内存可以"记住"旧的数据 。

有几种机制可以引发这种现象。如果相同的数据存人 SRAM(静态RAM)内存的同一单元一段时间,那么该数据将变成这个内存单元所优先的通电状态。我们的一个朋友在很久以前使用家用计算机时就遇到了这种现象1。他写了一个BIOS,在一个特殊的内存单元使用了一个特定值以决定重置时是冷启动还是热启动。6一段时间后机器在通电后拒绝启动,因为内存已经"学习"到了这个特定值,启动过程将每次的复位都认为是热启动,这样就不对变量进行初始化了,所以启动失败。这种情况的解决方案是交换内存芯片,让 SRAM认识不规则的特定值。这个事实告诫人们∶内存保留的数据远比想象的要多得多。

为了从这个存储区阅读信息,需要读取两部分,将第一个部分的数据进行散列运算,然后与第二部分进行异或运算得到m。通过对新的数据和h(R)进行异或并将数据存储在第二个存储单元内,来完成写入。

注意,尽量避免让R和h(R)田m的位在 RAM芯片上靠得很近。由于不知道RAM芯片的工作方式,这似乎很难做到。但多数内存将信息存储在一个位矩阵里,用某些地址位选择行,用另一些地址位选择列。如果两个部分的存储地址相差0x5555,则它们不大可能地被存储在邻近的芯片上(这里假设内存不使用偶地址位作为行号、奇地址位作为列号,但我们从来没有见到过这样的设计)。一个更好的解决方案是在非常大的地址空间里随机选择两个地址,这样两个单元邻近的概率就非常小,并与内存的实际芯片布局无关。

8.3.5 其他程序的访问

在计算机上保存秘密的数据还有另外一个问题∶计算机里的其他程序也可能会访问这些数据。有些操作系统允许不同的程序共享内存。如果其他程序读取了你的密钥,这会产生十分严重的后果。通常来说共享的内存通常必须由两个程序设立,这样降低了风险。在其他情况下,共享的内存可能由于调用共享程序库而被自动设置。

现在操作系统都具有一些可由调试器使用的特性,于是调试器是一个很危险的工具。各种 Windows的版本都允许将一个调试器附加在正在运行的程序上,这样调试器就可以做很多工作,包括从内存中读取数据。在UNIX下,有时可以强迫对程序进行核心转储。核心转储是一个包含程序数据的内存映像的文件,包括了所有的秘密数据。

另外一个危险来自于有特权的用户,称为超级用户或管理员。这些用户可以在计算机上访问一般用户不能访问的东西。例如,在UNIX下,超级用户可以读取任意内存里的数据。一般来说,程序自身不能有效地防御这些攻击。如果在使用时非常注意,就可以避免其中一些问题,但你会发现自己的处理能力会受到一些限制。所以,仍然应该在正在使用的特定平台上考虑这些问题。

8.3.6 数据完整性

除了要保证数据的保密性外,我们还要保护所存储的数据的完整性。在传输期间我们用MAC来保护数据的完整性,但如果数据可以在内存里进行修改,仍然会有问题。

为什么说这很重要呢?因为计算机中存在大量这样一位一位的数据,假设工程完成得很好,每秒每一位发生错误的可能性仅为10-1,如果你有一块大小为128MB的内存,那么大约共有 10°位,发生1位错误的期望时间大约为11天。出错率将会随着内存数量的增加而提高,如果内存大小为1GB,情况更糟糕,发生一位的错误大概只需要 32小时。服务器通常采用 ECC 内存,因为它们的内存很大,而且运行的时间也较长,我们希望所有的计算机都有相同的稳定性。

8.3.7 需要做的工作

现代计算机上保证数据的保密性并不像听起来那么简单、有很多渠道都可以将秘密消息泄露出去。要使得数据能够完全有效地保密,你必须阻止所有这些泄露途径。很遗憾,现在的操作系统和编程语言都不支持完全阻止这些泄露所需要的功能,使用者必须尽力而为,这要涉及很多工作,而且这些工作都与具体的工作环境有关。

8.4 代码质量

8.4.1 简洁性

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

安全信道就是一个典型的例子。它没有选项,不允许只对数据进行加密而不认证,也不允许对未加密的数据进行认证。人们总是希望得到这些特性,但他们并不知道使用这些特性导致的安全性方面的后果。大多数用户对安全性的理解并不足以使他们有能力对安全性选项做出正确的选择,最好的方法是不要任何选项,确保默认的安全性。如果一定要有选项,就只提供单项选择∶ 安全的或不安全的

8.4.2 模块化

即使已经消除了大量的选项和特性,得到的系统仍然会很复杂。有一种使得这样的复杂系统易于管理的技术就是模块化。模块化就是将系统分成一些模块,然后分别设计、分析、实现每一个模块。

在保证密码系统的正确性方面,正确的模块化起到了非常重要的作用。以前我们把加密原语作为模块。模块的接口应尽量简单易懂,它的行为应该和用户合理的预期一致。仔细研究模块的接口,通常会有这样的一些选项或特性,它们被用于解决其他模块的问题。每一个模块都应该只解决自己的问题,因此如有可能,应该尽量删去这样的选项或特性。我们发现模块接口开始产生一些奇怪特性时,也就是要重新设计软件的时候了,因为它们的产生总是源于设计上的缺陷。

8.4.3 断言

当编写密码系统的代码时,要有一种近乎狂热的专业态度。每个模块都不能信任其他模块,并且始终检测参数的有效性,强迫限制调用顺序,并且拒绝执行不安全的操作。大多数时候,这些都是直接的断言。如果模块规范要求在使用一个对象前必须进行初始化,那么在初始化之前使用对象就会产生一个断言错误。断言失败总是导致程序的异常中断,并带有一个文档,用于说明是哪一个断言失败及对应的失败原因。

一般的规则是∶任何时候,在可以对系统内部的一致性进行检查时,就应该增加一项断言。捕捉尽可能多的可以捕捉到的错误,不论是你自己的还是其他程序员的。由断言发现的错误不会导致安全漏洞。

8.4.4 缓冲区溢出

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

8.4.5 测试

安全漏洞却没什么作用。千万不要将测试与安全性分析混淆,这两者是互补的,但是不同。

应该进行的测试共有两种类型。第一类是由模块功能规范产生的一组通用测试集。理想的方法是让一个程序员实现模块,而让另一个程序员实现测试,两者都按照功能规范工作,如果他们之间出现任何误解,就表明必须对功能规范进行进一步的阐明。通用测试应尽量覆盖模块的所有功能。对有些模块。测试比较简单;对另外一些模块,测试程序必须模拟整个运行环境。在我们自已的很多代码中,测试代码差不多与功能代码一样长,而我们还没有找到一个有效的改进办法。

第二类测试集是由模块程序员自己开发出来的测试程序,用来测试对程序实现的限制。 例如,如果一个模块内部使用一个4KB大小的缓冲区,对这个缓冲区的起点和终点进行边界条件的附加测试将有助于捕捉任何缓冲区管理错误。这类测试的设计有时需要对模块内部有所了解。

我们常常写人由随机数生成器产生的测试序列,我们将在第9章对伪随机数生成器(PRNG)进行广泛的讨论。使用伪随机数生成器使得进行大量的测试变得很容易,如果我们保存了产生伪随机数生成器的种子,就可以重复同样的测试序列,这对测试和调试非常有用。具体细节取决于所分析的模块。

8.5 侧信道攻击

有一类攻击我们称之为侧信道攻击P2。当攻击者有另外一个关于这个系统的信息通道时,就会发生这类攻击。例如,攻击者可以对系统加密一条消息所需的时间进行详细的测量。根据整个系统的实现方式,攻击者可以通过这样的时间信息来推断消息本身的隐私信息以及底层的密钥信息。对嵌入智能卡的密码系统,攻击者还可以测量智能卡需要的电流随时间的变化。磁场、RF 辐射、功耗与时间以及对其他数据信道上的干扰都可以用于侧信道攻击。

毫无疑问,对那些设计时没有考虑侧信道攻击的系统,侧信道攻击成功的可能性很大。对智能卡进行功率分析非常容易获得成功.

8.6一些其他的话

二、问题

问题一:数据的完整性的重要怎样体现?

为了确保计算机上的主内存是 ECC(纠错码)内存9。如果只有1位发生错误,纠错码会检测到这个错误并进行纠正。如果没有ECC内存,任意1位的错误都将导致 CPU 读取了一个错误的数据。因为计算机中存在大量这样一位一位的数据,假设工程完成得很好,每秒每一位发生错误的可能性仅为10-1,如果你有一块大小为128MB的内存,那么大约共有 10°位,发生1位错误的期望时间大约为11天。出错率将会随着内存数量的增加而提高,如果内存大小为1GB,情况更糟糕,发生一位的错误大概只需要 32小时。服务器通常采用 ECC 内存,因为它们的内存很大,而且运行的时间也较长,我们希望所有的计算机都有相同的稳定性。

问题二:CTR 模式被广泛用于 ATM 网络安全和 IPSec应用中,相对于其它模式而言,CTR模式具有哪些特点?

  • 硬件效率
  • 软件效率
  • 预处理
  • 随机访问
  • 可证明的安全性
  • 简单性
  • 无填充
  • 错误不传播
  • 必须配合MAC使用
  • 不能进行完整性校验

三、思考

对称/分组密码一般分为流加密(如OFB、CFB等)和块加密(如ECB、CBC等)。对于流加密,需要将分组密码转化为流模式工作。对于块加密(或称分组加密),如果要加密超过块大小的数据,就需要涉及填充和链加密模式。

ECB(Electronic Code Book电子密码本)模式

ECB模式是最早采用和最简单的模式,它将加密的数据分成若干组,每组的大小跟加密密钥长度相同,然后每组都用相同的密钥进行加密。

优点:

1.简单; 2.有利于并行计算; 3.误差不会被传送; 缺点: 1.不能隐藏明文的模式; 2.可能对明文进行主动攻击; 因此,此模式适于加密小消息。

CBC(Cipher Block Chaining,加密块链)模式

优点:

1.不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。 缺点: 1.不利于并行计算; 2.误差传递; 3.需要初始化向量IV

CFB(Cipher FeedBack Mode,加密反馈)模式

优点:

1.隐藏了明文模式; 2.分组密码转化为流模式; 3.可以及时加密传送小于分组的数据; 缺点: 1.不利于并行计算; 2.误差传送:一个明文单元损坏影响多个单元; 3.唯一的IV;

OFB(Output FeedBack,输出反馈)模式

优点:

1.隐藏了明文模式; 2.分组密码转化为流模式; 3.可以及时加密传送小于分组的数据; 缺点: 1.不利于并行计算; 2.对明文的主动攻击是可能的; 3.误差传送:一个明文单元损坏影响多个单元

四、实践

Alpha 机构有自己的一套网络系统进行信息传送。情报员 A 位于节点 1,他准备将一份情报
发送给位于节点 n 的情报部门。可是由于最近国际纷争,战事不断,很多信道都有可能被遭到监
视或破坏。
经过测试分析,Alpha 情报系统获得了网络中每段信道安全可靠性的概率,情报员 A 决定选
择一条安全性最高,即概率最大的信道路径进行发送情报。
怎样情报员 A 找到这条信道路径吗?

include<iostream>#include<cstring>#include<vector>#include<queue>using namespace std;const int N=10005;const int INF=1e9+7;int n,m;int vis[N];double dis[N]; struct fun{ int v; double w;};vector<fun> p[N]; void SPFA(){ int i,u,v; queue<int>Q; for(i=1;i<=n;i++) dis[i]=-INF;//求最大路径就初始化最小 memset(vis,0,sizeof(vis)); vis[1]=1; dis[1]=1.0;//不能为0,因为要相乘 Q.push(1); while(!Q.empty()) { u=Q.front(); Q.pop(); for(i=0;i<p[u].size();i++) { v=p[u][i].v; double w=p[u][i].w; if(dis[v]<dis[u]*w)//松弛条件改变 { dis[v]=dis[u]*w; if(vis[v]==0) { vis[v]=1; Q.push(v); } } } vis[u]=0;//初始化操作 } } int main() { int T; cin>>T; while(T--) { cin>>n>>m; for(int i=1;i<=n;i++) p[i].clear(); for(int i=0;i<m;i++) { int a1,a2,k; cin>>a1>>a2>>k; fun t; t.v=a1,t.w=(double)k/100; p[a2].push_back(t); t.v=a2; p[a1].push_back(t); } SPFA(); printf("%.6f\n",dis[n]*100); }}

posted @ 2021-04-01 23:21  20181225周凌霄  阅读(50)  评论(0编辑  收藏  举报