The encryption is described in the following python code:

#!/usr/bin/env python import hashlib import sys import base64 pin = sys.argv[1] code = sys.argv[2] #code = open(sys.argv[2]).read().strip() #print "Code: ", code hashofpass = hashlib.sha1(pin).hexdigest()[:16] LEN = len(hashofpass) #print hashofpass previous = '' result = "" for i in range(0,len(code)): nexta = '' if(previous == ''): nexta = chr(ord(code[i]) ^ ord(hashofpass[i % LEN])) else: nexta = chr(ord(code[i]) ^ ord(hashofpass[i % LEN]) ^ ord(previous)) previous = nexta result += nexta print base64.b64encode(result)

Some remarks from the previous code:

- The length of the XOR key is always 16
- The character set of the key is [0..f]
- We also know the character set of the message ([0..9-,. ])
- More than one key can decrypt an encrypted text into a plain text in this rang
*e!* - Even if it might not be obvious at first, this algorithm is actually a repetitive XOR with a 16 bytes key - simple
*!*

# Transform input into repeating-key XOR def transXOR(enc): enc = base64.b64decode(enc) enc2 = [ascii_d[c] for c in enc] result = [ascii_c[enc2[i] ^ enc2[i-1]] for i in xrange(len(enc2)-1, 0, -1)][::-1] result = ascii_c[enc2[0]] + "".join(result) # print base64.b64encode(result) return resultEach block, starting from the last to second, is XOR-ed with the previous one.

The following script solves this problem, and also validates the results and fixes possible errors in decryption, where multiple keys produce valid plaintexts.

