def test_get_data_fragment_with_padding(): udp_payload = get_random_bytes(DATA_FRAG_PAYLOAD_SIZE - 1) f = FragmentGenerator(udp_payload) fragment = f.get_data_fragment() _, frag_byte, _, payload = cut(fragment, FRAG_ID_SIZE, FRAG_FLAG_SIZE, 1) assert b2i(frag_byte) & FragmentGenerator.LAST_FRAG_FLAG assert b2i(frag_byte) & FragmentGenerator.PADDING_FLAG assert b2i(frag_byte) == 0b1100_0000 assert payload[0:DATA_FRAG_PAYLOAD_SIZE - 1] == udp_payload assert len( fragment) == DATA_FRAG_PAYLOAD_SIZE + FRAG_ID_SIZE + FRAG_FLAG_SIZE udp_payload = get_random_bytes(1) f = FragmentGenerator(udp_payload) fragment = f.get_data_fragment() _, frag_byte, _, payload = cut(fragment, FRAG_ID_SIZE, FRAG_FLAG_SIZE, 2) assert b2i(frag_byte) & FragmentGenerator.LAST_FRAG_FLAG assert b2i(frag_byte) & FragmentGenerator.PADDING_FLAG assert b2i(frag_byte) == 0b1100_0000 assert payload[0:1] == udp_payload assert len( fragment) == DATA_FRAG_PAYLOAD_SIZE + FRAG_ID_SIZE + FRAG_FLAG_SIZE
def c11_encrypt_ecb_or_cbc_oracle(plain_text): block_size = 16 key = get_random_bytes(block_size) prefix = get_random_bytes(10) suffix = get_random_bytes(10) msg = pkcs7_pad(prefix + plain_text + suffix, block_size) if random.random() >= 0.5: print("S2C11 - doing CBC") iv = get_random_bytes(16) return aes128_cbc_encode(key, iv, msg) else: print("S2C11 - doing ECB") return aes128_ecb_encode(key, msg)
def c19(): b64_cipher_texts = [ b'SSBoYXZlIG1ldCB0aGVtIGF0IGNsb3NlIG9mIGRheQ==', b'Q29taW5nIHdpdGggdml2aWQgZmFjZXM=', b'RnJvbSBjb3VudGVyIG9yIGRlc2sgYW1vbmcgZ3JleQ==', b'RWlnaHRlZW50aC1jZW50dXJ5IGhvdXNlcy4=', b'SSBoYXZlIHBhc3NlZCB3aXRoIGEgbm9kIG9mIHRoZSBoZWFk', b'T3IgcG9saXRlIG1lYW5pbmdsZXNzIHdvcmRzLA==', b'T3IgaGF2ZSBsaW5nZXJlZCBhd2hpbGUgYW5kIHNhaWQ=', b'UG9saXRlIG1lYW5pbmdsZXNzIHdvcmRzLA==', b'QW5kIHRob3VnaHQgYmVmb3JlIEkgaGFkIGRvbmU=', b'T2YgYSBtb2NraW5nIHRhbGUgb3IgYSBnaWJl', b'VG8gcGxlYXNlIGEgY29tcGFuaW9u', b'QXJvdW5kIHRoZSBmaXJlIGF0IHRoZSBjbHViLA==', b'QmVpbmcgY2VydGFpbiB0aGF0IHRoZXkgYW5kIEk=', b'QnV0IGxpdmVkIHdoZXJlIG1vdGxleSBpcyB3b3JuOg==', b'QWxsIGNoYW5nZWQsIGNoYW5nZWQgdXR0ZXJseTo=', b'QSB0ZXJyaWJsZSBiZWF1dHkgaXMgYm9ybi4=', b'VGhhdCB3b21hbidzIGRheXMgd2VyZSBzcGVudA==', b'SW4gaWdub3JhbnQgZ29vZCB3aWxsLA==', b'SGVyIG5pZ2h0cyBpbiBhcmd1bWVudA==', b'VW50aWwgaGVyIHZvaWNlIGdyZXcgc2hyaWxsLg==', b'V2hhdCB2b2ljZSBtb3JlIHN3ZWV0IHRoYW4gaGVycw==', b'V2hlbiB5b3VuZyBhbmQgYmVhdXRpZnVsLA==', b'U2hlIHJvZGUgdG8gaGFycmllcnM/', b'VGhpcyBtYW4gaGFkIGtlcHQgYSBzY2hvb2w=', b'QW5kIHJvZGUgb3VyIHdpbmdlZCBob3JzZS4=', b'VGhpcyBvdGhlciBoaXMgaGVscGVyIGFuZCBmcmllbmQ=', b'V2FzIGNvbWluZyBpbnRvIGhpcyBmb3JjZTs=', b'SGUgbWlnaHQgaGF2ZSB3b24gZmFtZSBpbiB0aGUgZW5kLA==', b'U28gc2Vuc2l0aXZlIGhpcyBuYXR1cmUgc2VlbWVkLA==', b'U28gZGFyaW5nIGFuZCBzd2VldCBoaXMgdGhvdWdodC4=', b'VGhpcyBvdGhlciBtYW4gSSBoYWQgZHJlYW1lZA==', b'QSBkcnVua2VuLCB2YWluLWdsb3Jpb3VzIGxvdXQu', b'SGUgaGFkIGRvbmUgbW9zdCBiaXR0ZXIgd3Jvbmc=', b'VG8gc29tZSB3aG8gYXJlIG5lYXIgbXkgaGVhcnQs', b'WWV0IEkgbnVtYmVyIGhpbSBpbiB0aGUgc29uZzs=', b'SGUsIHRvbywgaGFzIHJlc2lnbmVkIGhpcyBwYXJ0', b'SW4gdGhlIGNhc3VhbCBjb21lZHk7', b'SGUsIHRvbywgaGFzIGJlZW4gY2hhbmdlZCBpbiBoaXMgdHVybiw=', b'VHJhbnNmb3JtZWQgdXR0ZXJseTo=', b'QSB0ZXJyaWJsZSBiZWF1dHkgaXMgYm9ybi4=', ] block_size = 16 random_key = get_random_bytes(block_size) reused_nonce = 0 def c19_cryptor(plain_text): return aes128_ctr_encode(random_key, reused_nonce, plain_text) cipher_texts = [b64decode(s) for s in b64_cipher_texts] repeat_length = min(map(len, cipher_texts)) chunks = [ct[0:repeat_length] for ct in cipher_texts] chunks = transpose(chunks) keystream = bytes(map(lambda t: t[1], map(c4_best_single_byte_xor, chunks))) for ct in cipher_texts: ctt = ct[0:repeat_length] print(xor_buf(ctt, keystream))
def make_fragment(message_id, fragment_number, last_fragment, payload, payload_limit): if fragment_number > 0b0011_1111: raise ValueError("Too many fragments needed for this payload.") if not payload and message_id != DUMMY_FRAG_ID: raise ValueError("No more fragments left to generate.") frag_byte = fragment_number if last_fragment: frag_byte |= FragmentGenerator.LAST_FRAG_FLAG if len(payload) < payload_limit: frag_byte |= FragmentGenerator.PADDING_FLAG padding_bytes, padding_len = padding_length_to_bytes(payload_limit - len(payload)) else: padding_len = 0 padding_bytes = bytes() fragment = i2b(message_id, FRAG_ID_SIZE) + i2b(frag_byte, FRAG_FLAG_SIZE) fragment += padding_bytes fragment += payload[:payload_limit] + get_random_bytes(padding_len) return fragment
def test_get_data_fragment_2_fragments(): udp_payload = get_random_bytes(400) fragmented_payload = bytes() f = FragmentGenerator(udp_payload) fragment = f.get_data_fragment() msg_id, is_last, fragment_id, payload = parse_fragment(fragment) fragmented_payload += payload assert msg_id == f.message_id assert not is_last assert fragment_id == 0 fragment = f.get_data_fragment() msg_id, is_last, fragment_id, payload = parse_fragment(fragment) fragmented_payload += payload assert msg_id == f.message_id assert is_last assert fragment_id == 1 assert udp_payload == fragmented_payload
def test_gen_init_msg(): priv1 = params.group.gensecret() priv2 = params.group.gensecret() priv3 = params.group.gensecret() pub1 = params.group.expon_base([priv1]) pub2 = params.group.expon_base([priv2]) pub3 = params.group.expon_base([priv3]) passes = 100 for _ in range(passes): chan_keys = [gen_sym_key(), gen_sym_key(), gen_sym_key()] payload = get_random_bytes(100) proc_chan_keys = [] message = gen_init_msg([pub1, pub2, pub3], chan_keys, payload) for priv_key in [priv1, priv2, priv3]: proc_chan_key, proc_payload, message = process(priv_key, message) proc_chan_keys.append(proc_chan_key) assert chan_keys == proc_chan_keys assert payload == proc_payload
def c13(): block_size = 16 secret_key = get_random_bytes(block_size) def encryptor(email_address): return aes128_ecb_encode( secret_key, pkcs7_pad(c13_profile_for(email_address), block_size)) def decryptor(cipher_text): return c13_parse_kv( pkcs7_unpad(aes128_ecb_decode(secret_key, cipher_text), block_size)) # The minimum amount of prefix padding to cause a duplicated block # will give us the target block in the next block for repeat_pad_size in range(2 * block_size - 1, 3 * block_size): repeat_pad = b"A" * repeat_pad_size trick_email_address = repeat_pad + pkcs7_pad( b"admin", block_size) + b"@example.com" cipher_text = encryptor(trick_email_address) chunks = chunk(cipher_text, block_size) # If we have a repeat, the block after repeat is target next_is_target = False target_cipher_block = b'' last_chunk = b'' for c in chunks: if next_is_target: target_cipher_block = c break next_is_target = (c == last_chunk) last_chunk = c if target_cipher_block != b'': break if target_cipher_block == b'': raise RuntimeError("Didn't find target cipher block") # At some padding between 0..block_size the end block should # be 'user<pkcspadding>'. If so, replacing it with our # target cipher block should give us something which will decode # to our desired plaintext for padding_size in range(0, block_size): padded_email_address = (b"A" * padding_size) + b"@example.com" cipher_text = encryptor(padded_email_address) # Splice in target block cipher_text = bytearray(cipher_text) cipher_text[-block_size:] = target_cipher_block cipher_text = bytes(cipher_text) try: profile = decryptor(cipher_text) if profile[b"role"] == b"admin": print("S2C13 - did it! got an admin role") return except (KeyError, ValueError): pass print("S2C13 fail. Bad coder, no biscuit")
def c14(): unknown_key = get_random_bytes(16) # oracle = lambda pt: c14_encryption_oracle(unknown_key, pt) def oracle(pt): return c14_encryption_oracle(unknown_key, pt) block_size = 16 pad_char = b'A' recovered_plain_text = bytearray() chosen_plain_text = bytearray() while True: # We construct a (block_size - 1) piece plain text. Which # ends in the our recovered plain text and is prepended with enough # pad_char to make the size chosen_plain_text[:] = recovered_plain_text if len(chosen_plain_text) > block_size - 1: chosen_plain_text = chosen_plain_text[-(block_size - 1):] added_pad = max(0, (block_size - 1) - len(chosen_plain_text)) chosen_plain_text = bytearray(pad_char * added_pad) + chosen_plain_text assert len( chosen_plain_text ) == block_size - 1, "Using correct size chosen_plain_text block" # By prepending with enough pad_chars and appending with bytes 0->255, # and repeating until we get block_size different # answers, we find 'block_size' candidate cipher blocks for each possible end byte dictionary = c14_dictionary_for_block(oracle, block_size, chosen_plain_text) next_byte = None for num_attempts in range(0, 10 * block_size): pad = pad_char * added_pad cipher_text = oracle(pad) for c in chunk(cipher_text, block_size): try: next_byte = dictionary[c] break except KeyError: pass if next_byte is None: raise RuntimeError("Failed to find next byte in {} iterations", num_attempts) recovered_plain_text.append(next_byte) print("{}".format(recovered_plain_text.decode('ascii'))) print("S2C14 msg is {}", recovered_plain_text)
def test_get_data_fragment_too_many(): udp_payload = get_random_bytes(400) f = FragmentGenerator(udp_payload) try: for _ in range(1000): f.get_data_fragment() assert False except ValueError: assert True
def test_get_init_fragment(): udp_payload = get_random_bytes(120) f = FragmentGenerator(udp_payload) fragment = f.get_init_fragment() msg_id, is_last, fragment_id, payload = parse_fragment(fragment) assert msg_id == f.message_id assert is_last assert fragment_id == 0 assert udp_payload == payload
def test_get_data_fragment(): udp_payload = get_random_bytes(DATA_FRAG_PAYLOAD_SIZE) f = FragmentGenerator(udp_payload) fragment = f.get_data_fragment() _, frag_byte, payload = cut(fragment, FRAG_ID_SIZE, FRAG_FLAG_SIZE) assert b2i(frag_byte) & FragmentGenerator.LAST_FRAG_FLAG assert b2i(frag_byte) == 0b0100_0000 assert payload[0:DATA_FRAG_PAYLOAD_SIZE] == udp_payload assert len( fragment) == DATA_FRAG_PAYLOAD_SIZE + FRAG_ID_SIZE + FRAG_FLAG_SIZE
def recv_response(self, response): self.last_interaction = time() """Turns the response into a MixMessage and saves its fragments for later sending. """ frag_gen = FragmentGenerator(response) while frag_gen: print(self, "Data", "<-", len(frag_gen.udp_payload)) fragment = frag_gen.get_data_fragment() packet = fragment + get_random_bytes(MIX_COUNT * CTR_PREFIX_LEN) ChannelExit.to_mix.append(DATA_MSG_FLAG + i2b(self.in_chan_id, CHAN_ID_SIZE) + packet)
def c17(): block_size = 16 random_key = get_random_bytes(block_size) random_iv = get_random_bytes(block_size) cipher_text = c17_encryptor(block_size, random_key, random_iv) cipher_blocks = chunk(cipher_text, block_size) cipher_blocks.insert(0, random_iv) def c17_decryptor(cipher_text): return c17_decryptor_good_padding(block_size, random_key, random_iv, cipher_text) def break_one_block(i): return c17_break_block(cipher_blocks[i], cipher_blocks[i + 1], c17_decryptor) plain_text = b''.join((map(break_one_block, range(0, len(cipher_blocks) - 1)))) plain_text = pkcs7_unpad(plain_text, block_size) print("S3C17: {}".format(plain_text))
def c16(): block_size = 16 random_key = get_random_bytes(block_size) random_iv = get_random_bytes(block_size) payload = bytearray(b";admin=true;") # Hide the special chars by flipping a bit in them payload[0] ^= 0x01 payload[6] ^= 0x01 payload[11] ^= 0x01 # Assuming we don't know the prefix, we will try at each offset for offset in range(0, block_size): chosen_plain_text = b'A' * offset # Prepend a sacrificial block, in which we can flip bits chosen_plain_text += b'A' * block_size chosen_plain_text += payload cipher_text = bytearray( c16_encryptor(block_size, random_key, random_iv, chosen_plain_text)) # We don't know which block to flip. Let's try 'em all for block_index in range(0, (len(cipher_text) // block_size) - 1): # Flip the corresponding bits in the sacrificial block cipher_text[(block_index * block_size) + offset + 0] ^= 0x01 cipher_text[(block_index * block_size) + offset + 6] ^= 0x01 cipher_text[(block_index * block_size) + offset + 11] ^= 0x01 try: if (c16_decryptor(block_size, random_key, random_iv, bytes(cipher_text))): print("S2C16 got admin") return except Exception: # pkcs 7 fail? pass print("S2C16 fail :-(")
def c12(): unknown_key = get_random_bytes(16) def oracle(pt): return c12_encryption_oracle(unknown_key, pt) # Shim is number of bytes to fill a block (block_size, shim_size) = c12_discover_block_and_shim_sizes(oracle) print("S2C12 - found block size {}".format(block_size)) is_ecb = c12_detect_ecb(oracle, block_size) print("S2C12 - is ECB?: {}".format(is_ecb)) known_bytes = bytearray() for index in range(0, 10 * block_size): # block_index = index // block_size chunk_index = index % block_size # print("block_index {} chunk_index {}".format(block_index, chunk_index)) needed_pad_len = (block_size - 1) - chunk_index needed_pad = bytes(needed_pad_len) trick_block = bytearray(block_size) + known_bytes trick_block = trick_block[-(block_size - 1):] block_dictionary = c12_make_block_dictionary(oracle, block_size, trick_block) cipher_text = oracle(needed_pad) cipher_chunks = chunk(cipher_text, block_size) interesting_chunk = cipher_chunks[index // block_size] # print("C0: {}".format(interesting_chunk)) try: plain_text_byte = block_dictionary[interesting_chunk] except KeyError: break known_bytes.append(plain_text_byte) # print("Got byte: {}".format(plain_text_byte)) # print("Got known bytes: {}".format(known_bytes)) plain_text = pkcs7_unpad(known_bytes, block_size) print("S2C12 - got msg: {}", plain_text.decode('ascii'))
def encrypt(self, plain_text): self.counter.next() msg_type, chan_id, ctr_prefix, payload = cut(plain_text, MSG_TYPE_FLAG_LEN, CHAN_ID_SIZE, CTR_PREFIX_LEN) reserved = get_random_bytes(RESERVED_LEN) # use all 0s as link key, since they can not be exchanged yet cipher = gcm_cipher(self.key, int(self.counter)) # ctr encrypt the header with a random link counter prefix header, mac = cipher.encrypt_and_digest(chan_id + ctr_prefix + msg_type + reserved) return bytes(self.counter) + header + mac + payload
def c14_encryption_oracle(key, chosen_plain_text): block_size = 16 secret_suffix = b64decode( """Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkg aGFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBq dXN0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUg YnkK""") prefix_size = randrange(20, 40) random_prefix = get_random_bytes(prefix_size) msg = random_prefix + chosen_plain_text + secret_suffix # chunk_index = 0 # chunks = chunk(msg, 16) # for c in chunks: # chunk_index+= 1 # print("JB - oracle pt {}/{}: [{}]".format(chunk_index, len(chunks), c)) msg = pkcs7_pad(msg, block_size) return aes128_ecb_encode(key, msg)
def forward_request(self, request): """Takes a mix fragment, already stripped of the channel id.""" self.last_interaction = time() ctr, cipher_text = cut(request, CTR_PREFIX_LEN) ctr = b2i(ctr) self.request_replay_detector.check_replay_window(ctr) cipher = ctr_cipher(self.key, ctr) forward_msg = cipher.decrypt(cipher_text) + get_random_bytes(CTR_MODE_PADDING) print(self, "Data", "->", len(forward_msg) - CTR_PREFIX_LEN) ChannelMid.requests.append(DATA_MSG_FLAG + i2b(self.out_chan_id, CHAN_ID_SIZE) + forward_msg) timed_out = check_for_timed_out_channels(ChannelMid.table_in) for in_id in timed_out: out_id = ChannelMid.table_in[in_id].out_chan_id del ChannelMid.table_in[in_id] del ChannelMid.table_out[out_id]
def send_chan_confirm(self): print(self, "Init", "<-", "len:", DATA_PACKET_SIZE) ChannelExit.to_mix.append(CHAN_CONFIRM_MSG_FLAG + i2b(self.in_chan_id, CHAN_ID_SIZE) + bytes(CTR_PREFIX_LEN) + get_random_bytes(DATA_PACKET_SIZE))
from Counter import Counter from LinkEncryption import LinkEncryptor, LinkDecryptor from ReplayDetection import ReplayDetectedError from constants import DATA_MSG_FLAG, CHAN_ID_SIZE, REPLAY_WINDOW_SIZE, CHANNEL_CTR_START from util import get_random_bytes, i2b, gen_sym_key payload = get_random_bytes(200) msg_type = DATA_MSG_FLAG chan_id = 128 msg_ctr = bytes(Counter(CHANNEL_CTR_START)) link_key = gen_sym_key() def test_link_encryption(): encryptor = LinkEncryptor(link_key) decryptor = LinkDecryptor(link_key) encrypted = encryptor.encrypt(msg_type + i2b(chan_id, CHAN_ID_SIZE) + msg_ctr + payload) chan_id2, msg_ctr2, payload2, msg_type2 = decryptor.decrypt(encrypted) assert int(encryptor.counter) in decryptor.replay_detector assert chan_id == chan_id2 assert msg_ctr == msg_ctr2 assert payload == payload2 assert msg_type == msg_type2 def test_replay_detection_already_seen():
def c24_cryptor(seed): known_plain_text = b'A' * 15 random_prefix = get_random_bytes(randrange(10, 20)) return mt_ctr(seed, random_prefix + known_plain_text)