def hmac_sha256(key, message): """ Creates an HMAC using SHA-256. Args: key: The HMAC key. message: The message to generate the MAC for. Returns: The HMAC for the message under the given key """ # If the key is longer than the blocksize, # then truncate it by hashing it if (len(key) > 64): key = sha256(key).digest() # If the key is shorter than blocksize, # pad with 0s if (len(key) < 64): key = key + (b'\x00' * (64 - len(key))) o_pad = c2.xorstrs(key, b'\x5c'*64) i_pad = c2.xorstrs(key, b'\x36'*64) i_msg = i_pad + message o_msg = o_pad + sha256(i_msg).digest() return sha256(o_msg).digest()
def attack_byte(block, prev_block, byte_num, plaintext): """ Attacks a single byte using the padding oracle attack. This function contains the real magic for the attack. Args: block: The block of ciphertext that is being attacked. prev_block: The previous block of ciphertext used to attack the current one. byte_num: The byte number of the block that we are getting. plaintext: The known plaintext so far. Returns: The byte of decoded byte of plaintext Raises: RuntimeException if no byte can be found """ # Knownxor is super tricky. Read the link from the readme. # We want all the last values to be good padding. # To do this we xor the prev_block with the known plaintext with # the value we want to get for padding. knownxor = b'' if (len(plaintext) > 0): knownxor = c2.xorstrs(prev_block[-len(plaintext):], plaintext) knownxor = c2.xorstrs(bytes([16-byte_num] * len(plaintext)), knownxor) # Test each byte, returning when the padding is valid. for i in range(1, 256): bad_prev_b = bytes([0]) * byte_num # The magic here will only allow for valid padding when i is # the same as the value of the original plaintext. bad_prev_b += bytes([i ^ (16-byte_num) ^ prev_block[byte_num]]) bad_prev_b += knownxor if (decryption_oracle(bad_prev_b + block)): return bytes([i]) raise Exception
def aes_128_cbc_decrypt(txt, key, IV=b'\x00' * 16): """ Decrypts the given bytestring using AES-128 in CBC mode Args: txt: The text to be decrypted key: The encryption key iv: The initialization vector Returns: The decrypted bytestring. """ # Assert all size constrains if len(txt) % 16 != 0: raise ValueError('Input length must be a multiple of 16, got ' + str(len(txt))) if len(key) != 16: raise ValueError('Key must be length 16, got ' + str(len(key))) if len(IV) != 16: raise ValueError('IV must be length 16, got ' + str(len(IV))) num_blocks = len(txt) // 16 prev_block = IV result = [] # Loop through each block, XORing with the previous for i in range(num_blocks): cur_block = c6.get_block(txt, i, 16) temp = cur_block cur_block = c7.aes_128_ecb_decrypt(cur_block, key) cur_block = c2.xorstrs(cur_block, prev_block) prev_block = temp result.append(cur_block) return b''.join(result)
def aes_128_cbc_encrypt(txt, key, IV=b'\x00' * 16): """ Encrypts a bytestring under AES-128 in CBC mode Args: txt: The plaintext to be encrypted key: The key to encrypt under iv: The initialization vector Returns: The encrypted text under AES-128 in CBC mode. """ # Assert all size constraints if len(txt) % 16 != 0: raise ValueError('Input length must be a multiple of 16, got ' + str(len(txt))) if len(key) != 16: raise ValueError('Key must be length 16, got ' + str(len(key))) if len(IV) != 16: raise ValueError('IV must be length 16, got ' + str(len(IV))) num_blocks = len(txt) // 16 prev_block = IV result = [] # Loop through each block, XORing with the previous for i in range(num_blocks): cur_block = c6.get_block(txt, i, 16) cur_block = c2.xorstrs(prev_block, cur_block) cur_block = c7.aes_128_ecb_encrypt(cur_block, key) prev_block = cur_block result.append(prev_block) return b''.join(result)
def single_byte_xor(txt): """ Solves the single byte XOR cipher by trying every possible key value and scoring the resulting plaintext for its similarity to the English language. Args: txt: The ciphertext to be deciphered. Returns: The key with the highest score. """ maxScore = -3 bestKey = 0 for x in range(256): # Score every attempt and take the highest score attempt = c2.xorstrs(txt, bytes([x]) * len(txt)) scr = score(attempt) if DEBUG: print(str(x) + ': ' + str(scr)) if scr > maxScore: maxScore = scr bestKey = x return bestKey
def test_challenge_3(self): actual_key = single_byte_xor(self.ctxt) expected_key = 88 actual_txt = c2.xorstrs(self.ctxt, bytes([actual_key])*len(self.ctxt)) expected_txt = b'Cooking MC\'s like a pound of bacon' self.assertEqual(actual_key, expected_key) self.assertEqual(actual_txt, expected_txt)
def repeating_key_xor(txt, key): """ Encrypts the given plain text under the given key after extending it. Args: txt: The plain text to be encrypted key: The key to encrypt under Returns: The ciphertext created by XORing the plaintext under the repeating key. """ return c2.xorstrs(txt, key_extend(key, len(txt)))
def attack_cbc(): """ Breaks CBC mode when the IV is key, as described in the challenge. Returns: True if the attack worked """ ct = encrypt_userdata(b'blahblahblah') bad_ct = ct[:16] + (b'\x00' * 16) + ct[:16] valid, pt = verify_url(bad_ct) k = c2.xorstrs(c6.get_block(pt, 0), c6.get_block(pt, 2)) return k == key
def hamming_dist(str1, str2): """ Calculates the Hamming distance between two bytestrings. Args: str1: The first bytestring str2: The second bytestring Returns: The hamming distance between the two given strings """ # XOR each character, convert to binary representation, # and count the 1's. This gives you the differing bits. xord = c1.asciitohex(c2.xorstrs(str1, str2)) return bin(int(xord, base=16)).count('1')
def aes_128_ctr(txt, key, nonce=0): """ Encrypts the given txt under AES-128 in CTR mode with the given key and a nonce. Args: txt: The text to encrypt key: The key to encrypt under nonce (optional): The nonce for CTR mode Returns: The encrypted txt. """ num_blocks = (len(txt) // 16) + 1 keystream = b'' for i in range(num_blocks): val = __little_endian(nonce) + __little_endian(i) keystream += AES.new(key, AES.MODE_ECB).encrypt(val) return c2.xorstrs(txt, keystream[:len(txt)])