Example #1
0
def decryption_oracle(payload):
    global iv_as_key
    iv = bytes(b'A' * block_size)
    payload = iv + payload + blocks_with_correct_padding
    plaintext = decrypt(payload, iv_as_key)
    if iv_as_key:
        return xor(plaintext[block_size:block_size * 2], iv)
    return xor(plaintext[:block_size], iv)
def break_one_char_key(ciphertext, lang='English', no_of_comparisons=5, alphabet=False, key_space=False,
                       reliability=100.0):
    """Brute for all one-char keys, return most language-like

    Args:
        ciphertext(string): text xored with short key
        lang(string): key in frequencies dict
        no_of_comparisons(int): used during comparing by frequencies
        alphabet(string/None): plaintext space
        key_space(string/None): key space
        reliability(float): between 0 and 100, used during comparing by frequencies

    Returns:
        list: sorted (by frequencies) list of tuples (key, plaintext)
    """
    if not alphabet:
        alphabet = string.printable
    if not key_space:
        key_space = list(map(chr, list(range(256))))

    result = {}
    for key in key_space:
        xored = xor(ciphertext, key)
        if is_printable(xored, alphabet=alphabet, reliability=reliability):
            result[key] = xored
    if result:
        return sorted(list(result.items()), key=operator.itemgetter(1), reverse=True,
                      cmp=lambda x, y: compare_by_frequencies(x, y, lang=lang, no_of_comparisons=no_of_comparisons))
    return False
Example #3
0
def iv_as_key(ciphertext,
              plaintext,
              padding_oracle=None,
              decryption_oracle=None,
              block_size=16):
    """If iv is used as key, we can recover it using decryption oracle, padding oracle or
    known plaintext (if first ciphertext block is repeated)

    Args:
        ciphertext(string): first block must be AES.encrypt(iv xor plaintext[0])
        plaintext(string): with padding
        padding_oracle(function/None)
        decryption_oracle(function/None)
        block_size(int)

    Returns:
        string: key (== iv)
    """
    key = None
    ciphertext = chunks(ciphertext, block_size)
    plaintext = chunks(plaintext, block_size)

    try:
        position_second = ciphertext.index(ciphertext[0], 1)
        log.debug("Position of the same block as the first is {}".format(
            position_second))
        key = xor(plaintext[0], ciphertext[position_second - 1],
                  plaintext[position_second])
    except ValueError:
        log.debug(
            "first ciphertext block is not repeated, will use decryption/padding oracle"
        )

    if key is None:
        iv = bytes(b'A' * block_size)
        iv_xor_plaintext0 = decrypt(ciphertext[0],
                                    padding_oracle=padding_oracle,
                                    decryption_oracle=decryption_oracle,
                                    iv=iv,
                                    block_size=block_size,
                                    is_correct=False,
                                    amount=1)
        iv_xor_plaintext0 = xor(iv_xor_plaintext0, iv)
        key = xor(iv_xor_plaintext0, plaintext[0])
    log.success("Key(hex): {}".format(b2h(key)))
    return key
