Creation

March 3, 2009

A coded message appears on the next page. The cipher-text was created by XORing the characters of a password, repeated as necessary, with the characters of the plain-text. The numbers are the ascii ordinals of the cipher-text characters; the cipher-text is given in that form in order to bypass the limitations of some browsers. The password consists of less than twenty alphanumeric characters (upper-case letters, lower-case letters, and digits). The plain-text consists of ascii text (printable characters, plus spaces and newlines) in English.

Like this:

Related

8 Responses to “Creation”

Can’t believe nobody else commented on this… That one was a lot of fun!
I took a different approach from the proposed Scheme solution, went for a dictionary attack:

#!/usr/bin/python
def read_cipher(textFile):
'''Takes the name of the file containing the encrypted text as argument,
returns the list of ascii ordinals'''
f = open(textFile, 'r')
returnList = [int(a) for a in f.read().split()]
f.close()
return returnList
def decrypt(ordList, password):
'''This function XORs a text with the code (both as list of ordinals!!)
and returns the encoded/decoded list of ordinals'''
from itertools import cycle
decryptedList = [a^ord(b) for a,b in zip(ordList, cycle(password))]
return decryptedList
def make_attack_dictionary(dictFile):
f = open(dictFile,'r')
returnDict = attack_dictionary(f.read().split())
f.close()
return returnDict
def attack_dictionary(rawDict):
'''Takes a list of words as argument, and produces an attack dictionary
from it: with added case variations (also capitalizes the words that
only exist as small case in the initial list), and sorted by length'''
from string import capitalize
# Generate a dict of already capitalized words in the raw dictionary
capWords = {}
for word in rawDict:
if word == capitalize(word):
capWords[word] = 1
# Now capitalize the rest of them; this works in O(n) because capWords
# is a dict
complementDict = [capitalize(word) for word in rawDict if not word in capWords]
# Finally, merge and sort the attack dictionary by word length
return sorted(rawDict + complementDict, key=len)
def words_found(text, dictionary):
'''Returns the number of known words (from dictionary) found in text'''
from re import findall
wordCount = 0
for word in findall(r'\w\w+', text):
if word in dictionary:
wordCount +=1
return wordCount
def attack(cipherText):
'''Performs a dictionary attack on an encrypted text, passed as a
list of integers'''
attackDict = make_attack_dictionary('/usr/share/dict/words')
# We'll also build a dict purely for performance reasons
compareDict = {}
for word in attackDict:
compareDict[word] = 1
# Tweak these parameters for the dictionary attack
attackDepth = 50
attackLengthMax = 8
attackThreshold = 6
# Now try all passwords of length <= attackLengthMax,
# scanning the first attackDepth characters in cipherText
# and print something when finding more than attackThreshold known words
for password in attackDict:
if len(password) > attackLengthMax: break
decryptedText = "".join([chr(a)
for a in decrypt(cipherText[:attackDepth], password)])
wordsFound = words_found(decryptedText, compareDict)
if wordsFound >= attackThreshold:
print '%d matches found with %s' % (wordsFound, password)
if __name__ == '__main__':
cipherText = read_cipher('text')
attack(cipherText)
print 'Password to try?'
password = raw_input()
print "".join([chr(a) for a in decrypt(cipherText, password)])

Basically, I start by building an attack dictionary, sorted by length. Then I brute-force my way through this attack dictionary, trying to decrypt the beginning of the encrypted text using each one of the possible passwords, and I count the number of real words I can find in the decrypted version.

Of course it could probably be automated completely by simply returning the password yielding the highest number of real words, but it works for me like that. Running it as it is shown here (scanning the first 50 letters of the encrypted text, and trying passwords up to 8 letters long), it shows me the list of following possible keys (only showing the keys producing 6 or more known words):
6 matches found with imber
6 matches found with infer
6 matches found with inker
6 matches found with ceresin
6 matches found with chooser
6 matches found with geronto
6 matches found with oenolin
6 matches found with Allower
6 matches found with Ceresin
6 matches found with Chooser
6 matches found with Foresin
8 matches found with Genesis
6 matches found with Geronto
6 matches found with Getaway
6 matches found with Subjoin
6 matches found with Thrower
6 matches found with Vetiver

I followed the ideas of the solution here, but wished to automate the process
a bit more. I had my code try different passwords (given by assuming that space
is the most common character), checking those words against membership in my
system’s dict file. It outputs all possibilities, so it still requires a human
eye to sort through the gobbledygook.

If you are really interested in automating this program fully, Google for ‘Kasiski examination’ and go from there. Like a lot of these things, it’s named for the wrong man; Charles Babbage figured it out twenty years before Kasiski.

Like others I want to automate the process as much as possible.
So my slightly different implementation returns a password and a “confidence number” based on the frequencies of “” and “e”.
Then we can search for all passwords given a maximum length and a confidence threshold.

[…] In their book Software Tools, Brian Kernighan and P. J. Plauger describe a simple command for encrypting files. It works by xor-ing each byte of a file with a byte of the key, extending the key cyclically until it is the same length as the text. The xor operation is symmetric, so only one program is needed to perform both encryption and decryption. This isn’t a particularly secure encryption algorithm; we showed how to break it in one of our earliest exercises. […]