예제 #1
0
def test_parity():
    key = key_1024

    print("\nTest: parity")
    plaintext1 = bytes(b"Some plaintext ") + random_bytes(10) + bytes(
        b" anything can it be")
    plaintext2 = bytes(b"Some plaintext ") + random_bytes(10) + bytes(
        b" anything can it be2")
    ciphertext1 = h2b(
        subprocess.check_output([
            "python", rsa_oracles_path, "encrypt", key.identifier,
            b2h(plaintext1)
        ]).strip().decode())
    ciphertext2 = h2b(
        subprocess.check_output([
            "python", rsa_oracles_path, "encrypt", key.identifier,
            b2h(plaintext2)
        ]).strip().decode())

    key.texts.append({'cipher': b2i(ciphertext1)})
    key.texts.append({'cipher': b2i(ciphertext2)})
    msgs_recovered = parity(parity_oracle, key.publickey())
    assert msgs_recovered[0] == b2i(plaintext1)
    assert msgs_recovered[1] == b2i(plaintext2)
    key.clear_texts()
예제 #2
0
def test_bit_flipping():
    print("Test: cbc.bit_flipping(ciphertext=ciphertext[-2*AES.block_size:],"
          "plaintext=add_padding(plaintext)[-AES.block_size:],\nwanted_last_block=wanted, block_size=AES.block_size)")

    plaintext = bytes(b"money=10000&userdata=whateverdata%20huehuehue%20spam%20and%20eggs")
    wanted = add_padding(bytes(b'&admin=true'))
    ciphertext = h2b(subprocess.check_output(['python', cbc_oracles_path, 'encrypt', b2h(plaintext)]).strip().decode())

    fake_cipher = cbc.bit_flipping(ciphertext=ciphertext[-2*AES.block_size:], plaintext=add_padding(plaintext)[-AES.block_size:],
                                   wanted=wanted, block_size=AES.block_size)
    fake_cipher = ciphertext[-AES.block_size*2:] + fake_cipher
    decrypted = h2b(subprocess.check_output(['python', cbc_oracles_path, 'decrypt', b2h(fake_cipher)]).strip().decode())
    assert decrypted[-len("&admin=true"):] == bytes(b"&admin=true")
예제 #3
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
예제 #4
0
def test_fake_ciphertext_decryption_oracle(amount=5):
    for _ in range(amount):
        new_plaintext = random_bytes(randint(1, 10))
        new_plaintext_padded = add_padding(new_plaintext, block_size)

        print("Test small: cbc.fake_ciphertext(new_plaintext_padded, decryption_oracle=decryption_oracle)")
        new_ciphertext = cbc.fake_ciphertext(new_plaintext_padded, decryption_oracle=decryption_oracle)
        decrypted = h2b(subprocess.check_output(
            ['python', cbc_oracles_path, 'decrypt', b2h(new_ciphertext)]).strip().decode())
        assert decrypted == new_plaintext

    for _ in range(amount):
        new_plaintext = random_bytes(randint(10, 50))
        new_plaintext_padded = add_padding(new_plaintext, block_size)

        print("Test large: cbc.fake_ciphertext(new_plaintext_padded, decryption_oracle=decryption_oracle)")
        new_ciphertext = cbc.fake_ciphertext(new_plaintext_padded, decryption_oracle=decryption_oracle, padding_oracle=padding_oracle)
        decrypted = h2b(subprocess.check_output(
            ['python', cbc_oracles_path, 'decrypt', b2h(new_ciphertext)]).strip().decode())
        assert decrypted == new_plaintext
예제 #5
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
예제 #6
0
def merkle_damgard(data, initial_state, compression_function):
    """Merkle-Damgard construction

    Args:
        data(string)
        initial_state(list of ints)
        compression_function(function)

    Returns:
        final state(string)
    """
    state = initial_state[:]
    data_chunks = chunks(data, 64)
    log.debug("Start merkle-damgard, chunks are: {}".format(data_chunks))
    for chunk in data_chunks:
        log.debug("Process chunk: {} with state: {}".format(b2h(chunk), state))
        state = compression_function(chunk, state)
    return state
