def edit_ctr(ciphertext: bytes, key: bytes, offset: int, newtext: bytes) -> bytes: """ Given the input `ciphertext`, decrypts using the `key`, seeks into the text to the `offset`, and overwrites `newtext` in. If `offset` is greater than the length of the original ciphertext, throws a ValueError. """ if offset > len(ciphertext): raise ValueError('Offset must be less than or equal to the length of the ciphertext') plaintext = aes_ctr(key, NONCE, ciphertext) modified_plaintext = plaintext[:offset] + newtext + plaintext[offset + len(newtext):] return aes_ctr(key, NONCE, modified_plaintext)
def encrypt(msg: bytes) -> bytes: """ Sandwiches user input between two existing strings, surrounding any ';' or '=' characters in single quotes. Pads and encrypts in AES CTR and returns the encrypted messge. """ # I interpreted "quote out" to mean surround disallowed characters with # single quotes msg_str = msg.decode() msg = CLEAN_RE.sub(r"'\1'", msg_str) # Convert string (necessary for regex replacement) back to bytes msg = bytes(msg.encode()) # Tack on the prefix and suffix msg = PREFIX + msg + SUFFIX return aes_ctr(KEY, NONCE, msg)
#!/usr/bin/env python from base64 import b64decode from os import urandom from cryptopals_10 import xor from cryptopals_18 import aes_ctr from cryptopals_19 import guess_aes_ctr_keystream if __name__ == '__main__': print('Challenge #20 - Break fixed-nonce CTR statistically') nonce = 0 key = urandom(16) ciphertexts = [] with open('data/20.txt', 'rb') as f: for line in f: ciphertexts.append(aes_ctr(key, nonce, b64decode(line))) guessed_keystream = guess_aes_ctr_keystream(ciphertexts) print(guessed_keystream) for ct in ciphertexts: print(xor(guessed_keystream, ct))
def is_admin(ciphertext: bytes) -> bytes: """ Returns whether or not an admin key-value pair is present in the decrypted string. """ msg = aes_ctr(KEY, NONCE, ciphertext) return b';admin=true;' in msg
best_score = None best_byte = None for test_byte in range(0, 256): test_score = score_english(bytes([i ^ test_byte for i in current_bytes])) if best_score is None or test_score > best_score: best_score = test_score best_byte = test_byte guessed_keystream.append(best_byte) idx += 1 return guessed_keystream if __name__ == '__main__': print('Challenge #19 - Break fixed-nonce CTR mode using substitutions') nonce = 0 key = urandom(16) ciphertexts = set() for plaintext in INPUTS: ciphertexts.add(aes_ctr(key, nonce, plaintext)) guessed_keystream = guess_aes_ctr_keystream(ciphertexts) print(f'Guessed Keystream: {bytes(guessed_keystream)}') for ct in ciphertexts: print(xor(guessed_keystream, ct))
Given the input `ciphertext`, decrypts using the `key`, seeks into the text to the `offset`, and overwrites `newtext` in. If `offset` is greater than the length of the original ciphertext, throws a ValueError. """ if offset > len(ciphertext): raise ValueError('Offset must be less than or equal to the length of the ciphertext') plaintext = aes_ctr(key, NONCE, ciphertext) modified_plaintext = plaintext[:offset] + newtext + plaintext[offset + len(newtext):] return aes_ctr(key, NONCE, modified_plaintext) def ecb_decrypt(key, file_path): with open(file_path, 'rb') as f: text = base64.b64decode(f.read()) cipher = AES.new(key, AES.MODE_ECB) # IV is ignored return cipher.decrypt(text) if __name__ == '__main__': print('Challenge #25 - Break "random access read/write" AES CTR') key = os.urandom(16) unknown_plaintext = ecb_decrypt(b'YELLOW SUBMARINE', 'data/25.txt') ciphertext = aes_ctr(key, NONCE, unknown_plaintext) plaintext = edit_ctr(ciphertext, key, 0, ciphertext) assert unknown_plaintext == plaintext