def cbc_encrypt(msg, key, iv): # check that msg == hex, key == hex, iv == hex # I should probably handle these better but whatever assert int(msg, 16) assert int(key, 16) assert int(iv, 16) padded_msg = pkcs7_pad(msg, block_size) msg_blocks = [ padded_msg[i:(i + block_size * 2)] for i in range(0, len(padded_msg), block_size * 2) ] iv_first_block = xor_hex_strings(iv, msg_blocks[0]) (block_0, encryptor) = aes_ecb_encrypt_with_key(bytes.fromhex(iv_first_block), bytes.fromhex(key)) block_i = block_0 ctext = [block_i.hex()] for msg_block in msg_blocks: iv_i = xor_hex_strings(block_i.hex(), msg_block) (block_i, encryptor) = aes_ecb_encrypt_with_key(bytes.fromhex(iv_i), bytes.fromhex(key)) ctext.append(block_i.hex()) return ''.join(ctext)
def hamming_distance(str1, str2): assert (len(str1) == len(str2)) hex_str1 = str1.hex() hex_str2 = str2.hex() # can find hamming distance by just xoring and counting number of 1s in xord result xor_strings = xor_hex_strings(hex_str1, hex_str2) num_ones = sum([1 for c in bin(int(xor_strings, 16))[2:] if c == '1']) return num_ones
def hmac_sha1(key, msg): assert type(msg) == bytes assert type(key) == bytes if len(key) > 64: key = sha1(key, len(key) * 8) if len(key) < 64: key += b'\x00' * (64 - len(key)) o_key_pad = bytes.fromhex(xor_hex_strings(key.hex(), (b'\x5c' * 64).hex())) i_key_pad = bytes.fromhex(xor_hex_strings(key.hex(), (b'\x36' * 64).hex())) i_pad_hash = bytes.fromhex( sha1(i_key_pad + msg, len(i_key_pad + msg) * 8)[2:]) return sha1(o_key_pad + i_pad_hash, len(o_key_pad + i_pad_hash) * 8)[2:]
def encrypt(key, msg): init_seed(key) keystream = '' while len(keystream) < len(msg): keystream += format(extract_number(), '08x') keystream = keystream[:len(msg)] return xor_hex_strings(keystream, msg)
def xor_single_byte(hex_str): best_score = -1000 best_string = '' key_byte = 0 for i in range(0, 256): decrypted = xor_hex_strings(hex_str, (int(len(hex_str) / 2) * bytes([i])).hex()) if len(decrypted) % 2: decrypted = '0' + decrypted temp_score = score_string(binascii.unhexlify(decrypted)) if temp_score > best_score: best_score = temp_score best_string = codecs.decode(decrypted, 'hex') key_byte = i return (best_string, best_score, key_byte)
def aes_ctr_operation(key, data, nonce): ctr_output = '' ctr = 0 block = data[ctr*32:(ctr+1)*32] while block: ctr_data = pack('<QQ', int(nonce, 16), ctr) # see struct library for packing formatting aes_out = aes_ecb_encrypt_with_key(ctr_data, bytes.fromhex(key)) temp_output = xor_hex_strings(aes_out[0].hex()[:len(block)], block) if len(temp_output) % 2: temp_output = '0' + temp_output ctr_output += temp_output ctr += 1 block = data[ctr*32:(ctr+1)*32] return ctr_output
def cbc_decrypt(ciphertext, key, iv): ctext_blocks = [ ciphertext[i:(i + block_size * 2)] for i in range(0, len(ciphertext), block_size * 2) ] CipherObj = Cipher(algorithms.AES(bytes.fromhex(key)), modes.ECB(), backend=default_backend()) iv_i = iv decrypted_text = [] for ctext in ctext_blocks: d_i = aes_ecb_decrypt(CipherObj, bytes.fromhex(ctext)).hex() decrypted_text.append(xor_hex_strings(iv_i, d_i)) iv_i = ctext return ''.join(list(dict.fromkeys(decrypted_text)))
def main(): with open('20.txt', 'r') as f: plaintext = f.readlines() encrypt_lines(plaintext) #print(ciphertexts) block_len = len(min(ciphertexts, key=len)) blocks = [ctext[:block_len] for ctext in ciphertexts] potential_keys = [] transposed_blocks = transpose_blocks(bytes.fromhex(''.join(blocks)), int(block_len / 2)) #print(f"\n------\nUsing KEYSIZE = {key_sz}\n------") probable_key = '' for block in transposed_blocks: block_hex = bytes([ord(c) for c in block]).hex() #print(block, block_hex) #print( xor_single_byte(block.encode()) ) (decrypted_str, score, key_byte) = xor_single_byte(block_hex) if score != 0: probable_key += chr(key_byte) if block_len / 2 == len(probable_key): potential_keys.append(probable_key) print(f"\nProbable key is \"{potential_keys}\"\n\n") # TODO: currently only decrypts the first <shortest-length-ciphertext> chars of every line since that's what challenge says # the rest of the string could be decrypted by simply finding the next shortest string and # continuing based off that length, but the concept here should already be clear enough. probable_key = bytes([ord(c) for c in probable_key]).hex() dec_message = [ bytes.fromhex(xor_hex_strings(probable_key, block)).decode() for block in blocks ] for i in range(len(dec_message)): assert dec_message[i].encode().hex() == base64.b64decode( plaintext[i])[:53].hex()