def generate_admin_profile(): # Calculate length of prefix and force encryption to a new block bytes_to_new_block = 'A' * (16 - (len(prefix) % 16)) # Identify ciphertext used as IV for the new block index_of_iv_to_use = ((len(prefix) + len(bytes_to_new_block)) // 16) - 1 # Add a known block known_block = bytes_to_new_block + ('X' * 16) # Encrypt chosen plaintext ciphertext = encrypt_parameters(known_block) # Slice ciphertext ciphertext_blocks = slice_blocks(ciphertext, 16) # Isolate IV block iv_to_use = ciphertext_blocks[index_of_iv_to_use] # XOR in IV with target value hex_iv = iv_to_use hex_known = b'X' * 16 hex_target = b";admin=true;\x04\x04\x04\x04" new_hex_iv = xor_strings(hex_iv, xor_strings(hex_target, hex_known)) # Inject block into ciphertext ciphertext_blocks[index_of_iv_to_use] = new_hex_iv # Re-generate ciphertext from block new_ciphertext = b'' for i in ciphertext_blocks: new_ciphertext += i return new_ciphertext
def main(): # Calculate shortest ciphertext lenght min_encrypted_target_length = min(len(i) for i in encrypted_target) print("Shortest ciphertext:", min_encrypted_target_length, "bytes") # Truncate all ciphertext to minimum length encrypted_target_truncated = [ i[:min_encrypted_target_length] for i in encrypted_target] # Check all ciphertext have been truncated to minimum length for i in encrypted_target_truncated: if len(i) != min_encrypted_target_length: raise Exception("*** Ciphertexts haven\'t been truncated ***") # Reusing code from Set 01 Challenge 03 # Transpose the blocks, make a block that is the first byte of every block, and a block that is the second byte etc. transposed_target = list( itertools.zip_longest(*encrypted_target_truncated, fillvalue=0)) # Initialize key variable keystream = b'' # Break each single block via single byte xor (Challenge 03) for i in transposed_target: single_byte = break_singlebyte_xor(i) keystream_byte = single_byte['key'].to_bytes(1, byteorder='big') keystream += keystream_byte print("Recovered key:", keystream) recovered_plaintext = [xor_strings(keystream, i) for i in encrypted_target_truncated] # First byte of keystream doesn't produce correct plaintext due to statistical limitation # The correct first byte is the one that when XOR'd with the first byte of the first cyphertext produces 'I' new_byte = b'' for i in range(256): if chr(i ^ encrypted_target_truncated[0][0]) == 'I': new_byte = i new_first_byte = bytes(chr(new_byte), 'utf-8') keystream = new_first_byte + keystream[1:] recovered_plaintext = [xor_strings(keystream, i) for i in encrypted_target_truncated] # Check decryption is correct for i in range(len(recovered_plaintext)): if recovered_plaintext[i] != target_list[i][:min_encrypted_target_length]: raise Exception("*** FAILED DECRYPTION ***") print("Success:", recovered_plaintext[i])
def decrypt_target(guessed_key, encrypted_target): key_length = len(guessed_key) # Account for the length value of both the ciphertext and the key # Short the key to the length of the ciphertext and viceversa # The decryption process is a simple XOR decrypted_target = [ xor_strings(guessed_key[:len(i)], i[:key_length]) for i in encrypted_target ] return decrypted_target
def repeating_key_xor(string1, string2): # Make string1s and string2 the same length if len(string1) > len(string2): string2 *= int(len(string1) / len(string2) + 1) string2 = string2[:len(string1)] if len(string1) < len(string2): string1 *= int(len(string2) / len(string1) + 1) string1 = string1[:len(string2)] return xor_strings(string1, string2)
def encrypt(self, plaintext): keystream = b'' while len(keystream) < len(plaintext): # Pack each RNG output to a 32-bit Big Endian bytes string keystream += struct.pack('>L', self.rng_out) if len(keystream) > len(plaintext): keystream = keystream[:len(plaintext)] # XOR the keystream with the plaintext return xor_strings(keystream, plaintext)
def main(): three_block_message = '0' * 48 # At least 3 blocks long ciphertext = encrypt_parameters(three_block_message) chosen_ciphertext = ciphertext[:16] + bytes([0]) * 16 + ciphertext[:16] output = decrypt_and_check_ascii(chosen_ciphertext) recovered_key = xor_strings(output[:16], output[32:]) if recovered_key == key: print("\nExpected key:", key) print("Recovered key:", recovered_key) print("--- Success ---") else: raise Exception("*** FAILED ***")
def cbc_decrypt(self, ciphertext): plaintext = b"" ciphertext = slice_target(ciphertext, self.keysize) previous_block = self.iv plainblocks = [b"" for i in ciphertext] for i in range(len(ciphertext)): plainblocks[i] = xor_strings(self.cipher.decrypt(ciphertext[i]), previous_block) previous_block = ciphertext[i] for i in plainblocks: plaintext += i return plaintext
def cbc_encrypt(self, plaintext): ciphertext = b"" plaintext = slice_target(plaintext, self.keysize) previous_block = self.iv if len(plaintext[-1]) != self.keysize: plaintext[-1] = pkcs7_padding(plaintext[-1], self.keysize) cipherblocks = [b"" for i in plaintext] for i in range(len(plaintext)): cipherblocks[i] = self.cipher.encrypt( xor_strings(plaintext[i], previous_block)) previous_block = cipherblocks[i] ciphertext = b"" for i in cipherblocks: ciphertext += i return ciphertext
def ctr(self, target): # Slice plaintext into blocks target_blocks = slice_target(target, self.keysize) # Initialize plaintext variable plaintext = b'' # Slice target into blocks blocks = slice_target(target, 16) # Iterate on each block for i in range(len(blocks)): # Expand counter to 64 bit little endian block_counter = struct.pack('<Q', i) # Generate block stream # format=64 bit unsigned little endian nonce, 64 bit little endian block count (byte count / 16) block_stream = self.nonce + block_counter # Generate keystream ciphertext = self.cipher.encrypt(block_stream) # Generate plaintext plaintext += xor_strings(blocks[i], ciphertext[:len(blocks[i])]) return plaintext