首页 理论教育 密码学与协议

密码学与协议

时间:2022-02-09 理论教育 版权反馈
【摘要】:可以看到,密码学不仅仅研究加密解密的数学算法。更多的时候,密码学研究保护信息安全的策略,我们可以称之为“协议”。除了消息保密性以外,密码学还研究一些更加有趣的问题。这里,就让我们一起来看四个有意思的密码学协议问题吧。首先我们来看一个日常生活中大家经常会遇到的密码学协议问题——签合同。合同一般同时规定了双方的权利和义务,并需要双方都在上面签名。

说到“密码学”,大多数人的第一念头或许是摩尔斯(Morse)电码、凯撒(Ceasar)移位密码以及同音替换密码之类的东西。这些东西在各类小说中都已是老面孔了,“字母e在英文中出现的频率最高”等基本的破密码方法已经是耳熟能详了。某次和网友云风[2]聊了一下,突然领会到了密码学的真谛。密码学关注更多的并不是加密解密的各种数学算法,而是在已有数学算法上如何实现各种安全需求。防止消息泄露只是众多安全问题中的冰山一角,而这个问题本身又有很多复杂的变化。

谈到“消息泄露”时,我们头脑中想到的往往是,在信息传输过程中如何防止第三方截获。当然,小偷防是防不住的,不过我能保证他偷到东西也没用。双方只需要事先约定一套加密解密的方法,以密文的方式进行传输,这样便能很好地防止消息泄露。但有时候,“消息泄露”的内涵复杂得多,加密解密的传统方法并不适用。考虑这么一个问题:10个人坐在一起谈天,突然他们想知道他们的平均年薪是多少,但每个人都不愿意透露自己的工资数额。有没有什么办法让他们能够得出答案,并且不用担心自己的年薪被曝光?事实上,最简单的解决办法不需要依赖任何密码学知识:第一个人随便想一个大数,比如 880 516。然后他在纸条上写下这个数与自己的年薪之和,传给第二个人;第二个人再在这个数上加上他的年薪数额,写在另一张纸条上传给第三个人;直到最后一个人把纸条传回第一个人后,第一个人把纸条上的数减去只有自己知道的那个880 516,便得到了全部10个人的工资和。

可以看到,密码学不仅仅研究加密解密的数学算法。更多的时候,密码学研究保护信息安全的策略,我们可以称之为“协议”。在已有的数学模型基础上,我们往往忽略具体的数学实现方法,转而专注地研究借助这些数学工具能够构建的安全措施。除了消息保密性以外,密码学还研究一些更加有趣的问题。这里,就让我们一起来看四个有意思的密码学协议问题吧。

首先我们来看一个日常生活中大家经常会遇到的密码学协议问题——签合同。签署合同会具备法律效应,人们往往不敢随意签名。合同一般同时规定了双方的权利和义务,并需要双方都在上面签名。第一个在上面签字的人就会觉得很亏:万一我签了字后对方突然翻脸耍赖不签了咋办?即使合同上规定“合同仅在双方均签署之后才有效”,这个问题仍然存在,因为后签名者将具有绝对的主动权,他想什么时候签就什么时候签,而只有他的签名才具有决定意义。因此很多时候,双方都希望能够在对方签名之后自己再签名,从而获得一些安全感。这里我们来探讨一个有趣的问题:有没有什么办法能够让双方同时签约,使得双方签名时都能确保自己的利益安全?

如果我们谈论的是传统意义上的签名,同时签名当然是有可能办到的:双方只需要拿起各自的笔,同时在文件上写下自己的名字即可。当然,事实上肯定不会有人这么做。试想这样一个荒唐的画面:两个西装笔挺的人挤在一起,两只手臂磕磕碰碰地交错在一起,然后双方同时喊“三、二、一”并一起开始写字……比起自己丢掉的脸面,自己先签名所带来的忧虑似乎也不算什么了。

有没有体面一些的,不那么荒唐的同时签字法呢?这里有一个很有启发性的办法。合同双方面对面地坐在桌子的两端。其中一个人在合同上写下自己名字的第一个字母,然后传给坐在对面的第二个人;第二个人写下他自己名字的第一个字母,然后又递回给第一个人;第一个人签下自己名字的第二个字母,再交给对方要求他写下他的名字的第二个字母……以此类推,直到双方都签署完自己的名字为止。为了让双方能够“同时”签完,名字较长的人偶尔可能需要连续写下两三个字母。换成汉字的话,这个方法同样适用——把字母改成笔划就行了。

