Example #1
0
File: xor.py Project: liona24/bop
def brute_xor(c, freq=Res.EN_freq_1):
    """Attempts to decrypt the given XOR ciphertext using frequency analysis.

    It is assumed that the given ciphertext was encrypted using XOR with a key of length 1.
    If this is not given you may want to use `brute_xor_multi`.

    Example:
    ```python
    >>> plain = b'Hello my name is Bop. We just have to make sure that this text is somewhat lengthy. You know. Stuff. Foo.'
    >>> key = 27
    >>> cipher = bytes(map(lambda x: x ^ key, plain))
    >>> score, guessed_key = brute_xor(cipher)[0]
    >>> guessed_key
    27

    ```

    Arguments:
        c {bytes} -- The ciphertext

    Returns:
        list -- List of (score, key) pairs. Best score first.
    """
    results = []
    frequency_distribution = load(freq)
    for key in range(0, 255):
        p = xor(c, key)

        f = analyze_frequency(p)
        d = to_distribution(f)

        results.append((measure_similarity(frequency_distribution, d), key))

    results.sort()
    return results
Example #2
0
def inject_malformed(ciphertext, offset, is_, should):
    """Inject a custom payload after encrypting a known plaintext with AES CTR

    This is usefull if one wants to bypass some input validation.

    Example:
    ```python
    >>> from bop.crypto_constructor import aes_ctr
    >>> import secrets
    >>> c = aes_ctr()
    >>> plaintext = secrets.token_bytes(7) + b'HEY' + secrets.token_bytes(3)
    >>> ciphertext = c.encrypt(plaintext)
    >>> new_ciphertext = inject_malformed(ciphertext, 7, b'HEY', b'BYE')
    >>> c.decrypt(new_ciphertext)[7:10]
    b'BYE'

    ```

    Arguments:
        ciphertext {byteslike} -- The ciphertext to inject the payload into
        offset {int} -- The offset into the ciphertext at which the `is_` block starts.
        is_ {byteslike} -- The known plaintext, which is to be altered.
        should {byteslike} -- The desired plaintext

    Raises:
        ValueError: If the lengths of `is_` and `should` do not match

    Returns:
        byteslike -- The new ciphertext
    """
    if len(is_) != len(should):
        raise ValueError(
            f"Length of `is_` should be equal to length of `should`: {len(is_)} != {len(should)}"
        )

    target_area = ciphertext[offset:offset + len(is_)]
    return ciphertext[:offset] + xor(xor(target_area, is_),
                                     should) + ciphertext[offset + len(is_):]
Example #3
0
def hmac(key, msg, alg=sha1):
    """Compute the HMAC (keyed-hash message authentication code) for the given message using the given key

    Arguments:
        key {bytes} -- The key to sign the message with
        msg {bytes} -- The message to sign

    Keyword Arguments:
        alg {callable} -- The hash algorithm to use. (default: {sha1})

    Returns:
        bytes -- The HMAC computed
    """
    if len(key) > 64:
        key = alg(key)

    if len(key) < 64:
        key = key + b'\x00' * (64 - len(key))

    outer = xor(key, 0x5c)
    inner = xor(key, 0x36)

    return alg(outer + alg(inner + msg))
