多次使用“一次性密钥”(one-time pad)为什么不安全?

确切地说,是在one time pad中多次使用同一个密钥。网上有人说,如果明文曾被破解出来过,那么用明文和密文就能获得密钥。但是,第一次使用one …
关注者
149
被浏览
127,122

14 个回答

0 2014年11月13日补充

我的这个答案出来以后,

@玄星

同学和我在评论区进行了很深入的讨论。我个人认为讨论内容应包含在此答案中,用来补充我们之间答案中出现的一些理解上面的问题。这些问题涉及到了对于密码学安全定义的基本理解,有兴趣的朋友们可以看一看,现原文摘录如下:

玄星:

CPA的定义里,A可以向encryption oracle提问,当challenge是Cb,A甚至可以问enc?m0和enc?m1.

下面是维基百科的条目Chosen-plaintext attack ,里面一开始就有:

A chosen-plaintext attack (CPA) is an attack model for cryptanalysis which presumes that the attacker has the capability to choose arbitrary plaintexts to be encrypted and obtain the corresponding ciphertexts.

定义在introduction to modern cryptography里也可以找到。k是在A攻击之前就已经确定,这个和OTP始终使用同一个密钥的情况相符。

但Known Plaintext Attack的确不能问询,只能观察。

The known-plaintext attack (KPA) is an attack model for cryptanalysis where the attacker has samples of both the plaintext (called a crib), and its encrypted version (ciphertext).

另外,你的攻击方法的成功率有一个前提,就是m的分布不是平均随机。

当然,如果仅仅是局限于Boneh的那门课里,这种解法没有问题。



我:

我认为对于流密码来说,选择明文攻击是不能如此理解的。如果按照你这样的理解,那么无论随机函数发生器多么安全,流密码都无法做到选择明文安全,因为攻击 者可以问询Enc(m0)或者Enc(m1),这样一来,如果key一定,那么选择明文攻击一定能够成立。当然了,对于电码本模式的分组加密体制同理(所 以会有其他的加密模式)。因此,在流密码中,我们可以允许访问加密预言,但是回复的是流密码继续产生key,然后和明文与或运算的结果。因此,流密码的安 全性可以等价于,在已知任意前面和任意后面的流密码密钥,无法过得被攻击密钥段的任何内容。这是流密码设计本身的要求,我建议不要用这个要求反过来解释 many time pad。另外,攻击中实际不需要假设明文的随机性。明文本来就不是随机分布的,因此才会出现KEM比Enc机制要弱的结。

注意,这里面我的理解是有问题的,在后面会涉及到。


玄星:


最大的分歧估计在这里,我理解的OTP是分块,你认为是流。

一旦你要求询问的回答是重新产生key,则Game本身就与标准CPA下的k的产生不同。换成你说的这个这个模型,流密码重新产生key就相当于引入了randomness, 或者说,key-bit的周期超过多项式长度,自然可以考虑CPA。

另外,标准game里的b(针对分块或者public key enc),相当于就是小范围({m0,m1})的让m处于平均分布状态,做了一个unbias, 正是因为这样,才让猜出bit b变得困难。

在这些安全模型下,m本身的分布的确不要求平均。但你介绍的这种攻击方式建立在m符合英文字母、空格在正常文章中的出现分布时才成立。如果m是完全无规律出现的数字或者bit,就没办法这么攻击了吧。


我:

b的理解不对…m0和m1本身可以是任意的,而且是由攻击者自己原定的。将这两个明文提交给挑战者后,挑战者以1/2的概率加密两者其一,并把加密结果返 回给攻击者。这和你的解释是有本质去别的哦!pad本身在流密码下才有意义啊,分组密码中,定义那种CPA安全是可以的,因为加密过程可以引入 randomness,比如IV什么的。

m完全无规律的话,其实加密本身都无意义了。正是因为m有统计规律,才会有古典密码不能用的概念吧?

玄星:

再次整理下我的观点

1. OTP可以理解为分块(意思就是说,加密的是固定长度的m),也可以理解为流(但逃不开的就是k会有周期)

Pad这个名词并非只是在流下面才有意义,否则不会有padding-oracle attack, 也不存在HMAC里面的ipad和opad,更没有padded RSA。

