学习笔记8

第15章 实现上的问题Ⅱ

15.1 大整数的运算

  • 所有公钥中的计算都是基于大整数运算,而大整数运算的处理例程总是与平台有关。
  • 我们不去研究实现大整数运算的细节,关心的是如何测试大整数运算。
  • 如何进行测试?
    • 第一,不要自己去实现大整数运算例程。去使用一个已有的库。如果你想在这个上面花时间,就去理解和测试已有的库。
    • 第二,用很好的测试用例去测试你的库。确保你测试了每一个可能的代码路径。
    • 第三,在应用里面加入额外的测试。

15.1.1 woop技术

  • woop技术的基本思想是为了验证对随机选择的小素数的取模计算。
  • woop流程:
    • 首先,我们先生成一个相对小的随机素数\(t\),长度在64~128位。\(t\)的值不应该固定或者被预测到,所以我们需要一个伪随机数生成器(PRNG)。\(t\)的值对于所有其他各方都是保密的。然后,对于计算中出现的所有大整数\(x\),我们同样可得到\(\tilde{x}:=(x \; mod \; t)\)。这里的\(\tilde{x}\)就是\(x\)的woop值。这个woop值有一个固定的长度,并且比大整数通常小很多。所以计算这个woop值算不上什么额外的开销。
    • 对于算法中任何输入的\(x\),我们直接计算\(\tilde{x}\)。对于所有的内部计算,我们可以直接计算使用woop值运算的结果,而不用从大整数运算的结果再次计算一遍。
  • 选择的woop值应该多大?这取决于许多因素。对于随机发生的错误,woop值验证方式无法检测到错误的概率是\(1/t\)
  • \(t\)必须是个随机、秘密的数,如果不知道\(t\),攻击者就无法保证结果里面的错误不被我们的woop验证系统检测到。
  • woop值不是系统的主要安全性保证,它只是一个后备系统。如果一个woop验证失败了,我们就知道软件里面存在故障,需要修复。这时程序不论在做什么,都会马上终止并报告一个致命错误。这也使得攻击者更难对系统进行重复攻击。所以,我们建议使用一个64位的随机素数作为\(t\)值。这样和使用128位的素数比会减少开销,并且实际实践中也足够好用。如果你无法使用这个64位的woop值,一个32位的woop值也比没有好。尤其在多数32位CPU上,32位的woop值可以非常有效地计算出来,因为有直接的乘法和除法指令可以用。

15.1.2 检查DH计算

  • 建议在拥有woop验证的库上运行DH协议,这样可以有效检查出很多常见的错误。

15.1.3 检查RSA加密

  • 同样是建议在woop中进行RSA加密。
  • 如果不能使用woop验证,还有另外两种可用的办法:
    • 假设实际的RSA加密包括计算\(c=m^5 mod \; n\),其中\(m\)是消息,\(c\)是密文。为了验证这个结果,我们可以计算\(c=m^{1/5} mod \; n\)并且和\(m\)进行比较。这种方法的缺点在于计算过程很快,而验证过程很慢,并且它需要知道私钥,而我们在进行RSA加密时通常是不知道私钥的。
    • 更好的办法是选择一个随机值\(z\),然后检查\(c \cdot z^5=(m \cdot z)^5mod \;n\)。这里,我们总共计算了三次5次幂,分别是\(c=m^5\)以及\(z^5\)\((m \cdot z)^5\)。随机的运算错误很有可能被这样的验证发现。通过选择一个随机值\(z\),可以让任何攻击者都无法定位到导致错误的变量上。在我们的设计中,我们只是用RSA去加密随机值,所以攻击者压根就没办法去进行定位。

15.1.4 检查RSA签名

  • RSA签名相当容易检查。签名者只需要运行签名验证算法即可。这是一个比较快的验证,如果有运算错误也很容易捕捉到。每个RSA签名计算都应当用这种方法去验证刚产生的签名,没有理由不去这么做。

15.1.5 结论

  • 上面谈论的检查是在大整数库的正常检查之外的额外检查。它们不能够替代任何软件中原本应当进行的正常检查,尤其是安全软件。
  • 如果这些检查中有任意一个检查没有通过,你就知道你的软件不合格。这种情况下你没有太多能做的事情。继续工作是不安全的,因为你不知道自己遇到的是什么样的软件错误。你唯一能做的是记录下这个错误,然后终止程序。

