def findBlockSize(oracle, upper_bound=256): """ Given a function and assuming that it pads its input with some prefix and suffix and then applies a block cipher such that each block of the ciphertext depends only on the key and the corresponding block of the plaintext (for example, AES-ECB), find the block size of the cipher. Algorithm: If the above description is true of a function, then the length of its output grows in discrete steps with the size of each step equal to the block size. So we compute the length of the function's output on empty input, and then feed the function increasingly long messages until the length of the output jumps. The difference between the new output length and the length on empty input is the block size. Args: function (function: Message -> Message): the function whose block size is to be determined. upper_bound (int): the upper bound on the range of possible block sizes to check. Must be an integer in [1, 255] inclusive. Returns: int: the likely block size of the cipher (a positive integer in range [1, 'upper_bound'-1]). Raises: AssertionError, "'upper_bound' must be an integer in range [1, 255] inclusive": if 'upper_bound' is invalid. InvalidAssumptions: in case the output length does not jump before 'upper_bound' is reached. """ assert (0 < upper_bound and upper_bound < 256), "'upper_bound' must be an integer in range [1, 255] inclusive" init_length = len(oracle(Message(b''))) block_size = 0 for k in range(upper_bound): test_len = len(oracle(Message(b'\x00' * k))) if test_len > init_length: block_size = test_len - init_length return block_size raise InvalidAssumptions
def AES_ECB(msg, key, fn='encrypt', strict=True): """ Encrypts or decrypts a message under a specified key using AES in ECB mode. Args: msg (Message): the message to be en/decrypted. key (Message): the key to be used for en/decryption. fn (string): the operation which should be performed using the cipher. Options are 'encrypt' (default) and 'decrypt'. strict (bool): if True, expect that padding scheme always adds padding bytes to messages even if the length of the message is a multiple of the block size (as PKCS#7 padding requires); if False, expect that padding bytes are added only if length of the message is not a multiple of the block size (as is desirable for non-terminal message blocks in AES-CBC). Returns: Message: the en/decryption of 'msg' under 'key' using AES in ECB mode. Raises: InvalidFn: if 'fn' is not equal to 'encrypt' or 'decrypt'. """ if fn not in valid_fns: raise InvalidFn cipher = AES.new(key.bytes, AES.MODE_ECB) if fn == 'encrypt': blocks = listBlocks(msg.pad(16, strict)) out_blocks = [Message(cipher.encrypt(block.bytes)) for block in blocks] else: blocks = listBlocks(msg) out_blocks = [Message(cipher.decrypt(block.bytes)) for block in blocks] return joinBlocks(out_blocks)
def test_Message_createDisplayInt(test_int, end): if test_int < 0: with raises(Exception): test_msg = Message(test_int, 'int', end) else: test_msg = Message(test_int, 'int', end) assert test_msg.int(end) == test_int
def decryptPostfixECB(oracle): """ Given a function and assuming that it concatenates its input with a prefix and postfix, and then applies a block cipher such that each block of the ciphertext depends only on the key and the corresponding block of the plaintext (e.g., AES-ECB), decrypt the postfix without using the decryption key. Args: function (function: Message -> Message): a function which satisfies the assumptions described above. verbose (bool): if True, prints each block of the postfix marquee-style as it is decrypted. Returns: Message: the postfix which 'function' concatenates with its input before encryption. """ block_size = findBlockSize(oracle) prefix_blocks = prefixBlocks(oracle, block_size) offset = prefixOffset(oracle, block_size, prefix_blocks) prefix_length = prefixLength(block_size, prefix_blocks, offset) postfix_length = postfixLength(oracle, block_size, prefix_length) num_blocks = int(ceil(postfix_length / block_size)) known_bytes = Message(b'') out_blocks = [Message(b'\x00' * block_size)] for block in range(num_blocks): for ch in range(block_size): known_bytes += decryptPostfixByteECB(oracle, block_size, offset, known_bytes, prev_block=out_blocks[block]) out_blocks.append(known_bytes) known_bytes = Message(b'') # slice off garbage bytes in first block and join blocks out = joinBlocks(out_blocks[1:]) return out
def prefixBlocks(oracle, block_size=16): """ Given a function and assuming that it concatenates its input with a prefix (and possibly suffix) and then applies a block cipher such that each block of the ciphertext depends only on the key and the corresponding block of the plaintext (e.g., AES-ECB), and given the block size of the cipher, find the number of blocks which consist exclusively of prefix bytes. Algorithm: We first compute the output of the function on two different one-byte inputs: say, '\x00' and '\x01'. If the prefix fully occupies the first m blocks of the input to the block cipher, then the first m blocks of the two outputs will agree. If in addition the (m+1)st block is not fully occupied by the prefix, then the (m+1)st blocks of the two outputs will disagree. Therefore, the number of blocks fully occupied by the prefix is the number of blocks before the first disagreement in the two outputs. Args: function (function: Message -> Message): a function which is assumed to be of the form described above. block_size (int): the block size of the cipher assumed to be used by 'cipher'. Must be in [1, 255] inclusive. Default value is 16. Returns: int: the number of complete blocks occupied by the prefix. Raises: InvalidAssumptions: if the lengths of the two test vectors are unequal, or if their length is not a multiple of 'block_length'. """ vec0 = oracle(Message(b'\x00')) vec1 = oracle(Message(b'\x01')) assert (0 < block_size and block_size < 256), "\'block_size\' must be an integer in range [1, 255] inclusive" if len(vec0) != len(vec1) or len(vec0) % block_size != 0: raise InvalidAssumptions num_blocks = int(len(vec0)/block_size) for k in range(num_blocks): low = block_size * k high = block_size * (k + 1) if vec0[low:high] != vec1[low:high]: return k return num_blocks
def __init__(self): self.oracle = Oracle(None, prefix, postfix) self.sep_field = Message(b';') self.sep_key = Message(b'=') self.default_keys = [ Message(b'comment1'), Message(b'userdata'), Message(b'comment2') ]
def forgeAdminCookieCBC(factory, try_byte): """ Produce a string which is validated as an admin token by tools.validateAuthString, without knowledge of that function's decryption key. Method: We'll produce an encryption of the following string (shown here with '|'s inserted to show divisions into blocks of 16 bytes): '00000000000000000|comment1=cooking|%20MCs;userdata=|;comment2=%20like|%20a%20pound%20of|%20bacon' using tools.newAuthString (with empty input). Then we'll XOR this encrypted string with a second string such that the decryption of the modified string will have ";admin=heckyeahX" (where X is some character) as its 4th block. The reason we can do this is that flipping the j-th bit in the k-th block of an AES-CBC ciphertext will flip the j-th bit in the (k + 1)-th block of its decryption. (Notice that the k-th block of the cipher- text gets XOR'd with the decryption of the (k + 1)-th block of the ciphertext to produce the (k + 1)-th block of the plaintext!). Specifically, we'll modify the 3rd block of the ciphertext by XORing it with XOR(';admin=heckyeahX', ';comment2=%20lik'), where X runs over some range of characters until the decryption of the modified string parses properly. If the decryption parses, it will also be validated as an admin token because it contains 'admin' as a key. The reason our first choice of X might not succeed is that, depending on the key (tools.rand_key), the decryption of the 3rd block might contain some meta- characters which prevent the decrypted string from parsing properly. In practice, this method usually succeeds in one or two tries. Arg: verbose (bool): if True, print some information about the intermediate steps; silent if False. Returns: bool: True if a string which is validated as an admin token was successfully produced; False if not. """ cookie = factory.newCookie('') current = Message(b';comment2=%20lik') new_cookie = Message(b';comment2=%20lik') wish = Message(b';admin=true;xx=') + Message(bytes([try_byte])) diff = XOR(current, wish) forged = cookie[:32] + XOR(cookie[32:48], diff) + cookie[48:] return forged
def test_challenge02_example(): hex_1 = '1c0111001f010100061a024b53535009181c' hex_2 = '686974207468652062756c6c277320657965' true_xor_hex = '746865206b696420646f6e277420706c6179' msg_1 = Message(hex_1, 'hex') msg_2 = Message(hex_2, 'hex') test_xor_hex = XOR(msg_1, msg_2).hex() assert true_xor_hex == test_xor_hex
def isAdminCookie(self, msg): decr_msg = self.oracle.decryptCTR(msg) try: token = Token.fromMsg(decr_msg, Message(b';'), Message(b'=')) except IndexError: raise InvalidToken try: return token.data[Message(b'admin')] == Message(b'true') except KeyError: return False
def findAffixLengthCTR(oracle): prefix_len, suffix_len = 0, 0 ciphertext_1 = oracle.encryptCTR(Message(b'\x00')) ciphertext_2 = oracle.encryptCTR(Message(b'\x01')) diff = XOR(ciphertext_1, ciphertext_2) for offset in range(len(diff)): if diff[offset] != Message(b'\x00'): prefix_len = offset suffix_len = len(ciphertext_1) - 1 - prefix_len return (prefix_len, suffix_len)
def __init__(self, key=None, prefix=None, postfix=None): if key is None: key = randMsg(16) if prefix is None: prefix = Message(b'') if postfix is None: postfix = Message(b'') self.key = key self.prefix = prefix self.postfix = postfix
def checkHMACSHA1_insecure(self, msg, hmac): true_hmac_bytes = Message(hmacSHA1(self.key, msg), 'hex').bytes test_hmac_bytes = Message(hmac, 'hex').bytes for (byte1, byte2) in zip(true_hmac_bytes, test_hmac_bytes): if byte1 != byte2: return False sleep(.05) if len(true_hmac_bytes) != len(test_hmac_bytes): return False else: return True
def hmacSHA1(key, msg): s = SHA1() block_size = 64 hash_size = 20 if len(key) > block_size: key = Message(s.hash(key), 'hex') key += Message(b'\x00' * (block_size - len(key))) outer_key = XOR(key, Message(b'\x5c' * block_size)) inner_key = XOR(key, Message(b'\x36' * block_size)) inner_msg = Message(s.hash(inner_key), 'hex') + msg hmac = s.hash(outer_key + inner_msg) return hmac
def isAdminAuthCookie(self, mac_pair): (cookie, mac) = mac_pair if not self.oracle.checkMACMD4(cookie, mac): raise BadMAC try: token = Token.fromMsg(cookie, Message(b';'), Message(b'=')) except IndexError: raise InvalidToken try: return token.data[Message(b'admin')] == Message(b'true') except KeyError: return False
def postfixLength(oracle, block_size=16, prefix_length=0): """ Given a function and assuming that it concatenates its input with a prefix and postfix, and then applies a block cipher such that each block of the ciphertext depends only on the key and the corresponding block of the plaintext (e.g., AES-ECB), and given the block size of the cipher and the length of the prefix, find the length of the postfix. Algorithm: Find the length of the output of the function when given the empty string as input. Feed the function increasingly long messages of 0 bytes until the length of the output jumps up to the next multiple of the block size. Here is an illustration of the input to the function's cipher. First with the empty message as our input: ...pp|pppPPPPPPPPPPPPP|PPPPxxxxxxxxxxxx| ^ prefix ^ postfix ^ padding then with one 0 byte as input: ...pp|ppp0PPPPPPPPPPPP|PPPPPxxxxxxxxxxx| then more... ...pp|ppp00000000000PP|PPPPPPPPPPPPPPPPx| then just enough to make the length jump: ...pp|ppp000000000000P|PPPPPPPPPPPPPPPPP|xxxxxxxxxxxxxxxx| The number of bytes we had to enter to get to the first jump in output length is the number of bytes of padding which appeared at the end of the postfix in the input to the function's cipher when we fed the empty message to the function. Subtracting the number of padding bytes and the length of the prefix from the length of the output on the empty string, we get the length of the postfix. Args: function (function: Message -> Message): a function satisfying the assumptions above. block_size (int): the block size of the cipher used in 'function'. Must be in range [1, 255] inclusive. Default value is 16. prefix_length (int): the length of the prefix which 'function' prepends to messages before encryption. Must be a nonnegative integer. Returns: int: the length of the postfix which 'function' appends to messages before encryption. A nonnegative integer. Raises: InvalidAssumptions: if the output length does not jump in 'block_size' steps or fewer. This may indicate that the specified block size is incorrect. """ assert (1 <= block_size and block_size <= 256), "\'block_size\' must be an integer in [1, 256] inclusive" assert (0 <= prefix_length), "\'prefix_length\' must be a nonnegative integer" empty_len = len(oracle(Message(b''))) for k in range(1, (block_size + 1)): fill = Message(b'\x00' * k) test_len = len(oracle(fill)) if test_len > empty_len: break if k > block_size: raise InvalidAssumptions postfix_length = empty_len - prefix_length - k return postfix_length
def crackEditableCTR(ciphertext, edit_fn): keystream = Message(b'') for offset in range(len(ciphertext)): for msg_byte in [Message(bytes([val])) for val in range(256)]: found_byte = False edit = edit_fn(ciphertext, offset, msg_byte) if edit[offset] == Message(b'\x00'): found_byte = True keystream += msg_byte break if msg_byte is Message(b'\xff') and not found_byte: raise Exception('Keystream byte not found') plaintext = XOR(keystream, ciphertext) return plaintext
def newResetToken(self, email, test_mode=False): email_msg = Message(email, 'ascii') token = self.encryptMT19937_32_CTR(email_msg) if test_mode: return (self.time_seed, self.key, token) else: return token
def mtResetFindSeed(reset_fn, user_input, test_mode=False, verbose=False): now = int(time()) hour = 3600 if test_mode: (time_seed, seed, ciphertext) = reset_fn(user_input, test_mode=True) else: ciphertext = reset_fn(user_input, test_mode=False) user_msg = Message(user_input, 'ascii') known_len = len(user_msg) prefix_len = len(ciphertext) - known_len known_ctext = ciphertext[prefix_len:] known_keystream = XOR(user_msg, known_ctext) if verbose: print("Password reset token:", ciphertext) print("Prefix length:", prefix_len) print("Known slice of keystream:", known_keystream) guess_rand_seed = mtCTRCheckSeedRange(known_keystream, prefix_len, 0, 2**16 - 1) if guess_rand_seed: if test_mode: assert (seed == guess_rand_seed and time_seed is False) return (False, guess_rand_seed) guess_time_seed = mtCTRCheckSeedRange(known_keystream, prefix_len, now - hour, now + 1) if guess_time_seed: if test_mode: assert (seed == guess_time_seed and time_seed is True) return (True, guess_time_seed) if test_mode: return (None, None) else: return None
def paddingOracleBlock(validation_oracle, block, prev_block, block_size=16): """ Given an implementation of AES-CBC whose decryption function rejects messages with invalid PKCS#7 padding, and a pair of consecutive blocks of an AES-CBC ciphertext, decrypt the second block without using the decryption key. Method: See the docstring of tools.paddingOracleByte. Args: block (Message): the block of the ciphertext which is to be decrypted. prev_block (Message): the block of the ciphertext which precedes 'block'. block_size (int): the block size of the AES cipher. Default value is 16. Returns: Message: the decryption of 'block'. """ known_bytes = Message(b'') for offset in range(1, 17): known_bytes = paddingOracleByte(validation_oracle, block, prev_block, known_bytes, offset, block_size) + known_bytes return known_bytes
def decryptSeries(factory, cookie_vals, cookie_id_byte, verbatim=True): found = [False for _ in range(cookie_vals)] goal = [True for _ in range(cookie_vals)] decrypts = [Message(b'') for _ in range(cookie_vals)] while found != goal: cookie = factory.newCookie() decryption = paddingOracle(factory.isValidCookie, cookie) cookie_val = int(decryption[cookie_id_byte].bytes) if not found[cookie_val]: found[cookie_val] = True decrypts[cookie_val] = decryption print("Found cookie", cookie_val) print("Putting it all together...\n") if not verbatim: decrypts = [decrypt.stripPad(strict=False) for decrypt in decrypts] series = Message(b'\n'.join(decrypt.bytes for decrypt in decrypts)) return(series)
def __init__(self, time_seed=False): self.prefix = randMsg(0, 20) self.postfix = Message(b'') if time_seed == 'coin_flip': time_seed = True if randint(0, 1) else False if time_seed is True: self.key = int(time()) else: self.key = randint(0, 2**16 - 1) self.time_seed = time_seed
def message(cls) -> Message: """Esse método estatico retorna ou cria uma instancia da classe Message. Returns: Message: instancia de Message. """ if not cls._instance: cls._instance = Message() return cls._instance
def test_Message_padThenStripPad(test_msg, block_size, strict): if block_size < 1 or block_size > 255: with raises(AssertionError): test_msg.pad(block_size, strict).stripPad(block_size, strict) elif strict is False and test_msg == Message(b''): with raises(AssertionError): test_msg.pad(block_size, strict).stripPad(block_size, strict) else: orig_msg = test_msg test_msg.pad(block_size, strict).stripPad(block_size, strict) assert orig_msg == test_msg
def keystreamCTR(key, length, offset=0, nonce=None, block_size=16): """ Generate a sequence of keystream bytes, of a specified length and beginning at a specified offset, for AES in CTR mode. Args: key (Message): the symmetric key to be used for en/decryption. Must have length equal to 'block_size'. length (int): the length of the desired keystream. Must be a positive integer. offset (int): the offset into the full keystream of the first byte of the sequence. Must be a nonnegative integer; default value is 0. nonce (Message): a one-time Message instance used to randomize the keystream. Must have length equal to 'block_size'/2. Default value is a message of zero bytes of appropriate length. block_size (int): the length in bytes of a block. Must be an even integer in range [2, 254] inclusive. Default value is 16. Returns: Message: the portion of the keystream of length 'length' and beginning at 'offset' bytes into the full AES-CTR keystream produced using key 'key' and nonce 'nonce. Raises: AssertionError, "Block size must be an even integer in range [2, 254] inclusive": if 'block_size' is invalid. AssertionError, "Length of keystream must be positive": if 'length' is invalid. AssertionError, "Offset must be nonnegative": if 'offset' is invalid. """ assert (0 < block_size and block_size < 256 and block_size % 2 == 0), "Block size must be an even integer in range [2, 254] inclusive" assert(0 < length), "Length of keystream must be positive" assert(0 <= offset), "Offset must be nonnegative" if nonce is None: nonce = Message(b'\x00' * int(block_size / 2)) start_block = int(offset / block_size) end_block = int((offset + length) / block_size) start_block_offset = offset % block_size keystream = Message(b'') ctr = start_block while ctr <= end_block: ctr_msg = nonce + Message(hex(ctr).lstrip('0x').rjust(16, '0'), 'hex', 'little') keystream += AES_ECB(ctr_msg, key, fn='encrypt', strict=False) ctr += 1 keystream = keystream[start_block_offset: start_block_offset + length] return keystream
def hammingDistance(msg1, msg2): """ Compute the Hamming distance between the byte arrays represented by two Message instances. If the two messages are not of equal byte length, then the shorter message is padded with zero bytes (on the high-order side) to the length of the longer message. Args: msg1 (Message): one of the two messages to be compared. msg2 (Message): the other message to be compared. Returns: int: the number of places at which the bit values of the byte arrays represented by 'msg1' and 'msg2'. Always a nonnegative integer. """ if len(msg1) < len(msg2): msg1 = Message(b'\x00' * (len(msg2) - len(msg1))) + msg1 elif len(msg2) < len(msg1): msg2 = Message(b'\x00' * (len(msg1) - len(msg2))) + msg2 xor = XOR(msg1, msg2) dist = sum(int(bit) for bit in xor.bin()) return dist
def formPad(self, msg_len): # encode the bit length of msg as a big-endian 8-byte word orig_bit_len = msg_len * 8 orig_len_msg = Message(orig_bit_len, 'int') orig_len_msg_padded = Message(b'\x00' * (8 - len(orig_len_msg))) + orig_len_msg # add a '1' bit to msg on the less-significant end and fill it out to a byte with 0s pad = Message(b'\x80') padded_msg_len = msg_len + len(pad) # pad with 0 bytes until padded message length is 8 bytes short of a multiple of 64 bytes if (padded_msg_len % 64 <= 56): pad += Message(b'\x00' * (56 - (padded_msg_len % 64))) else: pad += Message(b'\x00' * (56 + 64 - (padded_msg_len % 64))) # add encoding of bit length of the original msg # length of padded msg should now be a multiple of 64 bytes pad += orig_len_msg_padded assert ((msg_len + len(pad)) % 64 == 0) return pad
def breakIVKeyCBC(factory): block_size = 16 user_input = 'A' * 16 * 3 cookie = factory.newCookie(user_input) cookie_first_block = cookie[:block_size] broken_cookie = cookie_first_block + Message( b'\x00' * 16) + cookie_first_block with open('data/challenge27.log', 'w') as error_log: with redirect_stderr(error_log): try: factory.isAdminCookie(broken_cookie) except InvalidAscii: pass with open('data/challenge27.log', 'rb') as error_log: decrypt = Message(bytes(error_log.read(block_size * 3))) decrypt_first_block = decrypt[:block_size] decrypt_third_block = decrypt[-block_size:] key = XOR(decrypt_first_block, decrypt_third_block) return (key)
def newUserProfile(self, email): user_data = OrderedDict.fromkeys(self.default_keys) user_data[Message(b'email')] = Message(email, 'ascii') user_data[Message(b'uid')] = Message(str(randint(uid_min, uid_max)), 'ascii') user_data[Message(b'role')] = Message(b'user') token = Token(user_data, self.sep_field, self.sep_key) encr_token = self.oracle.encryptECB(token.msg) return encr_token
def decryptPostfixByteECB(oracle, block_size=16, offset=0, prev_bytes='', prev_block=None): """ Given a function which appends a prefix and suffix to user-supplied messages and then encrypts the result with a block cipher in which the encryption of a block depends only on the key and the corresponding block of the plaintext (e.g., AES-ECB), and some data about the function (specified in the arg list) and optionally some known bytes of the postfix, decrypts the next byte of the postfix without using the decryption key. Args: function (function: Message -> Message): a function which satisfies (or probably satisfies) the description above. block_size (int): the block size of the cipher used by 'function'. Must be in range [1, 255] inclusive. Default value is 16. offset (int): the number of bytes remaining in the last full block touched by the prefix. Must be in range [1, 'block_size' - 1] inclusive. prev_bytes (string): the bytes preceding the target byte in the same block as the target byte. Must have length in range [0, 'block_size' - 1] inclusive. prev_block (string): the block preceding the block containing the target byte. If target byte belongs to the first block of the prefix, instead pass '\x00' * 'block_size'. Must have length equal to 'block_size'. Returns: Message: the message of length 1 representing the byte of the postfix following 'prev_bytes', or of the first byte of the next block of the postfix if 'prev_bytes' was empty. """ assert (0 < block_size and block_size < 256), "\'block_size\' must be a in integer in [1, 256] inclusive." assert (0 <= offset and offset < block_size), "\'offset\' must be a positive integer less than \'block_size\'" assert (len(prev_bytes) >= 0 and len(prev_bytes) < block_size), "\'prev_bytes\' must have length in [0, \'block_size\' - 1] inclusive." known_len = len(prev_bytes) assert (0 <= known_len and known_len < block_size), "The length of \'known_bytes\' must be in [0, \'block_size\'-1] inclusive." if prev_block is None: prev_block = Message(b'\x00' * block_size) assert (len(prev_block) == block_size), "If a \'prev_block\' is provided, its length must be equal to \'block_size\'." filler_len = block_size - 1 - known_len null_fill = Message(b'\x00' * block_size) for test_byte in [Message(bytes([k])) for k in range(256)]: test_vec = prev_block[known_len + 1:] + prev_bytes + test_byte padded_test_vec = null_fill[:offset] + test_vec + null_fill[:filler_len] ciphertext = oracle(padded_test_vec) if isAES_ECB(ciphertext): return test_byte return Message(b'')
def extendMACMD4(msg, mac, add_msg, key_len): state_vars = 4 state_hex_digs = 8 state = [ Message(mac[k * state_hex_digs:(k + 1) * state_hex_digs], 'hex', 'little').int() for k in range(state_vars) ] s = MD4() orig_msg_pad = s.formPad(len(msg) + key_len) new_msg = msg + orig_msg_pad + add_msg new_msg_pad = s.formPad(len(new_msg) + key_len) extended_mac = s.hash(add_msg + new_msg_pad, state, pad=False) return (new_msg, extended_mac)