消息认证码
在上一章里当我们讨论“安全(security)”时,其实我们指的是消息的私密性(secrecy)。消息具有私密性,是指具有防止被一个窃听者得知其内容的能力。但有时相比于私密性,我们更在意消息的真实性(authetication)。例如在网购时,或许商家并不在意顾客的订单是否泄露给某些窃听者,而更关心订单的来源以及内容是否被恶意篡改过。私密性和真实性作为安全的两个方面实际上并不具有实际关联,我们之间所讨论的加密(encryption)对保护消息真实性没有任何实质的帮助,它确实能够通过不让敌手知道密文的含义而只能做出不基于文本的修改, 但并不为“监测消息是否被修改过”这一目的做出任何贡献。很多时候,只要篡改消息的一个二进制位就有可能彻底改变消息的本意。
消息认证码(Message Authentication Codes, MACs)
我们想要实现在私钥加密的背景下,接收方能够判定接收到的消息是否经过篡改。要能实现这一点,发送方和接收方肯定要提前掌握某些敌手所没有的信息(像私钥一样),否则接收方便与敌手无异。
在下面这种基于称为消息认证码(MAC)的方案中,发送方和接收方提前约定要一个私钥\(k\)(这里我们没有讨论有关加密的任何事情,这个私钥可能不同于加密时的私钥),一个程序\(\text{Mac}\)基于\(k\)和要发送的消息\(m\)生成一个字符串\(t\),称为标签(tag)(\(\text{Mac}\)可能是确定性的,可能是非确定性的)。接收方运行一个验证程序\(\text{Vrfy}\),输入\(m,t\)输出\(t\)是否是一个合法的标签(0或1)。我们要求\(\text{Vrfy}_k(m,\text{Mac}_k(m))=1\)必须始终成立。可以看出,接收方就是通过验证程序的输出来判断消息真实性的。
如果\(\text{Mac}\)是一个确定性的程序,那么\(\text{Vrfy}\)的最简单也最标准的实现方式就是再次运行\(\text{Mac}\)然后比较两次得到的标签,标签合法当且仅当两次的输出相同。这称为标准验证(canonical verification)。
如何定义一个MAC方案是否安全(secure)呢?我们假设敌手能够截获消息明文以及对应的标签,这意味着敌手实际上拥有选择任何明文并得知其标签的能力。如果敌手能够生成一个从未发送过的消息的标签,那么当它把这条消息连同敌手自己生成的标签发送给接收方时,消息的来源就无法辨别,敌手就成功破解了这一MAC方案。所以我们可以基于以下实验定义MAC方案的安全性:敌手可以询问多项式次消息对应的标签,如果敌手能够基于一个它未询问过的消息给出一个合法的标签,就称敌手成功。如果敌手成功的概率\(\leq \text{negl}(n)\),就称当前MAC是不可伪造的(unforgeable),也即这一MAC方案是安全的。
必须指出,以上MAC方案的定义是不针对重放攻击(replay attacks)的。想象发送方发过\((m,t)\),那么敌手只需重复发送\((m,t)\),接收方就完全不能判断消息来源。这很多时候是危险的,例如网购时顾客下单了一次,敌手做重放攻击九次,店家就要顾客交十倍的钱。这是由于MAC的定义中不涉及任何关于状态的量(it is stateless),一些能够保证通信双方同步(synchronized)的方案可以缓解这一问题。
另一方面,如果标签的生成程序是非确定性的,那么以上安全性的定义中也没有排除敌手关于已发送过的消息生成了另一个不同的合法标签的情况。为了囊括这一情形,可以在安全性的定义中把“未询问过的消息”改为“未询问过的消息和标签的二元组\((m,t)\)”,这样定义的MAC称为强不可伪造的(strongly unforgeable)。容易发现标准验证下的MAC一定是强不可伪造的。
在实际中,敌手还可以不停给接收方发送二元组\((m,t)\)从而确认当前伪造的标签是否合法,也就是我们实际上在定义中应该假定敌手有确认标签是否合法的能力。我们之所以在定义中没有提到这点,是因为可以证明强不可伪造性下拥有这种能力并不会增加敌手成功的概率。然而,在实际中这一确认标签的能力确实会引发问题。假如接收方的验证程序是按位比较两个字符串,在第一次遇到不同字符时立马停机返回,那么敌手就可以根据检验程序返回输出的时间差来直到得知第一个出错的位置,改正它并不断重复以上操作敌手就能最终伪造出一个合法的标签。这一攻击并不是基于我们假定的模型的,而是利用了真实物理世界中的时间信息。这类方法称为侧信道攻击(side-channel attacks)。为了防止这一攻击,我们可以要求MAC的检验程序的输出时间必须是与输入无关的(比如比较完所有位置再返回)。
具体的MAC的构造
假定消息空间中的消息是定长的,不妨设为\(n\)。一个最简单的安全MAC方案就是基于密钥\(k\)和消息\(m\),由一个伪随机函数\(F\)生成密钥\(t:=F_k(m)\)。由于\(F\)是发送方和接收方事先选定的,这一方案是确定性的。验证方只需基于同样的\(F\)验证是否有\(t=F_k(m)\)即可(标准验证)。这个方案是安全的,首先证明把伪随机函数替换为真随机函数只会对概率产生\(\text{negl}\)的影响,而敌手成功在一个\(\{0,1\}^n\)的标签空间中成功伪造一个长度为\(n\)的标记的概率只能为\(2^{-n}\)。综上,敌手伪造成功的概率是可忽略的,因此这一方案是安全的。
我们可以基于定长的MAC构造一个不定长的MAC。解决不定长问题的一个高效方法是分块,因此一个自然的方案是把消息分块,每个块分别做消息认证码以后连成一个大的消息认证码。但容易发现这是不安全的,敌手只需交换各个消息块和标签的顺序就可以构造一个从未发送过的消息的认证码。为此,我们在每个块里塞进块的编号\(i\),分别对每个\(i\| m_i\)做MAC,然而由于消息长度可以不同,敌手只需扔掉最后几个块就可以伪造一个合法的新MAC。为此,我们在每个块里都塞进总块数这一参数\(\ell\|i\|m_i\),然而敌手依然能够通过交错放入两个相同块数的消息的标签来伪造,为此我们再在每个块里塞进一个实现选好的伪随机数\(r\|\ell\|i\|m_i\)。我们证明做了所有这些以后,得到的方案是安全的了:对于每个\(r,\ell,i,m_i\),我们规定他们都是长度为\(n/4\)的二进制串。对于给定消息\(m=m_1\|\cdots \|m_\ell\),生成伪随机数\(r\),设\(\text{Mac}_k'\)是关于定长\(n/4\)的MAC(上一段构造的),令\(t_i:=\text{Mac}_k'(r\|\ell\|i\|m_i)\),最终输出标签\(t:=r\|t_1\|\cdots\|t_\ell\)。这一方案是不确定性的,因为它包含了一个每一次生成都会重新选择的随机数\(r\)。然而除此之外的部分都是确定性的,因此接收方可以在收到的标签的开头取出\(r\),然后用同样的过程验证生成的标签和收到的标签是否相等。敌手在伪造时,如果能够猜对\(r\),要么是\(r\)在它的选择明文攻击时出现过,要么是没出现过而它恰好猜对了。前者的概率是询问次数的多项式乘以关于\(n/4\)的指数,因此是可忽略的。而恰好猜对的概率是指数。综上,敌手成功伪造的概率是可忽略的。
以上构造中,我们其实是用了块密码来做MAC。我们还可以用链式块密码(CBC)来做MAC,称为CBC-MAC。在应用情境下,这些方法往往比上面构造的方法效率更高。对于定长的消息空间,设\(m=m_1\cdots m_\ell\),选定伪随机函数\(F\)和密钥\(k\),可以令\(t_0=0^n\),\(t_i=F_k(t_{i-1}\oplus m_i)\),输出\(t_\ell\)(而不是所有的\(t_i\)连起来)作为消息认证码。这是一个确定性的算法,可以证明它是安全的(略)。值得注意的是,以上方案本身并没有体现任何对\(m\)必须是定长的要求,但可以证明,如果用上述方案生成长度任意的消息的MAC,它将不再是安全的。为了让它对任意长的消息保持安全,有两种修改方法:一种是令\(t_0=F_k(|m|)\),其中\(|m|\)是表示\(m\)长度的一个\(n\)位二进制数;另一种方法是选取两个密钥\(k_1,k_2\),用原先的方案基于密钥\(k_1\)生成标签\(t\),再输出\(F_{k_2}(t)\)作为新的标签。