15.2 更快的乘法

  • Montgomery方法是一种计算\((x \; mod n)\)的方法,其中\(x\)远大于\(n\)
  • 传统的“长除法”是从\(x\)中减去\(n\)的倍数。
  • Montgomery的想法更简单:重复地将\(x\)除以2。如果\(x\)是偶数,我们除以2的方法是将它的二进制表示右移一位。如果\(x\)是奇数,我们首先加一个\(n\)(当然这不会影响\(mod \; n\)的结果),然后将这个偶数结果除以2。(这个技术只适合\(n\)是奇数的情况在我们的系统里通常如此,当\(n\)是偶数时可以进行一个简单的归纳。)如果\(n\)的长度是\(k\)位,并且\(x\)不大于\((n-1)/2\),我们总共进行\(k\)次除以2,这个结果总是在区间\(0, \dots, 2n-1\)中,这几乎就是\(mod \; n\)的约简结果。
  • 在实际实现中, Montgomery归约从来都不是逐位进行的,而是以字(word)为单位。假设CPU使用\(w\)位的字,给定一个\(x\)值,寻找一个小整数\(z\)使得\(x+zn\)的最低有效字(least significant word)全为0.你可以证明\(z\)是一个字,并且可以通过乘以\(x\)的最低有效字和个只取决于\(n\)的单字常数因子计算得到。一且\(x+zn\)的最低有效字是0,就可以通过将这个值右移一整个字来除以\(2^w\),这比按位实现快多了。

15.3 侧信道攻击

  • 时间攻击在攻击公钥计算时也非常有用。
  • IDEA和MARS也会受到时间攻击的影响。
  • 应对措施:
    • 在每次计算结束之后添加一个随机的延迟。
    • 通过强行要求每个操作持续一个标准长度的时间,让操作时间变成常数。
  • 对于时间攻击的问题,没有完美的解决方案。想让你买到的计算机能够避免如RF攻击这种复杂的攻击是不可能的。但是尽管不能给出一个完美的解决方案,你还是能够找到比较好的解决方案。只需要特别注意公钥操作的时间长度。一个比固定每个公钥操作的时间更好的解决方案是用上述办法,让整个交易的时间长度固定。也就是说,不仅让公钥的操作时间固定,而且也固定了请求进入和返回响应之间的时间。

15.4 协议

  • 实现密码协议和实现通信协议区别不是那么大。最简单的办法是保持程序计数器的状态,然后简单地轮流实现协议的每一步。除非使用了多线程,否则这会使得你在等待一个回复时,程序其他部分都停止执行。这通常不是一个好主意。
  • 一个更好的解决方案是保持一个显式的协议状态,并且在每次一条消息到来时更新这个状态。这种消息驱动(message-driven)的方法在实现的时候需要稍微多花点功夫,但是它也提供了更多的灵活性。

15.4.1 安全信道上的协议

  • 多数的密码协议都是在非安全信道上执行的,但是有时候你需要在一个安全信道上运行密码协议。
  • 有时安全信道会允许协议使用捷径。例如说,如果安全信道提供了重放保护,协议本身就不需要再实现一次重放保护了。不过,老的模块化规则认为,协议应该最小化其对安全信道的依赖程度。

15.4.2 接收一条消息

  • 当一个协议状态接收到了一条消息时,需要进行若干项检查。第一项是检查这条消息是否属于这个协议。每条消息都应该由以下几个字段开头:
    • 协议标识符(protocol identifier):用来识别这是哪个协议和哪个协议版本。版本标识符是很重要的。
    • 协议实例标识符(protocol instance identifier):识别这条消息属于协议的哪个实例。
    • 消息标识符(message identifier):识别协议内部的消息。最简单的方法是给它们编号。
  • 取决于具体情况,一部分标识符可能是隐式的。比如,对于那些在自己的TCP连接上运行的协议,端口号和关联的套接字就可以识别出本地机器上的协议实例。协议标识符和版本信息只需要交换一次。需要注意的是,这些信息至少需要交换一次以保证它们被包含在协议的所有认证或者签名中。