Example #4
0
def decrypt(oracle, msg, iv=None, blocksize=16):
    r"""Performs a CBC - blockcipher attack when given a padding oracle.

    A padding oracle reports whether a message given for decryption has valid
    padding after decrpyting it.

    This attack is able to decrypt the given message without knowing the secret key.

    Example:
    ```python
    >>> import secrets
    >>> from bop.oracles.padding import PaddingCBCOracle as Oracle
    >>> iv = secrets.token_bytes(16)
    >>> plain = b'Hello Bop.'
    >>> o = Oracle(plaintext=plain, iv=iv)
    >>> decrypt(o, o.msg, iv=iv)
    b'Hello Bop.\x06\x06\x06\x06\x06\x06'

    ```

    Arguments:
        oracle {oracles.PaddingOracle} -- The padding oracle
        msg {bytes} -- The encrypted message to decrypt

    Keyword Arguments:
        iv {bytes} -- The initialization vector used for decryption (default: {None})
        blocksize {int} -- The CBC block size in bytes (default: {16})

    Returns:
        bytes -- The decrypted message
    """
    assert (len(msg) % blocksize == 0)

    blocks = list(chunks(msg, blocksize))

    if iv is not None:
        blocks = [iv] + blocks
    # if we do not know the iv we cannot decrypt the first block

    it0, it1 = iter(blocks), iter(blocks)
    next(it1)

    plaintext = []

    # iterate over consecutive blocks
    for (iv, block) in zip(it0, it1):
        # this is our 'artificial iv', we only ever need 2 consecutive blocks
        # so we treat the first one as iv
        iv = bytes(iv)
        # the block we are actually decrypting
        block = bytes(block)

        decrypted = [0] * blocksize

        backtrace = []
        i = blocksize - 1
        b0 = 0

        # for each byte in the block, in reverse order
        while i >= 0:

            # we now alter each byte in the block, one byte at a time
            # by flipping bits of our iv.
            # the padding oracle will tell us if the tampered block was correct
            # Consider the last byte: A valid padding would be \x01
            # We now try all possible values for the last byte. Eventually
            # the oracle will report that we successfully changed the value
            # of the last byte to \x01

            # the value the result should have, i.e. the layout of the padding
            value_should = blocksize - i
            mask = [0] * blocksize
            mask[i + 1:] = xor(decrypted[i + 1:], value_should)

            # try out all possible values
            for b in range(b0, 256):
                mask[i] = b
                tampered_iv = xor(mask, iv)

                if oracle(tampered_iv + block):
                    decrypted[i] = b ^ value_should
                    backtrace.append((i, b + 1))
                    break
            else:
                # if the padding is valid naturally we may encounter collisions, i.e. hitting the "correct" byte twice.
                # if we ignore the second hit we will eventually fail if the first hit was the wrong one
                # therefor we trace such behaviour and perform backtracking to the last successfull i
                i, b0 = backtrace.pop()
                decrypted[:i] = [0] * i
                continue

            i -= 1
            b0 = 0

        plaintext.extend(decrypted)

    return bytes(plaintext)
Example #5
0
def inject_malformed(ciphertext, offset, is_, should, iv=None, blocksize=16):
    """Inject a custom payload after encrypting a known plaintext using AES CBC.

    This is usefull if one wants to bypass some input validation. Note that the maximum payload size is `blocksize`.
    In order to achieve this payload size the controlled block has to be aligned though, i.e. offset % blocksize = 0.

    Also note that block preceeding the block carrying the payload is destroyed when applying this technique.

    Example:
    ```python
    >>> from bop.crypto_constructor import aes_cbc
    >>> import secrets
    >>> c = aes_cbc()
    >>> plaintext = secrets.token_bytes(18) + b'HEY' + secrets.token_bytes(11)
    >>> ciphertext = c.encrypt(plaintext)
    >>> _, new_ciphertext = inject_malformed(ciphertext, 18, b'HEY', b'BYE')
    >>> c.decrypt(new_ciphertext)[18:21]
    b'BYE'

    ```

    Arguments:
        ciphertext {byteslike} -- The ciphertext to inject the payload into
        offset {int} -- The offset into the ciphertext at which the `is_` block starts.
        is_ {byteslike} -- The known plaintext, which is to be altered.
        should {byteslike} -- The desired plaintext

    Keyword Arguments:
        iv {byteslike} -- Provide the IV if available. This will allow injecting into the first block. Not required otherwise (default: {None})
        blocksize {int} -- The blocksize in bytes used for the AES algorithm (default: {16})

    Raises:
        ValueError: If the lengths of `is_` and `should` do not match
        ValueError: If the payload cannot be injected at the offset provided
        ValueError: If the payload is too long

    Returns:
        (bytes, bytes) -- A tuple containing the new IV and the new ciphertext respectively. IV will be None if not provided.
    """
    n = len(should)

    if len(is_) != n:
        raise ValueError(
            f"Length of `is_` should be equal to length of `should`: {len(is_)} != {n}"
        )
    if offset < blocksize and iv is None:
        raise ValueError(
            f"Cannot alter block located before offset {offset}! At least one full-sized block is required in front of the payload!"
        )

    if iv is not None:
        ciphertext = iv + ciphertext
        offset += blocksize

    possible_payload_size = blocksize - (offset % blocksize)
    if n > possible_payload_size:
        # Maximum value if blocks are aligned is equal to blocksize
        raise ValueError(
            f"Cannot inject payload: Too long ({n} > {possible_payload_size})")

    new_ciphertext = bytearray(ciphertext)

    i = offset - blocksize
    target_area = ciphertext[i:i + n]
    new_ciphertext[i:i + n] = xor(xor(target_area, is_), should)

    new_ciphertext = bytes(new_ciphertext)

    if iv is not None:
        iv = new_ciphertext[:blocksize]
        new_ciphertext = new_ciphertext[blocksize:]

    return iv, new_ciphertext