双方都愿意履行这一协议,因为在这个协议下双方是一点一点地签完整个文件的。第一个写字的人不会觉得自己很亏,因为写下一个字母是远不具备法律效应的;如果对方拒绝签他的第一个字母,我可以当即撕毁合同。虽然他们都不知道,究竟要写多少个字母才算签字,但大家都保持自己签下的名字长度与对方基本相当,因此不会担心对方突然放弃协议。就在这种互动的心理过程中,签名的法律效应一点一点地增强,直到最后双方写完自己的名字。

但是,这个办法不能用于数字签名。利用电子加密算法进行签名是一个整体的过程,不能一部分一部分地进行。能不能把合同拆成若干份,然后双方一份一份地逐个签名呢?当然不行。如果某一份合同里有一个至关重要的义务性条款,后签名的人等对方签到这里后便可以立即终止签名,从而谋取利益。那么,能不能规定“仅当你把所有n个部分的文件都签过了才算签”呢?这意味着最后一次签名才具有最终的决定意义,说穿了不过是把安全问题转移到了“谁签最后这一下”,问题实质上并未改变。其实,我们的解决办法相当简单。我们可以耍一个小花招,从本质上模拟上面的“逐字母签名法”。

首先,第一个人签署这样一份文件:“我愿意以 1%的概率接受该合同。”第二个人检查第一个人的签名,然后在上面附加一句“我愿意以2%的概率接受这份合同”,并进行签名,再交回给第一个人。第一个人检查对方已经签名,然后继续将这个条文升级为“我愿意以 3%的概率签署该合同”并签名。双方来来回回签 100次,直到最后第一个人签“我愿意以 99%的概率签署这份合同”,然后轮到第二个人签署“我接受该合同”,最后再轮到第一个人签署“我接受该合同”。

注意,这个“接受概率”是有实际意义的。如果在第一个人第一次签完文件后,第二个人立即放弃继续签署,法官可能会要求双方进行一次公开抽签测试,选取一个不超过100的正整数。如果这个数恰好为1,那么签署这句话的人就相当于签署了这份合同。类似地,我们也可以约定,当一人声称将以百分之(p-1)的概率接受此合同,另一人声明以百分之p的概率接受时,法官可以要求双方共同生成一个在1和100之间的整数:如果它不超过(p-1)则双方都接受,如果它的值比p大则双方都不接受,若它的值正好等于p则合同仅被后者接受。因此,这种协议实质上是用概率法再现了“逐字母签名法”的核心思想,将法律效应的问题进行量化,使得率先签名的潜在危险减小到了原来的百分之一。

合同签署的问题就说到这里了。让我们再来看另外一个有趣的协议问题。设想这样一个场景:总部打算把一份秘密文件发送给5名特工,但直接把文件原封不动地发给每个人,很难保障安全性。万一有特工背叛或者被捕,把秘密泄露给了敌人怎么办?于是就有了电影和小说中经常出现的情节:把绝密文件拆成5份,5名特工各自只持有文件的1/5。不过,原来的问题并没有彻底解决,我们只能祈祷坏人窃取到的并不是最关键的文件片段。因此,更好的做法是对原文件进行加密,每名特工只持有密码的1/5,这5名特工需要同时在场才能获取文件全文。但这也有一个隐患:如果真的有特工被抓了,当坏人们发现只拿到其中一份密码没有任何用处的同时,特工们也会因为少一份密码无法解开全文而烦恼。此时,你或许会想,是否有什么办法能够让特工们仍然可以恢复原文,即使一部分特工被抓住了?换句话说,有没有什么密文发布方式,使得只要5个人中半数以上的人在场就可以解开绝密文件?这样的话,坏人必须要能操纵半数以上的特工才可能对秘密文件造成实质性的影响。这种秘密共享方式被称为(3,5)门限方案,意即5个人中至少3人在场才能解开密文。

实现(m,n)门限方案的一个传统办法是,把这份文件的密码拆成figure_0350_0148份,每个人持有figure_0350_0149份密码。不妨假设文件的密码是一个100位数,那么在(3,5)门限方案中,我们需要把这个密码拆成figure_0350_0150份,每份密码都是一个 10位数。不妨把这 10份密码分别用0到9编号,把每份密码都额外复制两份。5名特工各持有6份密码,密码的分配如下:

你可以自己验证一下:任意 3名特工碰头,都能凑齐这 10份密码;但任意 2名特工碰头,都无法凑齐所有的密码。