Example #4
0
def fake_ciphertext(new_plaintext,
                    padding_oracle=None,
                    decryption_oracle=None,
                    block_size=16):
    """Make ciphertext that will decrypt to given plaintext
    Give padding_oracle or decryption_oracle (or both)

    Args:
        new_plaintext(string): with padding
        padding_oracle(function/None)
        decryption_oracle(function/None): maximum one block to decrypt
        block_size(int)

    Returns:
        fake_ciphertext(string): fake ciphertext that will decrypt to new_plaintext
    """
    _check_oracles(padding_oracle=padding_oracle,
                   decryption_oracle=decryption_oracle,
                   block_size=block_size)

    if block_size % 8 != 0:
        log.critical_error("Incorrect block size: {}".format(block_size))

    log.info("Start fake ciphertext")
    ciphertext = bytes(b'A' * (len(new_plaintext) + block_size))

    # prepare blocks
    blocks = chunks(ciphertext, block_size)
    new_pl_blocks = chunks(new_plaintext, block_size)
    if len(new_pl_blocks) != len(blocks) - 1:
        log.critical_error(
            "Wrong new plaintext length({}), should be {}".format(
                len(new_plaintext), block_size * (len(blocks) - 1)))
    new_ct_blocks = list(blocks)

    # add known plaintext

    for count_block in range(len(blocks) - 1, 0, -1):
        """ Every block, modify block[count_block-1] to set block[count_block] """
        log.info("Block no. {}".format(count_block))

        ciphertext_to_decrypt = bytes(b''.join(new_ct_blocks[:count_block +
                                                             1]))
        original_plaintext = decrypt(ciphertext_to_decrypt,
                                     padding_oracle=padding_oracle,
                                     decryption_oracle=decryption_oracle,
                                     block_size=block_size,
                                     amount=1,
                                     is_correct=False)
        log.info("Set block no. {}".format(count_block))
        new_ct_blocks[count_block - 1] = xor(blocks[count_block - 1],
                                             original_plaintext,
                                             new_pl_blocks[count_block - 1])

    fake_ciphertext_res = bytes(b''.join(new_ct_blocks))
    log.success("Fake ciphertext(hex): {}".format(b2h(fake_ciphertext_res)))
    return fake_ciphertext_res
def break_repeated_key(ciphertext, lang='English', no_of_comparisons=5, key_size=None, max_key_size=40,
                       alphabet=None, key_space=None, reliability=100.0):
    """Short key encrypted with long plaintext

    Args:
        ciphertext(string): text xored with short key
        lang(string): key in frequencies dict
        no_of_comparisons(int): used during comparing by frequencies
        key_size(int/None)
        max_key_size(int/None)
        alphabet(string/None): plaintext space
        key_space(string/None): key space
        reliability(float): between 0 and 100, used during comparing by frequencies

    Returns:
        list: sorted (by frequencies) list of tuples (key, plaintext)
    """
    if not key_size:
        key_size = guess_key_size(ciphertext, max_key_size)[0][0]

    cipher_same_key_char = [ciphertext[i::key_size] for i in range(key_size)]
    key = {}
    for position, item in enumerate(cipher_same_key_char):
        key_char = break_one_char_key(item, lang=lang, no_of_comparisons=no_of_comparisons, alphabet=alphabet,
                                      key_space=key_space, reliability=reliability)
        if not key_char:
            continue
        key_char = key_char[0]  # get only most probable, because product may be large
        key[position] = key_char

    key = [x[1] for x in list(key.items())]
    key = [x[0] for x in key]
    if not key:
        return False

    plaintexts = {}
    for guessed_key in itertools.product(*key):
        guessed_key = ''.join(guessed_key)
        plaintexts[guessed_key] = xor(guessed_key, ciphertext)
    return sorted(list(plaintexts.items()), key=operator.itemgetter(1), reverse=True,
                  cmp=lambda x, y: compare_by_frequencies(x, y, lang=lang, no_of_comparisons=no_of_comparisons))
Example #6
0
def bit_flipping(ciphertext, plaintext, wanted, block_size=16):
    """Given ciphertext and corresponding plaintext (two blocks) we can set first block of ciphertext
    so that last block of plaintext will be our wanted value

    Args:
        ciphertext(string): size == 2*block_size
        plaintext(string): size == block_size, plaintext of second ciphertext block
        wanted(string): size == block_size, we want ciphertext last block decrypt to this
        block_size(int)

    Returns:
        string: ciphertext that will decrypt to garbage_block+wanted_last_block
    """
    if len(ciphertext) != 2 * block_size:
        log.critical_error("Incorrect ciphertext size")
    if len(plaintext) != block_size:
        log.critical_error("Incorrect plaintext size")
    if len(wanted) != block_size:
        log.critical_error("Incorrect wanted_last_block size")

    return xor(ciphertext[:block_size], plaintext,
               wanted) + ciphertext[block_size:]