15.4.3 超时设定

  • 任何协议都包括超时设定。如果你没有在一段合理的时间内收到消息的响应,可以重新发送最后一条消息。在几次重发之后,你不得不放弃。当你无法和另一方通信时,继续执行协议是没有意义的。
  • 实现超时设定的最简单办法是发送计时消息给协议状态。你可以使用协议显式设置的计时器,或者使用每几秒发送一次的计时消息。
  • 一个广为人知的攻击方式是发送很多个“开始协议”消息到一台特定的机器上。每次你收到一条“开始协议”的消息时,会初始化一个新的协议执行状态。在收到几百万个这种消息时,机器内存被用完,所有事情都停止了。一个好的例子是SYN洪水攻击。总的来说没有简单的办法能够防止这些洪水攻击,尤其是在僵尸网络和分布式攻击的时代,但是它们证明了删除旧的协议状态有多么重要。如果一个协议停顿了太久,你应该删了它。

第16章 时钟

16.1 时钟的使用

  • 时钟在密码学上有几种应用,密钥管理函数常常与截止时间( deadline)相关联,当前时间能够提供一个唯一值和事件发生的完整顺序。我们将详细讨论这些用法。

16.1.1 有效期

  • 在多数情况下,需要限制文件的有效期。在现实生活中,我们也通常会看到有限的有效期。支票、飞机票、代金券、优惠券甚至是版权都有有限的有效期。限制数字文档有效期的标准方法是在文档内部包含有效期。但为了检查一个文档是否到期,我们需要知道当前时间,因此就要用到时钟。

16.1.2 唯一值

  • 时钟另一个重要功能(如果它的精度足够高的话)是可以为单个机器提供一个唯一值。
  • 我们已经在几个地方用到了瞬时值(nonce)。瞬时值的主要特点是任何一个值都不会被使用两次,至少在定义的范围内如此。在有些情况下瞬时值的范围被限定了,例如在安全信道中使用的瞬时值,这时它就能用一个计数器来生成。在其他情况下,瞬时值在经过机器的多次重启后仍然要保证唯一性。
  • 生成瞬时值的通用方法有两种。
    • 第一种是使用时钟的当前时间但是需要一些机制能够保证你永远不会两次使用相同的时间信号(time code)。
    • 第二种方法是使用在第9章中已详细讨论的伪随机数生成器(PRNG)。使用随机数作为瞬时值的缺点是需要的随机数相当大。为了达到128位的安全等级,我们需要使用256位的随机瞬时值。并不是所有的原语都支持这样大的瞬时值,而且,PRNG在某些平台上可能很难实现。因此,通过一个可靠的时钟才是生成瞬时值的有吸引力的可选方法。

16.1.3 单调性

  • 时间的一个有用的性质是它会一直不断向前走,从来不会停止或倒退。有一些密码协议用到了这个性质。在密码协议中包含时间信息可以防止攻击者把旧的消息当作有效消息放到当前协议中。
  • 时钟的另一个非常重要的应用是审计和日志。在任何交易系统中,记录所发生的所有事情都是很重要的。如果岀现意外,日志就会提供必需的数据来追踪确切的事件序列。在每次日志记录事件中所包含的时间信息是很重要的。

16.1.4 实时交易

  • 以电子支付为例,为了支持实时支付,银行需要运行实时金融交易系统。为了让审计能够完成,应该有一个清晰的交易序列。给定两个交易A和B,了解两者哪个先发生是很重要的,因为其中一个交易的结果取决于另一个是否已经完成。记录这一序列的最简单的方法是给每一个交易一个时间戳,但这需要在有可靠时钟的前提下才可行。

16.2 使用实时时钟芯片

  • 实时时钟芯片对一般的普通应用来说是足够了,但在安全系统中,我们必须强制要求更高的标准。作为安全系统的一部分,即使攻击者试图去操纵时钟,它也应该给出正确的时间来。第二个原因是时钟有时会有故障,如果时钟是安全系统的一部分,那么它的故障就会带来更大的危害。
  • 在一般硬件中的实时时钟并不像我们所需要的那样可靠和安全。
  • 除了实时时钟的意外错误,我们还必须考虑主动攻击。有些人可能尝试用某种方式操纵时钟。根据计算机的具体情况,改变时钟时间可能会容易也可能很困难,在有些系统中只有特定的管理员才有权限改变时钟,而有些系统的时钟可以被任何人修改。