上述分配表的构造其实很简单:给每个可能的“三人组”分配一份密码。从5个特工中选出3个人共有10种方案,因此我们正好要10份密码。例如把密码0分给特工1、2、3,把密码1分给特工1、2、4,一直到把密码9分给特工3、4、5。这样的话,任意2个人在场都无法打开文件,因为他们始终缺少一份密码(这份密码分给了其余3个人)。而任意3个人在场都足以打开文件,因为每一份密码都只缺少2个人的量,不可能出现这3个人都没有的情况。这样,我们便利用组合数学巧妙地解决了这一问题。

在密码学中,我们有一些更精妙的方案。最巧妙的方法是,把文件密码编码为三维空间中的一个点,然后生成5个过该点的平面,每个特工持有其中一个平面方程。显然,2个特工在一起是无法获得原文件的,因为 2个平面的公共点有无穷多个;但3个平面的交点是唯一的,因此任意3个人在一起都能解开原文件。

另一个有趣的办法利用了下面这个事实:知道m-1次多项式函数上的任意m个点就能恢复出整个多项式。因此,我们可以把文件密码编码为一个二次多项式 f(x),然后把 f(1)、f(2)、f(3)、f(4)和 f(5)的值告诉对应的特工。任意3个特工碰头之后,只需要解出这个多项式 f(x)即可恢复出文件的密码来。

上述两种方案的本质都是相同的:把文件密码设为3个数x、y、z,然后编写5个与x、y、z有关的一次方程,并把这 5个方程分别交给 5名特工。假如文件的密码是116.35、39.975、67.167这3个数,只有同时输入这3个数,才能解开原文件。那么,我们就用这3个数编写五个三元一次方程:

3.4x﹢5.6y-2.81z=430.711

x-2.11y﹢0.09z=38.0478

7x﹢9.9y-0.1z=1203.49

-0.3x﹢2.24y﹢5.6z=430.774

3x﹢4.5y﹢6.67z=976.941

其中x=116.35、y=39.975、z=67.167是它们的公共解。但是,要想确定出这个公共解,只有1个方程或者2个方程是不够的。事实上,至少需要3个方程,才能保证三元一次方程组存在唯一解。因此,至少需要 3 个人在场,才能获得秘密文件的密码。

利用数论知识我们还能得到一个简单的协议。中国剩余定理告诉我们,给出m个两两互质的整数,它们的乘积为P;假设有一个大整数M,如果我们已知M 分别除以这m个数所得的余数,那么在0到P-1的范围内可以唯一地确定这个M。我们可以想办法构造这样一种情况,n个数之中任意m个的乘积都比M 大,但是任意m-1个数的乘积就比M 小。这样,任意m个除数就能唯一地确定M,但m-1个数就不足以求出M 来。米尼奥(Mignotte)门限方案就用到了这样一个思路。我们选取n个两两互质的数,使得最小的m个数的乘积比最大的m-1个数的乘积还大。例如,在(3,5)门限方案中,我们可以取53、59、64、67、71这5个数,前面3个数乘起来得200 128,而后面两个数相乘才得4757。我们把文件的密码设为一个4757和200128之间的整数,比如123 456。分别算出123 456除以上面那5个数的余数,得到19、28、0、42、58。显然,知道任意3个同余方程就可以唯一地确定出123456,但仅知道2个方程只能得到成百上千个不定解。例如,假设我们知道了x除以59等于28,也知道了x除以67得42,那么我们只能确定在0和59×67-1之间的解913,并且只能断定M 是一个形如59×67×k﹢913的数,其中k的数量级和当初选的那五个数一样大。

我们的第三个协议问题就更有意思了。3个好朋友到一家餐厅吃饭。饭快吃完的时候,一个服务员过来告诉他们说,他们的账单已被匿名支付了。3个人都尊重他人匿名付款的权利,但同时他们也想知道,这个匿名支付者是他们三位中的一个,还是他们三人之外的某个第四者。有没有什么办法能够让他们知道在他们中间是否有人付账,但又保证任何人都推测不出究竟是谁付的账?利用3枚硬币就能轻易做到这一点。

假设这3个人围着一张圆桌坐成一圈。每个人都在自己和右手边那个人中间抛掷一枚硬币,并用另一只手挡住硬币,使得这枚硬币只有他俩才看得见。这样的话,每个人都只能看见他左右的两枚硬币(但看不见桌子对面的第三枚硬币)。每个人都大声报出,自己身边的两枚硬币的正反面是否相同。如果他们中间有人付账,则这个人报出与实际情况相反的词,相同的话说“不同”,不同的话则说“相同”。显然,如果大家说的都是真话,则报“不同”的次数一定是偶数次。如果有奇数个人说“不同”,那么一定有一个人说假话,这表明匿名支付账单的人就在他们中间。