Example #7
0
def decrypt(ciphertext,
            padding_oracle=None,
            decryption_oracle=None,
            iv=None,
            block_size=16,
            is_correct=True,
            amount=0,
            known_plaintext=None,
            async_calls=False):
    """Decrypt ciphertext
    Give padding_oracle or decryption_oracle (or both)

    Args:
        ciphertext(string): to decrypt
        padding_oracle(function/None)
        decryption_oracle(function/None)
        iv(string): if not specified, first block of ciphertext is treated as iv
        block_size(int)
        is_correct(bool): set if ciphertext will decrypt to something with correct padding
        amount(int): how much blocks decrypt (counting from last), zero (default) means all
        known_plaintext(string): with padding, from end (aligned to end of ciphertext)
        async_calls(bool): make asynchronous calls to oracle (not implemented yet)

    Returns:
        plaintext(string): with padding
    """
    _check_oracles(padding_oracle=padding_oracle,
                   decryption_oracle=decryption_oracle,
                   block_size=block_size)

    if block_size % 8 != 0:
        log.critical_error("Incorrect block size: {}".format(block_size))

    if len(ciphertext) % block_size != 0:
        log.critical_error("Incorrect ciphertext length: {}".format(
            len(ciphertext)))

    if decryption_oracle:
        if iv:
            ciphertext = iv + ciphertext
        blocks = chunks(ciphertext, block_size)
        plaintext = bytes(b'')
        for position in range(len(blocks) - 1, 0, -1):
            plaintext = xor(decryption_oracle(blocks[position]),
                            blocks[position - 1]) + plaintext
            log.info("Plaintext(hex): {}".format(b2h(plaintext)))
            if amount != 0 and len(plaintext) == amount * block_size:
                break
        log.success("Decrypted(hex): {}".format(b2h(plaintext)))
        return plaintext

    log.info("Start cbc padding oracle")
    log.debug(print_chunks(chunks(ciphertext, block_size)))

    # prepare blocks
    blocks = chunks(ciphertext, block_size)
    if iv:
        if len(iv) % block_size != 0:
            log.critical_error("Incorrect iv length: {}".format(len(iv)))
        log.info("Set iv")
        blocks.insert(0, iv)

    if amount != 0:
        amount = len(blocks) - amount - 1
    if amount < 0 or amount >= len(blocks):
        log.critical_error(
            "Incorrect amount of blocks to decrypt: {} (have to be in [0,{}]".
            format(amount,
                   len(blocks) - 1))
    log.info("Will decrypt {} block(s)".format(len(blocks) - 1 - amount))

    # add known plaintext
    plaintext = bytes(b'')
    position_known = 0
    chars_decoded = 0
    if known_plaintext:
        is_correct = False
        plaintext = known_plaintext
        blocks_decoded = len(plaintext) // block_size
        chars_decoded = len(plaintext) % block_size

        if blocks_decoded == len(blocks) - 1:
            log.debug("Nothing decrypted, known plaintext long enough")
            return plaintext
        if blocks_decoded > len(blocks) - 1:
            log.critical_error(
                "Too long known plaintext ({} blocks)".format(blocks_decoded))

        if blocks_decoded != 0:
            blocks = blocks[:-blocks_decoded]

        position_known = chars_decoded
        log.info("Have known plaintext, skip {} block(s) and {} bytes".format(
            blocks_decoded, chars_decoded))

    # start decryption
    for count_block in range(len(blocks) - 1, amount, -1):
        """ Blocks from the last to the second (all except iv) """
        log.info("Block no. {}".format(count_block))

        payload_prefix = bytes(b''.join(blocks[:count_block - 1]))
        payload_modify = blocks[count_block - 1]
        payload_decrypt = blocks[count_block]

        if chars_decoded != 0:
            # we know some chars, so modify previous block
            payload_modify = payload_modify[:-chars_decoded] +\
                             xor(plaintext[:chars_decoded], payload_modify[-chars_decoded:], bytes([chars_decoded + 1]))
            chars_decoded = 0

        position = block_size - 1 - position_known
        position_known = 0
        while position >= 0:
            """ Every position in block, from the end """
            log.debug("Position: {}".format(position))

            found_correct_char = False
            for guess_char in range(256):
                modified = payload_modify[:position] + bytes(
                    [guess_char]) + payload_modify[position + 1:]
                payload = bytes(b''.join(
                    [payload_prefix, modified, payload_decrypt]))

                iv = payload[:block_size]
                payload = payload[block_size:]
                log.debug(print_chunks(chunks(iv + payload, block_size)))

                correct = padding_oracle(payload=payload, iv=iv)
                if correct:
                    """ oracle returns True """
                    padding = block_size - position  # sent ciphertext decoded to that padding
                    decrypted_char = bytes(
                        [payload_modify[position] ^ guess_char ^ padding])

                    if is_correct:
                        """ If we didn't send original ciphertext, then we have found original padding value.
                            Otherwise keep searching and if won't find any other correct char - padding is \x01
                        """
                        if guess_char == blocks[-2][-1]:
                            log.debug(
                                "Skip this guess char ({})".format(guess_char))
                            continue

                        dc = int(decrypted_char[0])
                        log.info(
                            "Found padding value for correct ciphertext: {}".
                            format(dc))
                        if dc == 0 or dc > block_size:
                            log.critical_error(
                                "Found bad padding value (given ciphertext may not be correct)"
                            )

                        plaintext = decrypted_char * dc
                        payload_modify = payload_modify[:-dc] + xor(
                            payload_modify[-dc:], decrypted_char,
                            bytes([dc + 1]))
                        position = position - dc + 1
                        is_correct = False
                    else:
                        """ abcd efgh ijkl o|guess_char|xy || 1234 5678 9tre qwer - ciphertext
                            what ever itma ybex            || xyzw rtua lopo k|\x03|\x03\x03 - plaintext
                            abcd efgh ijkl |guess_char|wxy || 1234 5678 9tre qwer - next round ciphertext
                            some thin gels eheh            || xyzw rtua lopo guessing|\x04\x04\x04 - next round plaintext
                        """
                        if position == block_size - 1:
                            """ if we decrypt first byte, check if we didn't hit other padding than \x01 """
                            payload = iv + payload
                            payload = payload[:-block_size - 2] + bytes(
                                b'A') + payload[-block_size - 1:]
                            iv = payload[:block_size]
                            payload = payload[block_size:]
                            correct = padding_oracle(payload=payload, iv=iv)
                            if not correct:
                                log.debug("Hit false positive, guess char({})".
                                          format(guess_char))
                                continue

                        payload_modify = payload_modify[:position] + xor(
                            bytes([guess_char]) +
                            payload_modify[position + 1:], bytes([padding]),
                            bytes([padding + 1]))
                        plaintext = decrypted_char + plaintext

                    found_correct_char = True
                    log.debug(
                        "Guessed char(\\x{:02x}), decrypted char(\\x{:02x})".
                        format(guess_char, decrypted_char[0]))
                    log.debug("Plaintext: {}".format(plaintext))
                    log.info("Plaintext(hex): {}".format(b2h(plaintext)))
                    break
            position -= 1
            if found_correct_char is False:
                if is_correct:
                    padding = 0x01
                    payload_modify = payload_modify[:position + 1] + xor(
                        payload_modify[position + 1:], bytes([padding]),
                        bytes([padding + 1]))
                    plaintext = bytes(b"\x01")
                    is_correct = False
                else:
                    log.critical_error(
                        "Can't find correct padding (oracle function return False 256 times)"
                    )
    log.success("Decrypted(hex): {}".format(b2h(plaintext)))
    return plaintext