另外,别说简单的XOR流加密,固定IV的OFB和CTR模式的AES同样满足不了CPA。

2. 安全试验里面最重要的就是以平均概率从challenge {m0, m1}里面选出一个m。

如果不是unbias的,考虑极端情况: Pr[M=m0] = Pr[mb = m0] ,则A的成功率会大大增加。

我 强调的unbias选取,描述的是b的作用,你说的1/2概率,描述的也是b在sample集合大小为2的时候的作用,不存在“本质不同”。至于为什么是 1个bit,为什么是1/2,这个需要从香农对bit的定义出发。如果定义log3(n)为n的bit长度(以3为底的对数),b还是1-bit的话,这 个试验同样可以表示为从{m0,m1,m2}中任选,然后让adversary猜,成功率再和1/3作比较。

3. 不仅是古典密码,现代加密的密文里如果不打破固定的概率信息,同样不安全。

我提出概率分布的意思只是在说明你的XOR计算攻击存在的前提条件,成句的26字母+空格这毕竟是个特殊情况,而且这个分布同样会反映在密文里。

我:

对于这个问题,我刚刚和我的老师讨论了一下。我们做公钥密码学,特别是非确定性公钥密码学为主,对于这个问题的理解确实有所偏差。我们讨论的一个最核心点 是,在CPA安全攻击模型中,在攻击者选择m_0和m_1,并得到挑战者返回的c_b后,还能否向挑战者问询m_0或者m_1所对应的密文。这个问题在非 确定性PKE下是无意义的,但是在确定性PKE下是有意义的。我查阅了Boneh公开课的课件,攻击者是可以再向挑战者问询m_0或m_1所对应的密文。 Boneh也指出,所有的电码本模式的Cipher在CPA下都是insecure的。因此,在单钥密码体制中,如果满足CPA安全,需要使用 nonce-based Encryption。nonce是可以由攻击者选取的,但是每一次问询中,nonce必须是distinct的。这也就引入了CBC等模式(IV相当于 nonce)。总的来说,我的理解确实有问题。在非电码本模式下,IV由于是nonce,是不能够固定的(这是前提假设);所有电码本模式的 Cipher,和流密码,如果是确定性加密(不加nonce),不可能得到CPA安全。对于后面所说的明文概率信息,这个定义要加上Randomness 的定义。总之,这种讨论还是很有价值的~ 谢啦

玄星:

哈,能彼此明确自身的观点并且得到补充完善就是最好的了,这次讨论中也让我从更多方面来思考基本定义的意义,同样感谢。

至此,讨论结束。有兴趣的朋友们可以对应我们两个的回答,并进行自己的思考。总的来说,我们两个的回答都是对的,但都有不正确的地方。玄星同学的回答是从CPA的定义上,我的回答是一种攻击方法。这种攻击方法并不是一般性的,仅展示了Many Times Pad不安全的直观结论。偏向理论的同学可以参考玄星同学的答案。对于实际的攻击,可以参考我的答案。

================================分割线================================

1 多次使用One-time pad为何不安全?

实际上,More than one-time pad的安全性并非是因为Chosen Plaintext Attack。在实际使用中,我们要求流密钥k是不能够使用一次以上的。这样一来,在CPA安全证明中,实际我们是不能允许攻击者重复问询密钥k所对应的明文/密文对的。

玄星同学几乎回答到了核心问题上面,我这里同样按照他的例子来说明。设k是One-time Pad的密钥key。则对于消息m1,加密出来的密文是

c_1 = m_1 \oplus k

对于消息m2,如果k没变,则加密出来的密文是

c_2 = m_2 \oplus k

如果攻击者不知道m1,也不知道m2,则很难算出来k,没法“解密”。但是,实际上攻击者不需要知道k,也可以获取到m1和m2的相关信息。实际上,我们对c1和c2直接进行与或运算,则有

c_1 \oplus c_2 = m_1 \oplus k \oplus m_2 \oplus k = m_1 \oplus m_2

我们可以发现,结果中把k干掉了,直接是m1与m2的与或结果。结合m1和m2中的一些统计信息,我们就可以部分恢复出m1或者m2的内容,这样一来就能够泄露明文信息了。


