Пример #1
0
def get_likely_xor_keysizes(ciphertext, number_of_keysizes=1):
    """Return the most likely repeating keysize for a ciphertext encrypted with a repeating key XOR.

    This is a helper function for break_repeating_key_xor used further down the list of key sizes we need to try.

    :param ciphertext: The ciphertext to find likely key sizes for
    :param number_of_keysizes: An optional variable to set the number of likely key sizes to return
    :return: A list of likely key sizes with the most likely key sizes at smaller indexes.
    """
    normalized_distances = []
    for keysize in xrange(2, 40):
        key_size_chunks = list(chunks(ciphertext, keysize))
        total_distance = 0

        max_blocks = len(key_size_chunks)-1 if len(key_size_chunks) < 40 else 40
        number_of_blocks = 0
        # If the number of blocks in the average distance calculation is too low
        # we may end up with the wrong keysize. Using 40 blocks seems to give
        # results.
        for i in xrange(0, max_blocks, 2):
            total_distance += get_hamming_distance(key_size_chunks[i], key_size_chunks[i + 1])
            number_of_blocks += 1

        avg_distance = float(total_distance) / (number_of_blocks / 2)
        normalized_distances.append((keysize, avg_distance / keysize))

    # The most likely keysizes will have the lowest hamming weight
    normalized_distances.sort(key=lambda x: x[1])

    return [i[0] for i in normalized_distances[:number_of_keysizes]]
Пример #2
0
def aes_ctr_encrypt(plaintext, key, nonce_generator):

    ciphertext = ''

    for pt_block in chunks(plaintext, 16):
        nonce = nonce_generator.next()
        key_steam = aes_ecb_encrypt(nonce, key)

        ciphertext += string_xor(pt_block, key_steam)

    return ciphertext
Пример #3
0
def aes_ctr_decrypt(ciphertext, key, nonce_generator):

    plaintext = ''

    for ct_block in chunks(ciphertext, 16):
        nonce = nonce_generator.next()
        key_steam = aes_ecb_encrypt(nonce, key)

        plaintext += string_xor(ct_block, key_steam)

    return plaintext
Пример #4
0
def cbc_padding_oracle(ciphertext, iv, padding_valid_fn):
    """ Decrypt a CBC encrypted ciphertext using a padding oracle

    :param ciphertext: Ciphertext to decrypt
    :param padding_valid_fn: Function that decrypts the ciphertext, checks the padding, and returns the status of the padding (True or False)
    :return: Recovered plaintext
    """

    block_size = 16

    ct_blocks = list(chunks(iv + ciphertext, block_size))

    pt = ''
    for two_blocks in chunks(ct_blocks[::-1], 2, adv=1):
        try:
            pt = cbc_padding_attack_block(two_blocks[1], two_blocks[0], padding_valid_fn) + pt
        except IndexError:
            break

    return remove_pkcs7_padding(pt)
Пример #5
0
def break_repeating_nonce_ctr(ciphertexts):
    """ Decrypt CTR encrypted ciphertexts that were encrypted with a fixed nonce

    :param ciphertextx: A list of ciphertexts encrypted with a fixed nonce
    :return: A list of decrypted plaintexts
    """
    block_size = 16

    ciphertext = ''.join(ct[:block_size] for ct in ciphertexts)

    guess = list(chunks(break_repeating_key_xor(ciphertext, keysize=block_size), block_size))

    block_keystream = string_xor(guess[0], ciphertexts[0])

    plaintexts = []

    for ct in ciphertexts:
        plaintext = ''
        for block in chunks(ct, block_size):
            plaintext += string_xor(block, block_keystream)

        plaintexts.append(plaintext)

    return plaintexts
Пример #6
0
def aes_cbc_decrypt(ciphertext, key, iv):
    """ Perform AES CBC decryption

    This is the solution to Set 2, Challenge 10
    :param ciphertext: The data to  or decrypt
    :param key: The AES key to use
    :param iv: The initialization vector
    :return: The resulting plaintext
    """
    xor_block = iv
    plaintext = ''

    for ct_block in chunks(ciphertext, 16):
        plaintext += string_xor(aes_ecb_decrypt(ct_block, key), xor_block)
        xor_block = ct_block

    return plaintext