注意到这个方案可以扩展到n个人。我们只需要证明,假如有n个人坐成一圈,如果大家都说真话,则说“不同”的次数一定是偶数次。证明非常简单。想象你从某一枚硬币出发,顺时针查看每一枚硬币的正反,得到一个硬币正反序列。每当这个序列由正变反或者由反变正时,就相当于有一处“不同”的情况发生。然而,当你绕着圆桌走完一圈,回到出发点时,硬币序列又变回了出发时的正反。因此,途中发生的“不同”次数一定是偶数次。

其实,抛掷硬币只是一个形象的描述方法罢了。在没有硬币,甚至大家根本没坐在一起的情况下,这个协议也很容易实施。比方说,先在网上公布整个协议规则,并约定一个虚拟的座位顺序;然后每个人都在1和2之间想一个数,并把结果以短信的形式发给他右边的那个人;最后每个人都按照协议规则,在网上发一个“相同”或者“不同”。

这个协议有一个意想不到的用途——匿名的消息广播。假如一群人围坐成一圈开会,会议过程中需要在场的一个不愿透露自己身份的人进行匿名发言。为此,大家可以统一采用上面的抛硬币协议(或者对应的电子协议,只是为了简便,下面还是采用抛硬币的说法)。发言人将信息编码为一个长度为n的01串。硬币投掷分n轮进行。第i轮中,其他人都严格按照实际情况报是否相同,发言人则根据编码信息的第i位的值来通报:若第i位为0,则按照实际情况通报;若第i位为1,则说与实际情况相反的词。这样,实际的信息就应该是每轮叫“不同”的次数除以2的余数形成的序列。

我们把最有意思的话题放在了最后。现在,假如你碰到了一个宣称可以预知未来的人,他说他知道下周的彩票中奖号码。你肯定不会相信,便用激将法让他说出下周的中奖号码:“你说出来啊,你要是说不出来,那就表明你不能预测未来。”不过,他却一本正经地说:“不行,我虽然能预测未来,但却不能把它说出来,否则会产生蝴蝶效应,改变这个宇宙既定的将来,导致危险的时空悖论。”

哈哈,这个“先知”真是天才呀!能预言未来却不能说出来,这样就永远不能证伪了。

不过,治他的方法也不是没有。比方说,可以叫他把预测结果写在一张纸上,锁进一个盒子里。然后,你拿走盒子,他拿走钥匙。彩票中奖号码公布后,你们再碰个头,把盒子打开,来看看当初的预测结果是否正确。这样便能让他做出一个谁都不能看见,但他今后也不能抵赖的预测。我们把这样的协议叫做“带有防欺骗的承诺”。

只可惜,这种方法有一个局限性:它只能在现实生活中使用。如果你在网上遇到了自称能预知未来的人,你如何让他做出防欺骗的承诺呢?

有人可能会说,为什么不让他给预言加一个密呢?就像之前让他给预言加上一把锁一样。比方说,让他在下周的中奖号码上加一个很大的整数,然后把结果告诉你;这个很大的整数就是解开中奖号码的密钥,由他自己保管。仔细想想你会发现,这个方案显然不行,因为到了验证预言的时候,他可以谎报这个大整数,让密码解开来后是任何一个他想要的数。为了防止他耍赖,能否让他事先就把密钥公布出来呢?这也不行——知道了密钥后,你就能直接获得密码原文了,这样便失去了保密的作用。

注意到,传统的加密方法不能公开的原因就是,知道了加密方法也就知道了解密方法,只需要把加密方法反过来做就行了。有没有一种加密方法,使得即使你知道了加密的方法,也不能恢复出密码原文呢?有的。只需要在加密过程中加入一些不可逆的数学运算就行了。比方说,你们可以约定这样一种加密方法:先取中奖号码的正弦值的小数点后八位数字,得到一个八位整数;再求中奖号码与圆周率前六位数字形成的整数(314 159)之和,取该和的平方的第 3位到第 10位,又得到一个八位数;最后计算这两个八位数的和除以456 789的余数。假如他预言的中奖号码是1 234 567,那么对1 234 567进行上面这一串操作后,将会得到244 685。但是,即使知道加密的过程,你也不能把244 685还原成1 234 567。事实上,1 234 567甚至不一定是唯一解,很可能有别的数加密后也会变成 244 685。上述加密方法能把任何数都加密成一个小于456 789的数,因此必然会出现不同的数加密成同一结果的情况。这就意味着,这种加密方法是会丢掉原始信息的。我们不妨把这种不可逆的加密方法叫做“单向加密”。在密码学中,MD5和SHA1是两种比较常用的单向加密算法。由于其单向性,这种加密方法不能用于普通的信息传输。但它有很多其他的应用,做出带有防欺骗的承诺便是一例。拿到244685这个数后,你完全无法推出他究竟做了什么预测;到了验证预言的时候,只需要让对方宣布当初他的预测 1 234 567,你来检验一下 1 234 567加密后是否会得到244 685就行了。