举一个简单的例子,假设m1和m2都是英文,那么我们知道英文中“空格”或者字母“e”的数量会很多。而“空格”或者字母“e”的编码形式实际上是公开可知的,那么,我们对

c_1 \oplus c_2

在进行全“空格”或者全字母“e”编码的与或运算,如果结果中有有意义的信息,那么我们就知道对应位中的c1或者c2中有“空格”或者字母“e”,而另一部分则是那个有意义的信息。

================================分割线================================

2 一个例子

这个解释起来比较复杂,我们直接来看Dan Boneh在公开课《Cryptology》中给出的一个Programming Assignment的例子吧!这个问题的题目就是:Many Time Pad。原文请参见Dan Boneh的公开课,以及我自己的技术博客:

Stanford - Cryptography I

Many Time Pad
Let us see what goes wrong when a stream cipher key is used more than once. Below are eleven hex-encoded ciphertexts that are the result of encrypting eleven plaintexts with a stream cipher, all with the same stream cipher key. Your goal is to decrypt the last ciphertext, and submit the secret message within it as solution.

Hint: XOR the ciphertexts together, and consider what happens when a space is XORed with a character in [a-zA-Z].

ciphertext #1:


315c4eeaa8b5f8aaf9174145bf43e1784b8fa00dc71d885a804e5ee9fa40b16349c146fb778cdf2d3aff021dfff5b403b510d0d0455468aeb98622b137dae857553ccd8883a7bc37520e06e515d22c954eba5025b8cc57ee59418ce7dc6bc41556bdb36bbca3e8774301fbcaa3b83b220809560987815f65286764703de0f3d524400a19b159610b11ef3e

ciphertext #2:

234c02ecbbfbafa3ed18510abd11fa724fcda2018a1a8342cf064bbde548b12b07df44ba7191d9606ef4081ffde5ad46a5069d9f7f543bedb9c861bf29c7e205132eda9382b0bc2c5c4b45f919cf3a9f1cb74151f6d551f4480c82b2cb24cc5b028aa76eb7b4ab24171ab3cdadb8356f

ciphertext #3:

32510ba9a7b2bba9b8005d43a304b5714cc0bb0c8a34884dd91304b8ad40b62b07df44ba6e9d8a2368e51d04e0e7b207b70b9b8261112bacb6c866a232dfe257527dc29398f5f3251a0d47e503c66e935de81230b59b7afb5f41afa8d661cb

ciphertext #4:

32510ba9aab2a8a4fd06414fb517b5605cc0aa0dc91a8908c2064ba8ad5ea06a029056f47a8ad3306ef5021eafe1ac01a81197847a5c68a1b78769a37bc8f4575432c198ccb4ef63590256e305cd3a9544ee4160ead45aef520489e7da7d835402bca670bda8eb775200b8dabbba246b130f040d8ec6447e2c767f3d30ed81ea2e4c1404e1315a1010e7229be6636aaa

ciphertext #5:

3f561ba9adb4b6ebec54424ba317b564418fac0dd35f8c08d31a1fe9e24fe56808c213f17c81d9607cee021dafe1e001b21ade877a5e68bea88d61b93ac5ee0d562e8e9582f5ef375f0a4ae20ed86e935de81230b59b73fb4302cd95d770c65b40aaa065f2a5e33a5a0bb5dcaba43722130f042f8ec85b7c2070

ciphertext #6:

32510bfbacfbb9befd54415da243e1695ecabd58c519cd4bd2061bbde24eb76a19d84aba34d8de287be84d07e7e9a30ee714979c7e1123a8bd9822a33ecaf512472e8e8f8db3f9635c1949e640c621854eba0d79eccf52ff111284b4cc61d11902aebc66f2b2e436434eacc0aba938220b084800c2ca4e693522643573b2c4ce35050b0cf774201f0fe52ac9f26d71b6cf61a711cc229f77ace7aa88a2f19983122b11be87a59c355d25f8e4

ciphertext #7:

32510bfbacfbb9befd54415da243e1695ecabd58c519cd4bd90f1fa6ea5ba47b01c909ba7696cf606ef40c04afe1ac0aa8148dd066592ded9f8774b529c7ea125d298e8883f5e9305f4b44f915cb2bd05af51373fd9b4af511039fa2d96f83414aaaf261bda2e97b170fb5cce2a53e675c154c0d9681596934777e2275b381ce2e40582afe67650b13e72287ff2270abcf73bb028932836fbdecfecee0a3b894473c1bbeb6b4913a536ce4f9b13f1efff71ea313c8661dd9a4ce

ciphertext #8:

315c4eeaa8b5f8bffd11155ea506b56041c6a00c8a08854dd21a4bbde54ce56801d943ba708b8a3574f40c00fff9e00fa1439fd0654327a3bfc860b92f89ee04132ecb9298f5fd2d5e4b45e40ecc3b9d59e9417df7c95bba410e9aa2ca24c5474da2f276baa3ac325918b2daada43d6712150441c2e04f6565517f317da9d3

ciphertext #9:

271946f9bbb2aeadec111841a81abc300ecaa01bd8069d5cc91005e9fe4aad6e04d513e96d99de2569bc5e50eeeca709b50a8a987f4264edb6896fb537d0a716132ddc938fb0f836480e06ed0fcd6e9759f40462f9cf57f4564186a2c1778f1543efa270bda5e933421cbe88a4a52222190f471e9bd15f652b653b7071aec59a2705081ffe72651d08f822c9ed6d76e48b63ab15d0208573a7eef027

ciphertext #10:

466d06ece998b7a2fb1d464fed2ced7641ddaa3cc31c9941cf110abbf409ed39598005b3399ccfafb61d0315fca0a314be138a9f32503bedac8067f03adbf3575c3b8edc9ba7f537530541ab0f9f3cd04ff50d66f1d559ba520e89a2cb2a83

target ciphertext (decrypt this one):

32510ba9babebbbefd001547a810e67149caee11d945cd7fc81a05e9f85aac650e9052ba6a8cd8257bf14d13e6f0a803b54fde9e77472dbff89d71b57bddef121336cb85ccb8f3315f4b52e301d16e9f52f904

这道题如果不刻意做的话,似乎是没办法写成一个程序,然后处理并等待结果的。为什么呢?因为这道题中间的几个步骤需要我们用“眼睛”来看。做出这道题,核心点就是理解hint中的提示。

首先,我们需要查阅一下space,即空格的ASCII码,以及字母[a-z]和[A-Z]的ASCII码,并寻找一下他们之间的关系。为了后面方便,我们将ASCII码用8位2进制来表示。


在ASCII码表中,[A-Z]用[0x41-0x5a]表示。相应地,[a-z]用[0x61-0x7a]表示。而空格,则用0x20表示。空格(SP)这个表示带来了一个很巧妙的转换:如果一个大写字母与空格与或,那么结果为一个对应的小写字母;如果一个小写字母与空格与或,那么结果为一个对应的大写字母!举两个例子:

a \oplus SP = 01100001 \oplus 00100000 = 01000001 = A
A \oplus SP = a \oplus SP \oplus SP = a

同时,解密过程中也用到了xor的另一个性质:对于一个数,连续与或两次任意相同的数,其结果与原数相同。用公式表示就是,对于任意的x和y:

x \oplus y \oplus y = x

根据提示,每一个ciphertext都是用相同的streamcipher加密的。因此,假设plaintext分别为m1、m2,那么c1 = m1 \oplus kc2 = m2 \oplus k,于是c1 \oplus c2= m1 \oplus k \oplus m2 \oplus k = m1 \oplus m2。这样我们就把k消去,只剩下了m1和m2。


那么,我们的解法就很显然了:随便找任意给定的m_i,m_j相与或,如果发现了有意义的英文字母,那么对应位上很可能一个是空格,另一个是英文字母。