예제 #7
0
def test_decrypt():
    global constant, prefix_len, suffix_len, secret
    print(
        "test: ecb.decrypt(encryption_oracle_aes, constant, block_size=AES.block_size)"
    )
    constant = True
    for x in range(20):
        prefix_len = random.randint(0, 90)
        secret = random_bytes(random.randint(1, 90))
        print("Secret to guess(hex): {}".format(b2h(secret)))
        guessed_secret = ecb.decrypt(encryption_oracle_aes,
                                     constant,
                                     block_size=AES.block_size)
        assert secret == guessed_secret
        guessed_secret = ecb.decrypt(encryption_oracle_des,
                                     constant,
                                     block_size=DES3.block_size)
        assert secret == guessed_secret
예제 #8
0
def decrypt(encryption_oracle, constant=True, block_size=16, prefix_size=None, secret_size=None,
            alphabet=None):
    """Given encryption oracle which produce ecb(prefix || our_input || secret), find secret
    
    Args:
        encryption_oracle(callable)
        constant(bool): True if prefix have constant length (secret must have constant length)
        block_size(int/None)
        prefix_size(int/None)
        secret_size(int/None)
        alphabet(string): plaintext space
    
    Returns:
        secret(string)
    """
    log.debug("Start decrypt function")
    if not alphabet:
        alphabet = bytes(string.printable.encode())

    if not block_size:
        block_size = find_block_size(encryption_oracle, constant)

    if constant:
        log.debug("constant == True")
        if not prefix_size or not secret_size:
            prefix_size, secret_size = find_prefix_suffix_size(encryption_oracle, block_size)

        """Start decrypt"""
        secret = bytes(b'')
        aligned_bytes = random_bytes(1) * (block_size - (prefix_size % block_size))
        if len(aligned_bytes) == block_size:
            aligned_bytes = bytes(b'')

        aligned_bytes_suffix = random_bytes(1) * (block_size - (secret_size % block_size))
        if len(aligned_bytes_suffix) == block_size:
            aligned_bytes_suffix = bytes(b'')

        block_to_find_position = -1
        controlled_block_position = (prefix_size+len(aligned_bytes)) // block_size

        while len(secret) < secret_size:
            if (len(secret)+1) % block_size == 0:
                block_to_find_position -= 1
            payload = aligned_bytes + aligned_bytes_suffix + random_bytes(1) + secret
            enc_chunks = chunks(encryption_oracle(payload), block_size)
            block_to_find = enc_chunks[block_to_find_position]

            log.debug("To guess at position {}:".format(block_to_find_position))
            log.debug("Plain: " + print_chunks(chunks(bytes(b'P'*prefix_size) + payload + bytes(b'S'*secret_size), block_size)))
            log.debug("Encry: " + print_chunks(enc_chunks)+"\n")

            for guessed_char in range(256):
                guessed_char = bytes([guessed_char])
                payload = aligned_bytes + add_padding(guessed_char + secret, block_size)
                enc_chunks = chunks(encryption_oracle(payload), block_size)

                log.debug("Plain: " + print_chunks(chunks(bytes(b'P'*prefix_size) + payload + bytes(b'S'*secret_size), block_size)))
                log.debug("Encry: " + print_chunks(enc_chunks)+"\n")
                if block_to_find == enc_chunks[controlled_block_position]:
                    secret = guessed_char + secret
                    log.debug("Found char, secret={}".format(repr(secret)))
                    break
            else:
                log.critical_error("Char not found, try change alphabet. Secret so far: {}".format(repr(secret)))
        log.success("Secret(hex): {}".format(b2h(secret)))
        return secret
    else:
        log.debug("constant == False")
예제 #9
0
    if plain[:2] == bytes(b'\x00\x01') and plain_hash == asn1 + hash_msg:
        return True
    return False


