def test_challenge16(self): key = encryption_key() init_vector = iv() ciphertext = encrypt_kvps('admin=true', key, init_vector) self.assertFalse( is_admin(ciphertext, key, init_vector), 'Should have escaped the =' ) # 0 1 2 3 4 # comment1=cooking%20MCs;userdata=true;comment2=%20like%20a%20pound%20of%20bacon # comment1=cooking%20MCs;us;admin=true;comment2=%20like%20a%20pound%20of%20bacon ciphertext = encrypt_kvps('true', key, init_vector) # Block 0 ciphertext is XOR'ed against the decryptor output of block 1 # to produce the plaintext output. Twiddling a bit in the block 0 # ciphertext will invert the corresponding bit in the block 1 plaintext combined = zip( ciphertext[:16], '%20MCs;userdata=', '%20MCs;us;admin=' ) twiddled = ''.join( twiddle_bits(iv_byte, actual_char, target_char) for iv_byte, actual_char, target_char in combined ) ciphertext = twiddled + ciphertext[16:] self.assertTrue(is_admin(ciphertext, key, init_vector))
def test_challenge26(self): key = crypto.encryption_key() nonce = '\x00' * 8 ciphertext = encrypt_kvps('admin=true', key, nonce) self.assertFalse(is_admin(ciphertext, key, nonce), 'Should have escaped the =') # 0 1 2 3 4 # comment1=cooking%20MCs;userdata=true;comment2=%20like%20a%20pound%20of%20bacon # comment1=cooking%20MCs;us;admin=true;comment2=%20like%20a%20pound%20of%20bacon ciphertext = encrypt_kvps('true', key, nonce) self.assertFalse(is_admin(ciphertext, key, nonce)) # ciphertext ^ orig plaintext = keystream # keystream ^ (desired plaintext) = new ciphertext to splice in start = 25 orig_plaintext = 'erdata' keystream = bitops.xor(ciphertext[start:start + len(orig_plaintext)], orig_plaintext) tampered_chunk = bitops.xor(keystream, ';admin') tampered_ciphertext = (ciphertext[:start] + tampered_chunk + ciphertext[start + len(tampered_chunk):]) self.assertTrue(is_admin(tampered_ciphertext, key, nonce))
def test_challenge13(self): # email=ryan%40rescale.com&uid=10&role=user # Generate some ciphertext where the 2nd block starts with admin # 0 1 2 3 # email=AAAAAAAAAAadmin&uid=10&role=user key = encryption_key() ciphertext = encrypt_profile('AAAAAAAAAAadmin', key) admin_block = ciphertext[16:32] # print ecb_decrypt(admin_block, key) # Provide an email address that causes a cipher block to end with role= # 0 1 2 3 # email=ryan%2BAAAAAAAAAA%40gmail.com&uid=10&role=user ciphertext = encrypt_profile('*****@*****.**', key) prefix_length = 16 * 3 prefix = ciphertext[:prefix_length] suffix = ciphertext[prefix_length:] # Then paste the admin block to the end of that # Tack on the original last block to prevent two role qs args from # being created. manipulated = prefix + admin_block + suffix profile = decrypt_profile(manipulated, key) self.assertIn('admin', profile['role'])
def multimode_oracle(plaintext, mode): tampered = random_padding() + plaintext + random_padding() key = encryption_key() if mode == 'ecb': return ecb_encrypt(tampered, key) elif mode == 'cbc': return cbc_encrypt(tampered, key, iv()) raise ValueError('Unknown mode')
def test_challenge20(self): plaintexts = [ base64_to_bytes(line) for line in utils.readlines('20.txt') ] key = encryption_key() nonce = '\0' * 8 ciphertexts = [ ctr_encrypt(m, key, nonce) for m in plaintexts ] # Because of the fixed-nonce, the encrypted keystream bytes are # repeated for every plaintext message. # # ciphertext[i] ^ keystream[i] = plaintext[i] # # We can create a transposed ciphertext message by concatenating # ciphertext[i] from every encrypted message and then xor'ing that # against a guessed keystream byte. Then we can test whether the # resulting plaintext looks like english based on character # distributions. If so, then we've figured out the keystream byte. keystream = '' for index in itertools.count(): transposed = ''.join(m[index:index+1] for m in ciphertexts) if not transposed: break allowed_chars = None if index == 0: allowed_chars = string.ascii_uppercase + '"\'' score, _, key = crack.find_best_single_byte_key( transposed, allowed_chars=allowed_chars ) # print 'Best score for index {}: {}'.format(index, score) keystream += key[0] recovered_plaintexts = [ bitops.xor(m, keystream) for m in ciphertexts ] # for m in recovered_plaintexts: # print m self.assertIn( '\'Cause my girl is definitely mad / \'Cause it took us too long to do this album', recovered_plaintexts )
def test_challenge18(self): ciphertext = base64_to_bytes('L77na/nrFsKvynd6HzOoG7GHTLXsTVu9qvY/2syLXzhPweyyMTJULu/6/kXX0KSvoOLSFQ==') nonce = '\x00' * 8 plaintext = ctr_decrypt(ciphertext, 'YELLOW SUBMARINE', nonce) self.assertEqual( 'Yo, VIP Let\'s kick it Ice, Ice, baby Ice, Ice, baby ', plaintext ) plaintext = 'the quick brown fox jumped over the lazy dog.' key = encryption_key() ciphertext = ctr_encrypt(plaintext, key, nonce) self.assertEqual(plaintext, ctr_decrypt(ciphertext, key, nonce))
def test_challenge17(self): key = encryption_key() ciphertext, init_vector = encrypt_random_choice(key) block_size = len(init_vector) blocks = ( ciphertext[i:(i+block_size)] for i in range(0, len(ciphertext), block_size) ) prev_block = init_vector plaintext = '' for block in blocks: plaintext_block = [0] * block_size for i in reversed(range(block_size)): padding_value = block_size - i # Generate a tampered_iv that will produce a plaintext block # that is properly pkcs7-padded with padding_value tampered_iv = [0] * block_size for j in range(i + 1, block_size): tampered_iv[j] = ( ord(prev_block[j]) ^ plaintext_block[j] ^ padding_value ) # Use the oracle to find the tampered iv value for block # position i that will produce padding_value in the plaintext for modified in range(256): tampered_iv[i] = modified if is_padding_valid(block, key, ''.join(chr(c) for c in tampered_iv)): break # Now the plaintext for index i can be calculated: # Pi = prev_block[i] ^ modified ^ padding_value plaintext_block[i] = ( ord(prev_block[i]) ^ modified ^ padding_value ) plaintext += ''.join(chr(c) for c in plaintext_block) prev_block = block plaintext = strip_pkcs_7(plaintext) # print plaintext self.assertIn(plaintext, secret_messages)
def test_challenge27(self): key = crypto.encryption_key() ciphertext = encrypt_kvps_cbc('hello', key) tampered = (ciphertext[:16] + ('\x00' * 16) + ciphertext[:16] + ciphertext[16:]) try: is_admin_cbc(tampered, key) except ValueError as e: expected_prefix = 'Invalid message ' plaintext = e.message[len(expected_prefix):] # C1 = 0 # P2 = D(E(P0 ^ IV)) ^ C1 # P2 = P0 ^ IV ^ 0 # P2 = P0 ^ IV # The first block in the recovered plaintext is P0 and iv = key so: # P0 ^ P2 = IV = KEY # # The 3rd plaintext block is # D(E(P0 ^ IV)) ^ 0 recovered_key = bitops.xor(plaintext[:16], plaintext[32:48]) self.assertEqual(key, recovered_key)
def test_challenge25(self): ciphertext = convert.base64_to_bytes(utils.read('25.txt')) plaintext = crypto.ecb_decrypt(ciphertext, 'YELLOW SUBMARINE') key = crypto.encryption_key() nonce = '\x00' * 8 ciphertext = crypto.ctr_encrypt(plaintext, key, nonce) # The edit function lets us recover the original keystream because # in CTR mode: plaintext ^ keystream = ciphertext and then through # the magic of xor: # # plaintext ^ ciphertext = keystream # # Through the edit function, we know the plaintext and ciphertext # for every index in the byte stream. keystream = '' for offset in range(len(ciphertext)): new_ciphertext = edit(ciphertext, key, nonce, offset, 'A') keystream += bitops.xor(new_ciphertext[offset], 'A') recovered_plaintext = bitops.xor(ciphertext, keystream) self.assertEquals(plaintext, recovered_plaintext)
'U28gc2Vuc2l0aXZlIGhpcyBuYXR1cmUgc2VlbWVkLA==', 'U28gZGFyaW5nIGFuZCBzd2VldCBoaXMgdGhvdWdodC4=', 'VGhpcyBvdGhlciBtYW4gSSBoYWQgZHJlYW1lZA==', 'QSBkcnVua2VuLCB2YWluLWdsb3Jpb3VzIGxvdXQu', 'SGUgaGFkIGRvbmUgbW9zdCBiaXR0ZXIgd3Jvbmc=', 'VG8gc29tZSB3aG8gYXJlIG5lYXIgbXkgaGVhcnQs', 'WWV0IEkgbnVtYmVyIGhpbSBpbiB0aGUgc29uZzs=', 'SGUsIHRvbywgaGFzIHJlc2lnbmVkIGhpcyBwYXJ0', 'SW4gdGhlIGNhc3VhbCBjb21lZHk7', 'SGUsIHRvbywgaGFzIGJlZW4gY2hhbmdlZCBpbiBoaXMgdHVybiw=', 'VHJhbnNmb3JtZWQgdXR0ZXJseTo=', 'QSB0ZXJyaWJsZSBiZWF1dHkgaXMgYm9ybi4=', ] ] key = encryption_key() nonce = '\x00' * 8 ciphertexts = [ ctr_encrypt(m, key, nonce) for m in plaintexts ] translations = Translations(ciphertexts) view = TranslationsView(translations) curses.wrapper(view.event_loop) # Answer: Easter 1916 by Yeats # Set the line third from the bottom to: # # He, too, has been changed in his turn, #