Пример #7
0
def break_repeating_key_xor(ciphertext, keysize=None):
    """Try to break a ciphertext encrypted with a repeating key XOR
    :param ciphertext: Attempt to decrypt this ciphertext
    :return: The recovered plaintext or None on failure
    """
    best_guess = None
    best_guess_unprintable = None

    # Get the 4 most likely keysizes. The first most likely keysize it probably the
    # correct keysize, but if it is not we can try the next couple
    keysizes = get_likely_xor_keysizes(ciphertext, 4) if keysize is None else [keysize]

    for keysize in keysizes:
        ct_chunks = list(chunks(ciphertext, keysize))
        ct_chunks = [[ord(c) for c in chunk] for chunk in ct_chunks]

        # Transpose the blocks so we get lists of the byte at position 0, 1, etc
        transposed_blocks = [[chunk[i] for chunk in ct_chunks if len(chunk) > i]
                             for i in xrange(len(ct_chunks[0]))]

        # Brute force the XOR key one byte at a time
        block_hexstrings = [''.join('%02x' % byte for byte in block)
                            for block in transposed_blocks]

        key = [single_byte_xor_break(hexstring)[0] for hexstring in block_hexstrings]
        key_string = ''.join(chr(c) for c in key)

        plaintext = repeating_key_xor_encrypt(ciphertext, key_string).decode('hex')

        # If everything is a printable ASCII character we definetly have the right
        # key and can stop here
        unprintable_chars = sum(1 for c in plaintext if c not in string.printable)

        if not unprintable_chars:
            return plaintext
        else:
            if best_guess is None:
                best_guess = plaintext
                best_guess_unprintable = unprintable_chars
            else:
                if unprintable_chars < best_guess_unprintable:
                    best_guess = plaintext
                    best_guess_unprintable = unprintable_chars

    # Unable to find a perfect decryption return the best guess
    return best_guess
Пример #8
0
def get_no_prefix_oracle(oracle, block_size):
    # We may have a random number of bytes prepended to our string. We need to add bytes until we start a new block
    # that we have full control of
    for i in xrange(0, 16):
        blocks = list(chunks(oracle('A' * i + 'B' * 32), block_size))

        for j, block in enumerate(blocks):
            try:
                if block == blocks[j+1]:
                    index_of_first_controlled_block = j * block_size
                    number_of_padding_bytes = i
            except IndexError:
                break

    def no_prefix_oracle(plaintext):
        return oracle('B' * number_of_padding_bytes + plaintext)[index_of_first_controlled_block:]

    return no_prefix_oracle
Пример #9
0
def aes_cbc_encrypt(plaintext, key, iv=None):
    """ Perform AES CBC encryption, adding PKCS 7 padding as needed
    :param plaintext: The data to encrypt
    :param key: The AES key to use
    :param iv: The initialization vector
    :return: The resulting ciphertext
    """
    iv = os.urandom(16) if iv is None else iv

    xor_block = iv
    ciphertext = ''

    for pt_block in chunks(plaintext, 16):
        ct_block = aes_ecb_encrypt(string_xor(pt_block, xor_block), key)
        ciphertext += ct_block
        xor_block = ct_block

    return ciphertext, iv
Пример #10
0
def detect_aes_ecb(hexstring_list):
    """Determine if any hexstrings provided have been encrypted with AES 128 ECB
    :param hexstring_list: A list of strings to search for AES 128 ECB encrypted blocks
    :return: A list of indices of strings that are likely encrypted with AES 128 ECB
    """
    indexes_that_are_aes_ecb = []

    for i, hexstring in enumerate(hexstring_list):
        blocks = list(chunks(hexstring, 2 * 16))

        # Count the number of occurrence of each element in the block list
        # if the count list is shorter than the block list that means there
        # are duplicate blocks. EX:
        # ['a','b','c'] -> [1,1,1]
        # ['a','b','a'] -> [2,1]
        block_frequency = {block: blocks.count(block) for block in blocks}
        if len(block_frequency.values()) != len(blocks):
            indexes_that_are_aes_ecb.append(i)

    return indexes_that_are_aes_ecb
Пример #11
0
def test_chunks():
    assert list(cc_util.chunks('aa00bb00cc00', 2, 4)) == ['aa', 'bb', 'cc']