#!/usr/bin/env python import string import base64 import sys import re # Example of input: # "BgAbHU8bSx5JBBEbV1haCwkNDhxLHlUFUwIOA1RBRAsJCwsOVQJHEkYJBgZUVVIHGwAHG0EWRBJOFw8AU0RCGhkdGhBeEUcKWwkFC15VQBQQCAsIXA1dDEQVAwpfVFICEhETD14EVgVUAhYfU1xfDAsODx1KGVIGWwMLAFNGXg4QEBMVRBFMD14PGRhPREkYHwUBHU8UQRJCEgoGVkFFFx4eGhtVGk4DXwgBCl5fSh4YAAIIUwBUBk4CCxxGTkIaHxEEA1EdShpPHBwTXUlOAQEGBg1dB0IVSAcHDF9UVgAcBwYaTRxKH0Ib" Debug = False data = sys.argv[1] charset = "0123456789-,. " key_charset = "0123456789abcdef" key_charset2 = [ord(c) for c in key_charset] keysize = 16 ascii_c = ['\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\t', '\n', '\x0b', '\x0c', '\r', '\x0e', '\x0f', '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', ' ', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '\x7f', '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', '\x87', '\x88', '\x89', '\x8a', '\x8b', '\x8c', '\x8d', '\x8e', '\x8f', '\x90', '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97', '\x98', '\x99', '\x9a', '\x9b', '\x9c', '\x9d', '\x9e', '\x9f', '\xa0', '\xa1', '\xa2', '\xa3', '\xa4', '\xa5', '\xa6', '\xa7', '\xa8', '\xa9', '\xaa', '\xab', '\xac', '\xad', '\xae', '\xaf', '\xb0', '\xb1', '\xb2', '\xb3', '\xb4', '\xb5', '\xb6', '\xb7', '\xb8', '\xb9', '\xba', '\xbb', '\xbc', '\xbd', '\xbe', '\xbf', '\xc0', '\xc1', '\xc2', '\xc3', '\xc4', '\xc5', '\xc6', '\xc7', '\xc8', '\xc9', '\xca', '\xcb', '\xcc', '\xcd', '\xce', '\xcf', '\xd0', '\xd1', '\xd2', '\xd3', '\xd4', '\xd5', '\xd6', '\xd7', '\xd8', '\xd9', '\xda', '\xdb', '\xdc', '\xdd', '\xde', '\xdf', '\xe0', '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7', '\xe8', '\xe9', '\xea', '\xeb', '\xec', '\xed', '\xee', '\xef', '\xf0', '\xf1', '\xf2', '\xf3', '\xf4', '\xf5', '\xf6', '\xf7', '\xf8', '\xf9', '\xfa', '\xfb', '\xfc', '\xfd', '\xfe', '\xff'] ascii_d = {} for c in ascii_c: ascii_d[c] = ord(c) def printd(s): if Debug: print s # Transform input into repeating-key XOR def transXOR(enc): enc = base64.b64decode(enc) enc2 = [ascii_d[c] for c in enc] result = [ascii_c[enc2[i] ^ enc2[i-1]] for i in xrange(len(enc2)-1, 0, -1)][::-1] result = ascii_c[enc2[0]] + "".join(result) # print base64.b64encode(result) return result # Used below for single-char key xor decoding def transpose(blocks): len_b = len(blocks) len_b0 = len(blocks[0]) t = [[blocks[j][i] for j in xrange(len_b)] for i in xrange(len_b0)] return t def isGood(text): text2 = [ascii_d[c] for c in text] # Score: number of valid characters for o in text2: if o > 57: return 0 if o < 44: if o == 32: continue else: return 0 if o == 47: return 0 return 1 # Check for possible errors in the decrypted coordinates # Returns position of the invalid character def check(coords): inv_pos = string.find(coords, '=') if -1 != inv_pos: return inv_pos % keysize inv_pos = string.find(coords, ';') if -1 != inv_pos: return inv_pos % keysize inv_pos = string.find(coords, '/') if -1 != inv_pos: return inv_pos % keysize inv_pos = string.find(coords, '--') if -1 != inv_pos: # Condition needed return inv_pos % keysize inv_pos = string.find(coords, ',,') if -1 != inv_pos: return (inv_pos + 1) % keysize inv_pos = string.find(coords, ' ,') if -1 != inv_pos: return (inv_pos + 1) % keysize inv_pos = string.find(coords, ' .') if -1 != inv_pos: if coords[inv_pos+2] in list("0123456789"): return (inv_pos + 1) % keysize m = re.search('\d\-\d', coords) if m: inv_pos = string.find(coords, m.group(0)) if coords[inv_pos-1] in list("0123456789"): return (inv_pos + 1) % keysize # ,80, m = re.search(',\d\d,', coords) if m: inv_pos = string.find(coords, m.group(0)) return (inv_pos + 3) % keysize # .21. m = re.search('\.\d\d\.', coords) if m: inv_pos = string.find(coords, m.group(0)) return (inv_pos) % keysize # 7,.3 m = re.search('\d,\.\d', coords) if m: inv_pos = string.find(coords, m.group(0)) return (inv_pos + 2) % keysize # ,-60, m = re.search(',-\d\d,', coords) if m: inv_pos = string.find(coords, m.group(0)) return (inv_pos + 4) % keysize # -73, m = re.search('-\d\d,', coords) if m: inv_pos = string.find(coords, m.group(0)) return (inv_pos + 3) % keysize # -61- m = re.search('-\d\d-', coords) if m: inv_pos = string.find(coords, m.group(0)) return (inv_pos + 3) % keysize # -04. m = re.search('-0\d\.', coords) if m: inv_pos = string.find(coords, m.group(0)) return (inv_pos + 1) % keysize # 03. m = re.search('^0\d.', coords) if m: inv_pos = string.find(coords, m.group(0)) return (inv_pos) % keysize #,.. if coords[0] == ',': return 0 # 2,6199 m = re.search('\d,\d\d\d\d', coords) if m: inv_pos = string.find(coords, m.group(0)) return (inv_pos + 1) % keysize inv_pos = string.find(coords, '.-') if -1 != inv_pos: pos = string.find(coords, '.', inv_pos + 1) if pos-inv_pos > 6: return (inv_pos + 1) % keysize else: return inv_pos % keysize return -1 # Decrypt/encrypt function are the same def rxor(text, key): text2 = [ascii_d[c] for c in text] key2 = [ascii_d[c] for c in key] len_k = 16 # len(key) len_t = len(text) dec = [ascii_c[text2[i] ^ key2[i % len_k]] for i in xrange(0, len_t)] return "".join(dec) if __name__ == "__main__": enc = transXOR(data) k = [0] * keysize blocks = [enc[i: i+keysize] for i in xrange(0, len(enc), keysize)] # Transpose the blocks and find the key for each transposed block # Leave the last incomplete block for easier calculations t_blocks = transpose(blocks[:len(blocks)-1]) # all possible values for each byte of the key all_keys = [] for i in xrange(keysize): b = t_blocks[i] b2 = [ascii_d[c] for c in b] d = {} keys_b = [] for key in key_charset2: dec = "".join([ascii_c[c ^ key] for c in b2]) ok = isGood(dec) if ok: d[dec] = key keys_b = [d[w] for w in d] all_keys.append(keys_b) # Take the first possible value. Check later! k[i] = keys_b[0] k = "".join([ascii_c[c] for c in k]) coords = rxor(enc, k) while 1: inv = check(coords) if -1 == inv: break all_keys[inv].remove(all_keys[inv][0]) k = k[:inv] + ascii_c[all_keys[inv][0]] + k[inv+1:] coords = rxor(enc, k) print coords.split(' ')[3] # Original message ''' To profile the code: python -m cProfile -s time ./decode.py BgQaGRYXFxVAVBtERA8EAAICBw0bGhwEVVwNWFQIHR8eBAADBgkICE1YAlRCFBsaHxMWCBMZHwdaVgZQXgsSCQkKFBIRGhgaTVkDXEoYGBscGh8NFhkfB1ZfBFpbBhMICAsVFhMYHRhPVxhJSAMDDAoNBQAaEwsIXVMCVl8aGhQLDgwMAgoEHlZXAUlCHh8cDQ4IFBccHB5CSAZNTBsMCw4CAQYCGh4eVVkCUVoLCxENDA4SEhoVFkFBA0hCCQMNDwwODBYDBABLQRRAQRYWABwQEAwPAAMDX1cZUlwPGBwYGB8eEAgTEkFXDVlXBggOEwoNERIYGxxB '''

The algorithm is generic and can be applied to decrypt any repetitive XOR encrypted text.