def hex2base64_custom(hex_string): """ Encode a hex string into base64 without using the base64 library https://tools.ietf.org/html/rfc3548.html#section-3 """ alphabet = list( 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=') # Split hex string into bytes, then group into threes bytes = [int(byte, 16) for byte in group(hex_string, 2)] triplets = group(bytes, 3) # Pack 24 bits together and convert into a bit string packed = [ format(int.from_bytes(triplet, byteorder='big'), str(len(triplet) * 8) + 'b') for triplet in triplets ] # Split the bit string into groups of 6 bits input_group = [group(bits, 6) for bits in packed] # Add padding bits if the group is less than 24 bits long padding = 4 - len(input_group[-1]) if padding: input_group[-1].extend([format(64, 'b')] * padding) # Flatten the list and convert to integer indexes input_group = list(itertools.chain.from_iterable(input_group)) indexes = [int(index, 2) for index in input_group] return ''.join(alphabet[index] for index in indexes)
def test_group_invalid(): """Testing non positive sizes raise ValueError""" with pytest.raises(ValueError): list(group("example", 0)) with pytest.raises(ValueError): list(group("example", -1))
def calculate_prefix_length(encrypt, block_size): # This will fail if the prefix has two repeating blocks sequentially for i in range(0, block_size + 1): plaintext = b"A" * i + ((2 * block_size) * b"A") ciphertext = group(encrypt(plaintext), block_size) for j in range(0, len(ciphertext) - 1): if ciphertext[j] == ciphertext[j + 1]: print("j is " + str(j)) print("i is " + str(i)) if j == 0: return 0 else: return (j * block_size) - i
STATIC_KEY = os.urandom(AES.block_size) STATIC_IV = os.urandom(AES.block_size) def encrypt(plaintext: str): """Enterprise Grade Super secure encryption function""" plaintext = plaintext.replace(';', '%3B').replace('=', '%3D') plaintext = b"comment1=cooking%20MCs;userdata=" + bytes( plaintext, 'ascii') + b";comment2=%20like%20a%20pound%20of%20bacon" print(plaintext) return cbc_encrypt(pad(plaintext), STATIC_KEY, STATIC_IV) def decrypt(ciphertext, iv): """Decrypt and check for admin-true""" plaintext = cbc_decrypt(ciphertext, STATIC_KEY, iv) print(plaintext) return bytes(";admin=true;", 'ascii') in plaintext # We need two blocks, the first to modify such that it flips the bits in the second block ciphertext = encrypt("XXXXXXXXXXXXXXXX:admin-true") blocks = group(ciphertext, AES.block_size) blocks[2][0] ^= (ord(':') ^ ord(';')) blocks[2][6] ^= (ord('-') ^ ord('=')) blocks = b''.join(blocks) assert decrypt(blocks, STATIC_IV) is True
return sum(c1 != c2 for c1, c2 in zip(a, b)) assert hamming_distance(str_to_bits("this is a test"), str_to_bits("wokka wokka!!!")) == 37 ciphertext = b64decode(ciphertext) decryptions = [] # Determine the keysize by brute forcing key sizes and checking the edit distance between byte groups # If we have chosen the correct keysize then we compute dist(a XOR b) which will roughly 2-3 bits different in each byte # However if we have chosen incorrectly then we compute dist(x XOR y) where x and y are effecitvely 'random' bytestrings # which we expect to share approximately 4 bits due to probabilily. Because the correct key will show a lower edit distance # we can keep track of the lowest edit distance through comparisons and then use that as an indicator of the correct key size for keysize in range(2, 40): blocks = group(ciphertext, keysize) # 10 blocks is enough of an average to make the correct keysize appear on top NUM_BLOCKS = 10 edit_distance = sum( hamming_distance(bytearray_to_bits(blocks[i]), bytearray_to_bits(blocks[i + 1])) for i in range(NUM_BLOCKS)) edit_distance /= (NUM_BLOCKS * keysize) decryption = Decryption(key=keysize, plaintext='', score=edit_distance) decryptions.append(decryption) decryptions = sorted(decryptions) keysize = decryptions[0].key
('uid', 10), ('role', 'user')]) # Now, two more easy functions. Generate a random AES key, then: key = os.urandom(16) cipher = AES.new(key, AES.MODE_ECB) encrypted_profile = cipher.encrypt( pad(bytes(dict_to_form(profile_for("*****@*****.**")), 'ascii'))) # Tamper the block to make the ciphertext read role=admin plaintext_profile = pad( bytes(dict_to_form(profile_for("*****@*****.**")), 'ascii')) encrypted_profile = cipher.encrypt(plaintext_profile) print("Profile: {0}, Encrypted Profile: {1}".format(plaintext_profile, encrypted_profile)) #[email protected] maaaaaa&uid=10& role=user 7 padding bytes #[email protected] maaaaaa&uid=10& role=admin 6 padding bytes replacement_block = b"role=admin" + b"\x06" * 6 encrypted_replacement_block = cipher.encrypt(replacement_block) blocks = group(encrypted_profile, AES.block_size) blocks[-1] = encrypted_replacement_block tampered_cipertext = b''.join(blocks) print(tampered_cipertext) print(cipher.decrypt(tampered_cipertext))
def test_group_odd_length(): """Testing group with odd length string""" assert group("example", 2) == ['ex', 'am', 'pl', 'e']
def test_group_even_length(): """Testing group with even length string""" assert group("test", 2) == ['te', 'st']