16.3 安全性威胁

16.3.1 时钟后置

  • 假定攻击者能够把时钟设为过去的任何一个时间。这会导致各种可能的危害,机器会错误地相信自己生活在过去。也许攻击者曾经是个临时雇员的时候可以访问一些数据,但这种访问权限现在已经过期。
  • 另一种攻击是关于定时任务的。

16.3.2 时钟停止

  • 如果时钟停止了,时间似乎就静止了。这时事情可能就无法完成。而且很多系统可能会有无法预测的行为。

16.3.3 时钟前置

  • 会导致简单的拒绝服务攻击。
  • 时钟被设置到未来的时间也会带来一些直接的安全风险。在很多情况下,某些数据在特定的时间之前保持保密,而那个时间以后公开。在自动系统中,时钟向前设置就可以使这些数据能够被访问了。

16.4 建立可靠的时钟

  • 针对时钟问题目前还没有一个简单的解决方案,这里只给出一些建议和技术:
    • 通过计算机的计数器来测量两个事件之间经过的时间。
    • 第二种简单的检查办法是记录最后关机的时间,或是数据写入磁盘的最后时间。
    • 询问Internet或intranet上的时间服务器,有一些广泛应用的时间同步协议,如NTP或SNTP。

16.5 相同状态问题

  • 实时时钟芯片能够解决相同状态问题。这个小型嵌入式计算机能用一个固定的密钥加密当前时间,来生成高度随机的数据,这一数据能用作密码协议的瞬时值。由于这个实时时钟从来不重复它的状态,嵌入式计算机也就可以避免掉进相同状态的陷阱里了。
  • 硬件随机数生成器也有相同的作用,它能够让嵌入式计算机在每次重启后有不同的行为。
  • 还有一种可能的解决方案,虽然它很少在实际中使用。有时候你可以在非易失的存储器中放置一个重启计数器。每当CPU重启时,存储器中计数器的数值将增加。但是这个方案也存在问题。一些非易失存储器只能更新几千次,如果一直更新计数器就会使机器崩溃。

16.6 时间

  • 不要选择本地时间,因为本地时间会随着夏令时和时区改变。
  • 最明显的选择是使用UTC时间。这是一个基于原子钟的国际时间标准,在全世界范围内被广泛地应用。任何一个计算机都能得到本地时间与UTC的偏差,以此来和用户交互。
  • UTC存在跳秒(The leap seconds)的问题。

16.7 结论

  • 很遗憾,我们并没有给出一个很理想的解决方案。想要制造一个可靠的时钟是很棘手的,尤其是在密码学设定中,当你假设有很多恶意攻击者时。最好的解决方案还是要根据具体情况来决定。这里,我们给出的建议是,时钟的使用一定会带来一些潜在的安全问题,所以要尽可能地最小化对时钟的依赖,如果必须使用,一定要谨慎。另外,时间戳的唯一性和单调性通常是最重要的问题。

第17章 密钥服务器

  • 密钥管理是密码系统中最困难的问题,因为它牵扯的是人而不只是数学。
  • 密钥管理的一个办法是让一个可信实体来分发所有的密钥,我们把这个实体叫作密钥服务器(key server)。

17.1 基本概念

  • 我们假定每个人都与密钥服务器建立一个共享密钥。例如,Alice产生一个只有她和密钥服务器知道的密钥\(K_A\),Bob也建立一个只有他和密钥服务器知道的密钥\(K_B\)。其他参与方都以同样的方式建立密钥。
  • 现在假定Alice想与Bob进行通信。她没有与Bob通信的密钥,但她可以与密钥服务器安全地通信。密钥服务器反过来也能与Bob安全地通信。我们能简单地把所有的通信传给密钥服务器,把密钥服务器当成一个巨大的邮局。但对密钥服务器来说存在一些困难,因为它将不得不处理大量的通信。一个更好的解决方案是,让密钥服务器建立一个Alce和Bob的共享密钥\(K_{AB}\)

