4

So, we're told to not use the same key for one-time pad, because if an attacker knows the two cipher texts, he can get the XOR of the two plain texts. For example:

Plain Text1: 0001011
Key        : 1010110
Ciphertext : 1011101

Plain Text2: 0110011
Key        : 1010110
Ciphertext : 1100101

XOR of ciphertexts
1011101
1100101
0111000

XOR of plaintexts (which of course match)
0001011
0110011
0111000

But what advantage exactly this information gives an attacker? What can he do with the XOR of the two plain texts?

good_evening
  • 21,085
  • 65
  • 193
  • 298
  • Not exactly the XOR of the two ciphertexts, but one can perform a brute-force attack, XORring the first ciphertext as well as the second one with the current, guessed key; if they match, bingo. –  Sep 30 '13 at 16:34
  • I'm not a crypto expert but wouldn't the attacker knowing one of the plaintexts be able to recover the other one? – szx Sep 30 '13 at 17:19
  • Note that the key can be brute forced one letter at a time if several ciphertexts are known – this method does not involve XORing ciphertexts together. – ntoskrnl Sep 30 '13 at 18:32

3 Answers3

3

I guess there will be a lot of other answers, but you can do the following - try guessing that a known word is in either text at a given position and xor that position with the word. If the value looks reasonable (statistically looks like the plaintext you're interested in), then you know part of both plaintexts.

Let's say you have the following xor of plaintexts (or ciphertexts, it's the same for the situation described in the question where ciphertext == plaintext xor OTP):

"\x10\x00\x1f\x17E\x0c\x00H\r\x1dR\x06\x0bK\x0c\x0e\x03\x1aE\x01\rR\x1a\x1a\x06P\x04\x00RE"

now you try to match words from a dictionary and find that if you xor this string with "correct" at position 1, you get:

some ot

Ok, so your plaintexts are most likely:

correct.....
some ot.....

Now try to xor words starting with "ot..." with the xor and find out that for "other" you get (along with known beginning):

correct ho

So your plaintexts are:

correct ho....
some other....

etc. Continue this way and you can recover both complete strings. For plaintexts that are not English words this will be harder of course, but still possible. And you don't need to know the OTP contents at any point.

viraptor
  • 33,322
  • 10
  • 107
  • 191
  • @KerrekSB Assuming the ciphertext was produced by OTP xor plaintext, then ciphertext1 xor ciphertext2 == plaintext1 xor plaintext2. And the example above is for that value (xor of the messages). – viraptor Sep 30 '13 at 16:51
2

The xor of two plaintexts is very useful for an attacker. Just as an example, space characters (ascii 32) when xored with alphabetic characters, just change their case. So if one plaintext has lots of spaces in it, you can just read off the other plaintext by inverting the case.

Keith Randall
  • 22,985
  • 2
  • 35
  • 54
2

Lest take letters used as one time pass (using circular arithmetic). If one gets cyper-text

GUTV

One can't know anything about plain-text except that it is 4 letters long. It can be BOMB or LOVE or any other word. You cant distinguishing and neither is more probable than another.

But if you have two plain-texts using same pad, that mean than once you take one solution, second one is automatically defined.

Using some trial and error (or computer with vocabulary) you can significantly reduce to just few options at worst.

So you get from something that is total secure, (true one time pad) to something that is easily breakable.

EDIT

Here is little cracker writen in python You need file of words. I take if from http://norvig.com/big.txt

What this code does it takes two words bomb and love and uses pad/password haha to encrypt both. It then finds out all possible passwords that decrypts this cypertexts pairs to words from vocabulary.

import re

with open("big.txt","r") as f:
    words = set(re.findall('[a-z]+', f.read().lower())) 

def encrypt(word,password):
   add_letters = lambda x,y:chr((ord(x)+ord(y)-2*ord('a'))%26 + ord('a'))
   return "".join(add_letters(*i) for i in zip(word.lower(),password.lower()))

def decrypt(word,password):
   sub_let = lambda x,y:chr((ord(x)-ord(y)+26)%26 + ord('a'))
   return "".join(sub_let(*i) for i in zip(word.lower(),password.lower()))


def crack(a,b):
    assert(len(a) == len(b))
    w = (i for i in words if len(i) == len(a))
    for i in w:
        password = decrypt(a,i)
        b_plain= decrypt(b,password)
        if b_plain in words:
            print(i,b_plain,password)

password = "haha"
a="bomb"
b="love"

a_cyper=encrypt(a,password)
b_cyper=encrypt(b,password)

print("cyper",a_cyper,b_cyper)

crack(a_cyper,b_cyper)

Output:

('cyper', 'iotb', 'soce')
('tomb', 'dove', 'paha')
('bath', 'lack', 'hoau')
('bomb', 'love', 'haha')  <---
('vote', 'foch', 'naax')
('reid', 'berg', 'rkly')
('tank', 'dawn', 'pogr')
('felo', 'peur', 'dkin')
('hath', 'rack', 'boau')
('cork', 'moan', 'gacr')
('rath', 'back', 'roau')
('ruth', 'buck', 'ruau')
('bank', 'lawn', 'hogr')
('rake', 'bath', 'rojx')
('mike', 'with', 'wgjx')
('hero', 'rear', 'bkcn')
('comb', 'move', 'gaha')
('foch', 'polk', 'daru')
('foci', 'poll', 'dart')
('both', 'lock', 'haau')
('peri', 'zeal', 'tkct')
('maim', 'warp', 'wolp')
('limb', 'vive', 'xgha')
Luka Rahne
  • 10,336
  • 3
  • 34
  • 56