def count_matching_prefix_blocks(a: bytes, b: bytes, block_size: int) -> bytes:
    i = 0
    for a_block, b_block in zip(chunks(a, block_size), chunks(b, block_size)):
        if a_block == b_block:
            i += 1
        else:
            return i
Beispiel #2
0
def detect_block_cipher_mode(cipher: Callable[[bytes], bytes]) -> str:
    pt = bytes(100)  # Chosen to produce repeated blocks in ECB ciphertext
    ct = cipher(pt)
    counter = Counter()
    for block in utils.chunks(ct, AES_BLOCK_SIZE_BYTES):
        counter[block] += 1
    most_common = counter.most_common(1)[0]
    return 'ECB' if most_common[1] > 1 else 'CBC'
def detect_aes_ecb(ciphertexts: Sequence[bytes]):
    candidates = []
    for ciphertext in ciphertexts:
        counter = Counter()
        for block in chunks(ciphertext, AES_BLOCK_SIZE_BYTES):
            counter[block] += 1
        candidates.append((ciphertext, counter.most_common(3)))
    candidates.sort(key=lambda t: t[1][0][1], reverse=True)
    return candidates[0][0]
    def encrypt(self, plaintext: bytes) -> bytes:
        previous = self.iv
        ciphertext_bytes = []
        for block in chunks(plaintext, AES_BLOCK_SIZE_BYTES):
            combined = fixed_xor(block, previous)
            ciphertext_block = self.aes_ecb.encrypt(combined)
            previous = ciphertext_block
            ciphertext_bytes.extend(ciphertext_block)

        return bytes(ciphertext_bytes)
    def decrypt(self, ciphertext: bytes) -> bytes:
        previous = self.iv
        plaintext_bytes = []
        for block in chunks(ciphertext, AES_BLOCK_SIZE_BYTES):
            combined = self.aes_ecb.decrypt(block)
            plaintext_block = fixed_xor(combined, previous)
            previous = block
            plaintext_bytes.extend(plaintext_block)

        return bytes(plaintext_bytes)
def break_repeating_xor(ciphertext: bytes) -> DecryptionCandidate:
    size_distance_pairs = []
    for key_size in range(2, 41):
        first_n_blocks = list(chunks(ciphertext, key_size))[:N]
        pairwise = combinations(first_n_blocks, 2)
        normalized_distances = [
            hamming_distance(a, b) / key_size for a, b in pairwise
        ]
        average_normalized = sum(normalized_distances) / len(
            normalized_distances)
        size_distance_pairs.append((key_size, average_normalized))

    key_size_candidates = sorted(size_distance_pairs, key=lambda p: p[1])

    candidates = []
    for key_size, _ in key_size_candidates[:N]:
        # First element is the concatenation of the first byte in every block.
        # Second is the concatenation of the second byte in each block.
        # etc...
        transposed = [bytearray() for _ in range(key_size)]
        for block in chunks(ciphertext, key_size):
            for i, b in enumerate(block):
                transposed[i].append(b)

        key_bytes = []
        for i, block in enumerate(transposed):
            part, _, _ = break_single_xor(block)
            key_bytes.extend(part)

        key = bytes(key_bytes)
        plaintext = repeating_xor(ciphertext, key)

        candidates.append(
            DecryptionCandidate(
                key=key,
                plaintext=plaintext,
                score=english_language_score(plaintext),
            ))
    return max(candidates, key=lambda c: c.score)
Beispiel #7
0
def decrypt_using_oracle(
    iv: bytes,
    ciphertext: bytes,
    oracle: Callable[[bytes, bytes], bool],
) -> bytes:
    known = bytearray()

    previous = iv
    for ct_block in chunks(ciphertext, AES_BLOCK_SIZE_BYTES):
        # We decrypt the ciphertext blocks from head to tail but the order
        # doesn't matter. You could even do it in parallel.
        pt_block = _decrypt_block(previous, ct_block, oracle)
        known.extend(pt_block)
        previous = ct_block

    plaintext = _padder.unpad(known)
    return bytes(plaintext)