17.2 Kerberos协议

  • 当Alice想与Bob对话时,她首先与密钥服务器联系。密钥服务器向Alice发送一个新的密钥\(K_{AB}\)以及使用Bob的密钥\(K_B\)\(K_{AB}\)加密后的值。这些消息都用\(K_A\)加密,因此只有Alice能读。Alice解密之后,再用\(K_B\)加密之后的消息,这条消息称为标签(ticket),发送给Bob。Bob对它解密,然后得到\(K_{AB}\),这是只有Alice和Bob知道的会话密钥,当然,密钥服务器也知道。
  • Kerberos的一个特点是密钥服务器,在Kerberos协议的术语中叫作KDC,KDC不需要太经常地更新自己的状态。当然,密钥服务器要记住与每个用户共享的密钥。但当Alice请求KDC建立一个她和Bob之间的密钥时,KDC完成这个任务之后就把结果忘记了。它并不记录用户之间建立了哪些密钥。这是一个很好的性质,因为它可以让密钥服务器以一种简单的方式把原本很重的负载分布在几个机器上。由于没有状态要更新,Alice在某个时候可与密钥服务器的一个副本进行交互,而下一时刻使用另一个副本。

17.3 更简单的方案

  • 有时使用Kerberos协议是不可能的,因为协议太复杂,而且还有一些限制。
  • 如果我们不是那么强调效率,可以给出一个更简单、更强壮的解决方案。

17.3.1 安全连接

  • 首先,我们假定Alice和密钥服务器共享密钥\(K_A\)。他们不直接使用这个密钥,而是用它来运行一个密钥协商协议,密钥协商协议建立了密钥服务器和Alice之间的新密钥\(K_A'\)。所有其他参与者也都与密钥服务器运行同样的协议,都建立新的密钥。
  • Alice和密钥服务器用\(K_A'\)来产生一个安全的通信信道。通过使用这个安全的信道,他们可以进行安全的通信。安全信道能够提供保密性、可认证性、重放保护等特性,所有进一步的通信都发生在这个安全的信道上。其他的参与者也都与密钥服务器建立了类似的安全信道。

17.3.2 建立密钥

  • Alice请求密钥\(K_{AB}\)服务器来建立她和Bob之间的密钥,密钥服务器把新密钥\(K_{AB}\)发送给Aice和Bob。密钥服务器甚至可以通过Alice把消息传递给Bob,这样的话它就无须与Bob直接通信,Alice就简单地等同于一个密钥管理器和Bob之间的安全信道的一个网络路由器。
  • 这样做对系统有一个限制:在Alice请求密钥服务器建立同Bob的共享密钥之前,Bob的密钥服务器必须运行密钥协商协议。这是否是一个问题还要由具体的环境来决定,解决方案也由具体环境来决定。

17.3.3 重新生成密钥

  • 密钥\(K_A'\)需要有一个有限的生命周期,一般来说大约是几个小时。
  • 因为我们总能够重新生成密钥,密钥服务器不必以可靠的方式来存储安全信道的状态。假定密钥服务器崩溃,失去了所有的状态信息,而只要它还记得\(K_A\)(以及其他参与者相应的密钥)就没有问题。我们能在密钥服务器与每个参与者之间重新运行密钥协商协议,因此,虽然密钥服务器不是无状态的,但当运行协议时它不必修改自己的长期状态,也就是储存在非易失的介质上的部分。

17.3.4 其他性质

  • 简化协议的流程。
  • 得到前向安全性(forward security),如果Alice的密钥\(K_A\)泄露了,但是之前的密钥\(K_A'\)不会被泄露,过去的通信都是安全的。

17.4 如何选择

  • 如果需要实现一个中心的密钥服务器,可能的话应该使用Kerberos协议。
  • 在Kerberos协议不适合使用的情况下,需要设计和构建一些类似于前文描述的解决方案。
posted @ 2023-04-11 20:05  acacacac  阅读(33)  评论(0编辑  收藏  举报