import re, sys, operator, itertools, getopt
FREQ_CHARS = ['e', ' ', 'n', 'i', 's']
FREQ_WORDS = ['der', 'die', 'und', 'in', 'den']
HELP_TEXT = 'decryptxor.py -i <inputfile> [-e <encoding>]'
def decrypt(ciphertext, pw, encoding):
plaintext = bytearray(ciphertext)
length = len(pw)
for i in range(len(plaintext)):
plaintext[i] = plaintext[i] ^ ord(pw[i % length])
return plaintext.decode(encoding = encoding, errors = 'replace')
def getMostFreqChars(ciphertext, pwLength):
dicts = [{} for i in range(pwLength)]
i = 0
for byte in ciphertext:
dic = dicts[i]
if byte in dic:
dic[byte] += 1
else:
dic[byte] = 1
i = (i + 1) % pwLength
pairs = [(0,0) for i in range(pwLength)]
i = 0
for dic in dicts:
for byte in dic:
freq = dic[byte]
if freq > pairs[i][1]:
pairs[i] = (byte, freq)
i += 1
return [pair[0] for pair in pairs]
def getMostFreqWords(text, count):
dic = {}
for word in text.split(' '):
word = word.strip(' \t\n\r,.;:?!()[]{}\'\"').lower()
if word in dic:
dic[word] += 1
else:
dic[word] = 1
mostFreq = sorted(dic.items(), key = operator.itemgetter(1), reverse = True)
return [x[0] for x in mostFreq[:count]]
def getPassword(ciphertext, pwLength, encoding):
chars = getMostFreqChars(ciphertext, pwLength)
for i in range(1, len(FREQ_CHARS)):
for product in itertools.product(FREQ_CHARS[:i], repeat = pwLength):
pw = ''.join([chr(chars[i] ^ ord(product[i])) for i in range(pwLength)])
plaintext = decrypt(ciphertext, pw, encoding)
words = getMostFreqWords(plaintext, 5)
if (all([re.match("^[^\W\d]+$", x) for x in words])
and any([x in words for x in FREQ_WORDS])):
return pw, plaintext
return None, ciphertext
def getPasswordLength(ciphertext):
textLength = len(ciphertext)
maxKeyLength = min(textLength, 128)
coincidences = {}
for i in range(1, maxKeyLength):
coincidences[i] = 0
for j in range(textLength):
if ciphertext[j] is ciphertext[(j + i) % textLength]:
coincidences[i] += 1
list = sorted(coincidences.items(), key = operator.itemgetter(1), reverse = True)
listLength = min(len(list), 10)
list = list[:listLength]
for i in range(listLength - 1):
maxIdx = max(list[i][0], list[i + 1][0])
minIdx = min(list[i][0], list[i + 1][0])
if maxIdx % minIdx is 0:
minAbs = minIdx
for j in range(listLength):
maxIdx = max(list[j][0], minAbs)
minIdx = min(list[j][0], minAbs)
if maxIdx % minIdx is 0 and minIdx < minAbs and minIdx > 1:
minAbs = minIdx
return minAbs
return 1
def main(argv):
inputfile = ''
encoding = 'utf-8'
try:
opts, args = getopt.getopt(argv,'hi:e:',['ifile=','encoding='])
except getopt.GetoptError:
print(HELP_TEXT)
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
print(HELP_TEXT)
sys.exit()
elif opt in ('-i', '--ifile'):
inputfile = arg
elif opt in ('-e', '--encoding'):
encoding = arg
with open(inputfile, 'rb') as file:
ciphertext = file.read()
pwLength = getPasswordLength(ciphertext)
pw, plaintext = getPassword(ciphertext, pwLength, encoding)
if pw is not None:
print('Password: ' + pw)
print('')
print(plaintext.replace(u'\ufffd', '?'))
else:
print('I failed :-(')
if __name__ == '__main__':
main(sys.argv[1:])