不过,这个方法有一个局限性:如果他宣称他能预测某只股票会涨还是会跌,上述方法就有漏洞了。比方说,你们可以约定,数字1表示股票会涨,数字2表示股票会跌,然后让他用刚才的那套方法把他的预测结果加密发过来。如果他告诉你的结果是316554,那你只需要分别试一下1和2加密后分别得多少,就知道原始数据是1还是2了。原始数据的取值太有限,让穷举式的“暴力破解”变得易如反掌。怎么办呢?可以想办法硬把原始数据的取值范围扩大。比如,约定所有个位数字为1的数都表示股票会涨,约定所有个位数字为2的数都表示股票会跌。假如对方预测股票会涨,他可以选取任意一个末位为1的数,对其进行加密,这下你便没办法暴力破解了。不过,这里还有一个小问题:刚才我们说了,单向加密可能会把不同的原始信息加密成同一个结果,因此完全有可能出现这样两个数,它们的末位分别是1和2,但加密后的结果相同(虽然找到这样的例子并不容易)。为了避免对方手中持有精心构造的“两可解”情况,我们可以在每次实施协议时都改变一下协议的细节,比如每次都换一种单向加密方式,或者更好地,每次都要求对方选取的那个数必须以你想的某个随机数打头。这样一来,整个协议就完美了。

其实,这个协议并不只在揭穿超能力者的时候才有用。我们生活当中有很多地方都可以用到带有防欺骗的承诺。有一次,我在战网上和别人打星际,打出了一个非常搞笑的局面:两边的兵都一个不剩,两边的钱也都不够造东西了,双方都完全丧失了战斗能力。但是,双方都还剩有建筑,因此都不算输。此时,必须有一个玩家主动认输,先退出游戏,才能结束僵局。该谁先退呢?我和他便在游戏中互发消息谈论了起来。其实,在现实生活中,这很好解决:来玩一次石头剪子布就可以了。但是,怎么在网上玩石头剪子布呢?总不能让一个人先发消息说“我出的是剪子”,另一个人回复“哈哈,我出的石头”吧。这时候,就要用到带有防欺骗的承诺了。我们可以利用前面讲的方法,各自向对方承诺自己要出什么拳,然后双方再公布自己出的拳,让对方验证自己并没撒谎。更简单的方法就是,我在1和2之间想一个数,然后把我想的数加密告诉对方,由对方来猜我想的数是多少,猜对了我就认输退出,猜错了他就认输退出。对方做出猜测后,我再公布加密前的原始信息,以证明我没有耍赖。

我们常常在电视上看到这样的一幕:一位老太太兴冲冲地走上台去,翻过3个商标牌,发现上面尽是5块钱、10块钱的小奖,垂头丧气地回到观众席;然后主持人会跑出来,边翻着另外几个牌子边说,1000元的大奖在这个后面,800元的在这里之类的。为什么主持人要演出“事后揭大奖”这一幕呢?道理很简单,节目组想通过这个“验证过程”告诉观众,这个环节不是骗人的,大奖真的就在这里面,只是刚才那家伙运气背了没摸到而已。摸奖前宣称有大奖,摸完奖之后还能证实大奖真的存在,这也是带有防欺骗的承诺。

但是,我们喝饮料参与开盖有奖活动时,就会有被欺骗的感觉:你说中奖率是千分之一,我凭什么相信你呢?那么,有没有办法让开盖有奖活动的中奖率变得透明呢?有的。我就想过这么一个方法。比如说,开盖后你将得到一个参与活动的序列号,把这个序列号短信发送给活动举办方参与抽奖。此时,活动举办方的服务器从1到1000中随机生成一个整数,并把这个整数加上你指定的前缀和它自选的前缀,用公开的单向加密方法加密后发回给你。你需要猜出服务器生成的数是什么,如果猜对就能中奖,如果猜错就结束游戏。发送了你的猜测结果后,服务器将发来加密前的信息,确保自己没有撒谎。

密码学与协议的故事多得讲也讲不完。公钥加密算法、密钥交换协议、盲签名协议、投票协议、虚拟货币协议、中间人攻击……这些简直都是密码学中的珍宝。还没过瘾的读者,不妨买一本密码学与协议的书,继续研究下去。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