if __name__ == '__main__':
    if len(sys.argv) < 4 or sys.argv[1] not in ['encrypt', 'decrypt', 'sign', 'verify', 'parity',
                                                'verify_bleichenbacher_middle', 'verify_bleichenbacher_suffix']:
        print("Usage: {} encrypt|decrypt|sign|verify|parity|verify_bleichenbacher_suffix|verify_bleichenbacher_middle " \
              "key hexdata [more hexdata]".format(sys.argv[0]))
        sys.exit(1)

    key = RSAKey.import_key(sys.argv[2])

    if sys.argv[1] == 'encrypt':
        print(b2h(encrypt(h2b(sys.argv[3]), key)))

    elif sys.argv[1] == 'decrypt':
        print(b2h(decrypt(h2b(sys.argv[3]), key)))

    elif sys.argv[1] == 'sign':
        print(b2h(sign(h2b(sys.argv[3]), key)))

    elif sys.argv[1] == 'verify':
        print(verify(h2b(sys.argv[3]), h2b(sys.argv[4]), key))

    elif sys.argv[1] == 'parity':
        print(parity_oracle(h2b(sys.argv[3])))

    elif sys.argv[1] == 'verify_bleichenbacher_suffix':
        message = h2b(sys.argv[3])
예제 #10
0
    payload = iv + payload
    try:
        decrypt(payload, iv_as_key)
    except BadPadding as e:
        return False
    return True


blocks_with_correct_padding = encrypt(bytes(b'A' *
                                            (block_size + 5)))[block_size:]


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)


if __name__ == '__main__':
    if len(sys.argv) != 3 or sys.argv[1] not in ['encrypt', 'decrypt']:
        print("Usage: {} encrypt|decrypt data".format(sys.argv[0]))
        sys.exit(1)
    if sys.argv[1] == 'encrypt':
        print(b2h(encrypt(h2b(sys.argv[2]))))
    else:
        print(b2h(decrypt(h2b(sys.argv[2]))))
예제 #11
0
def test_bleichenbacher_signature_forgery():
    key = key_1024_small_e
    print(
        "\nTest bleichenbacher_signature_forgery(key, garbage='suffix', hash_function='sha1')"
    )
    for _ in range(10):
        message1 = bytes(b"Some plaintext ") + random_bytes(10) + bytes(
            b" anything can it be")
        message2 = bytes(b"Some plaintext ") + random_bytes(10) + bytes(
            b" anything can it be")

        key.add_plaintext(b2i(message1))
        key.add_plaintext(b2i(message2))

        forged_signatures = bleichenbacher_signature_forgery(
            key, garbage='suffix', hash_function='sha1')
        assert len(forged_signatures) == 2

        verify_signature1 = subprocess.check_output([
            "python", rsa_oracles_path, "verify_bleichenbacher_suffix",
            key.identifier,
            b2h(message1),
            i2h(forged_signatures[0]), 'sha1'
        ]).strip().decode()
        assert verify_signature1 == 'True'
        verify_signature2 = subprocess.check_output([
            "python", rsa_oracles_path, "verify_bleichenbacher_suffix",
            key.identifier,
            b2h(message2),
            i2h(forged_signatures[1]), 'sha1'
        ]).strip().decode()
        assert verify_signature2 == 'True'
        key.clear_texts()

    print(
        "\nTest bleichenbacher_signature_forgery(key, garbage='middle', hash_function='sha1')"
    )
    for _ in range(10):
        message1 = bytes(b"Some plaintext ") + random_bytes(10) + bytes(
            b" anything can it be")
        message2 = bytes(b"Some plaintext ") + random_bytes(10) + bytes(
            b" anything can it be")

        key.add_plaintext(b2i(message1))
        key.add_plaintext(b2i(message2))

        forged_signatures = bleichenbacher_signature_forgery(
            key, garbage='middle', hash_function='sha1')
        print(forged_signatures)

        # first plaintext signed
        if 0 in forged_signatures:
            verify_signature1 = subprocess.check_output([
                "python", rsa_oracles_path, "verify_bleichenbacher_middle",
                key.identifier,
                b2h(message1),
                i2h(forged_signatures[0]), 'sha1'
            ]).strip().decode()
            assert verify_signature1 == 'True'

        # second plaintext signed
        if 1 in forged_signatures:
            verify_signature2 = subprocess.check_output([
                "python", rsa_oracles_path, "verify_bleichenbacher_middle",
                key.identifier,
                b2h(message2),
                i2h(forged_signatures[1]), 'sha1'
            ]).strip().decode()
            assert verify_signature2 == 'True'
        key.clear_texts()
예제 #12
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