因此我们可以得到正确的Target Text: The secret message is: when using a stream cipher, never use the key more than once。在此我也贡献出中间需要的一些源代码:

    import java.io.UnsupportedEncodingException;  
    import java.math.BigInteger;  
      
    public class W1Solution {  
        //给定的10个ciphertext以及target ciphertext  
        private final String string_cipher_text01 = "315c4eeaa8b5f8aaf9174145bf43e1784b8fa00dc71d885a804e5ee9fa40b1" +  
       "6349c146fb778cdf2d3aff021dfff5b403b510d0d0455468aeb98622b137dae857553ccd8883a7bc37520e06e515d22c954eba50";  
       
        private final String string_cipher_text02 = "234c02ecbbfbafa3ed18510abd11fa724fcda2018a1a8342cf064bbde548b12b07df" +  
       "44ba7191d9606ef4081ffde5ad46a5069d9f7f543bedb9c861bf29c7e205132eda9382b0bc2c5c4b45f919cf3a9f1cb741";  
       
        private final String string_cipher_text03 = "32510ba9a7b2bba9b8005d43a304b5714cc0bb0c8a34884dd91304b8ad40b62b07df" +  
       "44ba6e9d8a2368e51d04e0e7b207b70b9b8261112bacb6c866a232dfe257527dc29398f5f3251a0d47e503c66e935de812";  
       
        private final String string_cipher_text04 = "32510ba9aab2a8a4fd06414fb517b5605cc0aa0dc91a8908c2064ba8ad5ea06a0290" +  
       "56f47a8ad3306ef5021eafe1ac01a81197847a5c68a1b78769a37bc8f4575432c198ccb4ef63590256e305cd3a9544ee41";  
       
        private final String string_cipher_text05 = "3f561ba9adb4b6ebec54424ba317b564418fac0dd35f8c08d31a1fe9e24fe56808c2" +  
       "13f17c81d9607cee021dafe1e001b21ade877a5e68bea88d61b93ac5ee0d562e8e9582f5ef375f0a4ae20ed86e935de812";  
       
        private final String string_cipher_text06 = "32510bfbacfbb9befd54415da243e1695ecabd58c519cd4bd2061bbde24eb76a19d8" +  
       "4aba34d8de287be84d07e7e9a30ee714979c7e1123a8bd9822a33ecaf512472e8e8f8db3f9635c1949e640c621854eba0d";  
       
        private final String string_cipher_text07 = "32510bfbacfbb9befd54415da243e1695ecabd58c519cd4bd90f1fa6ea5ba47b01c9" +  
       "09ba7696cf606ef40c04afe1ac0aa8148dd066592ded9f8774b529c7ea125d298e8883f5e9305f4b44f915cb2bd05af513";  
       
        private final String string_cipher_text08 = "315c4eeaa8b5f8bffd11155ea506b56041c6a00c8a08854dd21a4bbde54ce56801d9" +  
       "43ba708b8a3574f40c00fff9e00fa1439fd0654327a3bfc860b92f89ee04132ecb9298f5fd2d5e4b45e40ecc3b9d59e941";  
       
        private final String string_cipher_text09 = "271946f9bbb2aeadec111841a81abc300ecaa01bd8069d5cc91005e9fe4aad6e04d5" +  
       "13e96d99de2569bc5e50eeeca709b50a8a987f4264edb6896fb537d0a716132ddc938fb0f836480e06ed0fcd6e9759f404";  
       
        private final String string_cipher_text10 = "466d06ece998b7a2fb1d464fed2ced7641ddaa3cc31c9941cf110abbf409ed395980" +  
       "05b3399ccfafb61d0315fca0a314be138a9f32503bedac8067f03adbf3575c3b8edc9ba7f537530541ab0f9f3cd04ff50d";  
       
        private final String target_text          = "32510ba9babebbbefd001547a810e67149caee11d945cd7fc81a05e9f85aac650e90" +  
       "52ba6a8cd8257bf14d13e6f0a803b54fde9e77472dbff89d71b57bddef121336cb85ccb8f3315f4b52e301d16e9f52f904";  
       
        //这里是猜想到的key,我在这已经给出最终key的结果  
        private final String guest_key            = "66396e89c9dbd8cc9874352acd6395102eafce78aa7fed28a07f6bc98d29c50b69b0" +  
       "339a19f8aa401a9c6d708f80c066c763fef0123148cdd8e802d05ba98777335daefcecd59c433a6b268b60bf4eF03C9A61";  
      
        //将每一个ciphertext转换成为BigInteger  
        private BigInteger bigIntegerText01 = new BigInteger(string_cipher_text01,16);  
        private BigInteger bigIntegerText02 = new BigInteger(string_cipher_text02,16);  
        private BigInteger bigIntegerText03 = new BigInteger(string_cipher_text03,16);  
        private BigInteger bigIntegerText04 = new BigInteger(string_cipher_text04,16);  
        private BigInteger bigIntegerText05 = new BigInteger(string_cipher_text05,16);  
        private BigInteger bigIntegerText06 = new BigInteger(string_cipher_text06,16);  
        private BigInteger bigIntegerText07 = new BigInteger(string_cipher_text07,16);  
        private BigInteger bigIntegerText08 = new BigInteger(string_cipher_text08,16);  
        private BigInteger bigIntegerText09 = new BigInteger(string_cipher_text09,16);  
        private BigInteger bigIntegerText10 = new BigInteger(string_cipher_text10,16);  
        private BigInteger bigIntgerTargetText = new BigInteger(target_text,16);  
        private BigInteger bigIntegerGuestKey = new BigInteger(guest_key,16);  
      
        //计算过程如下  
        public void calculateAndPrint(){  
            try {  
                System.out.println("Plaintext 01: " + new String(bigIntegerGuestKey.xor(bigIntegerText01).toByteArray(), "GBK"));  
                System.out.println("Plaintext 02: " + new String(bigIntegerGuestKey.xor(bigIntegerText02).toByteArray(), "GBK"));  
                System.out.println("Plaintext 03: " + new String(bigIntegerGuestKey.xor(bigIntegerText03).toByteArray(), "GBK"));  
                System.out.println("Plaintext 04: " + new String(bigIntegerGuestKey.xor(bigIntegerText04).toByteArray(), "GBK"));  
                System.out.println("Plaintext 05: " + new String(bigIntegerGuestKey.xor(bigIntegerText05).toByteArray(), "GBK"));  
                System.out.println("Plaintext 06: " + new String(bigIntegerGuestKey.xor(bigIntegerText06).toByteArray(), "GBK"));  
                System.out.println("Plaintext 07: " + new String(bigIntegerGuestKey.xor(bigIntegerText07).toByteArray(), "GBK"));  
                System.out.println("Plaintext 08: " + new String(bigIntegerGuestKey.xor(bigIntegerText08).toByteArray(), "GBK"));  
                System.out.println("Plaintext 09: " + new String(bigIntegerGuestKey.xor(bigIntegerText09).toByteArray(), "GBK"));  
                System.out.println("Plaintext 10: " + new String(bigIntegerGuestKey.xor(bigIntegerText10).toByteArray(), "GBK"));  
                System.out.println("Target Text : " + new String(bigIntegerGuestKey.xor(bigIntgerTargetText).toByteArray(), "GBK"));  
            }catch (UnsupportedEncodingException e) {  
                e.printStackTrace();  
            }  
        }  
        public static void main(String[] args){  
            new W1Solution().calculateAndPrint();  
        }  
    }  

感谢各位前辈的回答。今天偶然又看到自己当年刚接触密码学时提的这个问题,自己也来回答一下。

正如很多答主所说,m_1\oplus key = e_1m_2\oplus key = e_2

仅由e_1e_2就可以得出e_1\oplus e_2 = m_1 \oplus key \oplus m_2 \oplus key = m_1 \oplus m_2

当年作为一个密码学小白,我不太理解的是为什么泄露了m_1 \oplus m_2是件严重的事情。所以今天就用图片展示一个直观的例子。

假设我们的明文m_1, m_2是两幅01二值的黑白图片,其中一幅是我的自画像,一幅是我的知乎用户名缩写。

被重复使用的key (one time pad)是幅同样大小的随机二值图片。

如下图:

m_1=

m_2=

key=

现在分别用同一个OTP做异或操作,得到

e_1=m_1\oplus key=

e_2 = m_2\oplus key=

嗯,两个密文似乎都没有泄露什么信息。然而,

e_1\oplus e_2 = m_1\oplus m_2=

信息泄露啦!