def crackMTSeedKnownPlain(bstr_cipher: bytes, bstr_known_plain: bytes, offset: int, bits: int, seed_bits: int) -> int: """ From a known plaintext recover the seed TODO: for bits to be other sizes too some lines need to be changed :param bstr_cipher: :param bstr_known_plain: :param offset: the known plain text starts at this position in the cipher text (ch. 24 prepends random known text) :param bits: number of bits to get from MT :param seed_bits: number of bits of the seed :return: recovered seed """ known_plain_cipher = bstr_cipher[offset:offset + len(bstr_known_plain)] for seed in range(2**seed_bits): random.seed(seed) # construct key keystream = b'' # fast forward to the proper point in the MT sequence for _ in range(offset): random.getrandbits(bits) for i in range(len(bstr_known_plain)): keystream += bytes([random.getrandbits(bits)]) plain = xorBytestrings(known_plain_cipher, keystream) if plain == bstr_known_plain: return seed return None
def aesCTREditRecoverPlain(ctr_edit_fn: types.FunctionType, *edit_fn_args) -> bytes: """ Recovers the the plain text to a ctr encoded stream by calling the CTR edit function on it with and editing the whole plain text. The key is then "removed" by xoring the original and the edited cipher text leaving the xored original plain text and the newplain text :param ctr_edit_fn: CTR edit function name :param edit_fn_args: the arguments to the function. assumes the cipher bytearray is the first :return: """ original_cipher = edit_fn_args[0][:] newplain = b'X' * len(original_cipher) ctr_edit_fn(*edit_fn_args[:-1], newplain) plain_xor = xorBytestrings(original_cipher, edit_fn_args[0]) return xorBytestrings(plain_xor, newplain)
def aesCTR(bstr, bstr_key, num_bits, bstr_nonce): """ encryption and decryption is the same in this mode """ state_iter = stateGenerator(bstr, 16, modis0=False) xored = b'' counter = 0 for state in state_iter: bstr_nonce_ctr = bstr_nonce + struct.pack('<q', counter) secondary_key = aesEncrypt(bstr_nonce_ctr, bstr_key, num_bits) # this will be shorter than state len if the last state is remainder: secondary_key = secondary_key[:len(state)] xored += xorBytestrings(state, secondary_key) counter += 1 return xored
def mtStreamCipher(bstr_msg: bytes, seed: int) -> bytes: """ set 3 / ch. 24, en/decrypt with keystream generated from MT random 8 bit numbers :param bstr_msg: :param seed: seed that will be converted to 16 bit (xored with 0xFFFF) :return: """ # 16 bit seed random.seed(seed & 0xFFFF) keystream = b'' for i in range(len(bstr_msg)): rnum = random.getrandbits(8) keystream += bytes([rnum]) return xorBytestrings(bstr_msg, keystream)
def aesEncrypt(bstr_msg, bstr_key, num_bits, mode='ecb', bstr_IV=None): if len(bstr_key) != 16: # no key derivation yet print("Unsuitable key length!") exit(1) # without padding for now #bstr_msg = misc.padPKCS7(bstr_msg, 16) state_iter = stateGenerator(bstr_msg, 16) rounds = {128: 10, 192: 12, 256: 14} round_keys = aesKeyExpansion(bstr_key) cipher = b'' preceding_cipher_block = bstr_IV for state in state_iter: if mode == 'cbc': state = xorBytestrings(state, preceding_cipher_block) # initial (not actual) round state_trans = bytes(makeNDArrayFrom(state, 4, 4).transpose().flatten()) key_trans = bytes( makeNDArrayFrom(bstr_key, 4, 4).transpose().flatten()) bstr_state = aesAddRoundkey(state_trans, key_trans) for r in range(0, rounds[num_bits]): if r == rounds[num_bits] - 1: # last round no Mix Columns bstr_state = aesSubBytes(bstr_state) bstr_state = aesShiftRows(bstr_state) key_trans = bytes( makeNDArrayFrom(round_keys[r], 4, 4).transpose().flatten()) bstr_state = aesAddRoundkey(bstr_state, key_trans) else: bstr_state = aesSubBytes(bstr_state) bstr_state = aesShiftRows(bstr_state) bstr_state = aesMixColumns(bstr_state) key_trans = bytes( makeNDArrayFrom(round_keys[r], 4, 4).transpose().flatten()) bstr_state = aesAddRoundkey(bstr_state, key_trans) state_result = bytes( makeNDArrayFrom(bstr_state, 4, 4).transpose().flatten()) preceding_cipher_block = state_result cipher += state_result return cipher
def hammingDistance(bstr1: bytes, bstr2: bytes) -> int: """ Compute Hamming Distance between two strings :param bstr1: byte string 1 :param bstr2: byte string 2 :return: int """ if len(bstr1) != len(bstr2): print("xor not equal len!") exit(1) diff = b'' b1 = bstr1 b2 = bstr2 diff = xorBytestrings(b1, b2) distance = 0 for b in diff: distance += bin(b).count("1") return (distance)
def aesDecrypt(bstr_cipher, bstr_key, num_bits, mode='ecb', bstr_IV=None): if len(bstr_key) != 16: # no key derivation yet print("Unsuitable key length!") exit(1) state_iter = stateGenerator(bstr_cipher, 16) rounds = {128: 10, 192: 12, 256: 14} round_keys = aesKeyExpansion(bstr_key) cipher = b'' prev_cipherstate = bstr_IV for state in state_iter: # initial round bstr_state = bytes(makeNDArrayFrom(state, 4, 4).transpose().flatten()) key_trans = bytes( makeNDArrayFrom(bstr_key, 4, 4).transpose().flatten()) for r in reversed(range(0, rounds[num_bits])): if r == rounds[num_bits] - 1: # first round no inverse MixColumns key_trans = bytes( makeNDArrayFrom(round_keys[r], 4, 4).transpose().flatten()) bstr_state = aesAddRoundkey(bstr_state, key_trans) bstr_state = aesInvShiftRows(bstr_state) bstr_state = aesInvSubBytes(bstr_state) else: key_trans = bytes( makeNDArrayFrom(round_keys[r], 4, 4).transpose().flatten()) bstr_state = aesAddRoundkey(bstr_state, key_trans) bstr_state = aesInvMixColumns(bstr_state) bstr_state = aesInvShiftRows(bstr_state) bstr_state = aesInvSubBytes(bstr_state) key_trans = bytes( makeNDArrayFrom(bstr_key, 4, 4).transpose().flatten()) bstr_state = aesAddRoundkey(bstr_state, key_trans) bstr_state = bytes( makeNDArrayFrom(bstr_state, 4, 4).transpose().flatten()) if mode == 'cbc': bstr_state = xorBytestrings(bstr_state, prev_cipherstate) prev_cipherstate = state cipher += bstr_state #cipher = misc.unpadPKCS7(cipher, 16) return cipher
def aesCTREdit(barray_cipher: bytes, bstr_key: bytes, bstr_nonce: bytes, bits: int, offset: int, bstr_newtext: bytes): """ set 4 / ch. 25 Edit AES CTR text encrypted in place at offset. Therefor generate the key bytes from that offset on, ecrypt the newtext and replace. :param barray_cipher: AES CTR encrypted plain text, bytearray that will be changed :param bstr_key: key :param bstr_nonce: :param bits: :param offset: offset :param bstr_newtext: new plain text (not going beyond the end of the cipher) :return: """ if offset >= len(barray_cipher): raise exceptions.ParamValueError if len(bstr_newtext) > len(barray_cipher) - offset: raise exceptions.ParamClashError counter = offset // 16 keybytes_to_generate = len(bstr_newtext) secondary_key = b'' while keybytes_to_generate > 0: bstr_nonce_ctr = bstr_nonce + struct.pack('<q', counter) secondary_key += aesEncrypt(bstr_nonce_ctr, bstr_key, bits) counter += 1 keybytes_to_generate -= 1 index_in_block = offset % 16 secondary_key_sub = secondary_key[index_in_block:index_in_block + len(bstr_newtext)] new_cipher = xorBytestrings(bstr_newtext, secondary_key_sub) if len(bstr_newtext) == 1: barray_cipher[offset] = new_cipher[0] else: barray_cipher[offset:offset + len(bstr_newtext)] = new_cipher
def aesKeyExpansion(bstr_key): """ Returns the roundkeys a list of byte strings TODO: make length parameters variable """ if len(bstr_key) != 16 and len(bstr_key) != 24 and len(bstr_key) != 32: print("Invalid key length!") exit(1) numbits = len(bstr_key) * 8 rounds = {128: 10, 192: 12, 256: 14} round_keys = [] # transposed since we are operating on the columns prevkey = makeNDArrayFrom(bstr_key, 4, 4) newkey = b'' offset = 0 rcon_round = 1 while len(round_keys) < rounds[numbits]: # on every first 4 byte group of the 16 byte blocks perform rotate, # subbytes if offset == 0: word = bytes(prevkey[3]) word = aesKeyExpansionCore(word, rcon_round) rcon_round += 1 else: word = newkey[offset - 4:offset] word_from_prevkey = bytes(prevkey.flatten())[offset:offset + 4] word = xorBytestrings(word, word_from_prevkey) newkey += word offset += 4 if offset == 16: offset = 0 prevkey = makeNDArrayFrom(newkey, 4, 4) # transpose back and convert to byte string before appending to # result list newkey = bytes(makeNDArrayFrom(newkey, 4, 4).flatten()) round_keys.append(newkey) newkey = b'' return round_keys
def testXorBytestrings(self): bstr1 = b'\x01\x01\x01\x01\x01\x01\x01\x01' bstr2 = b'\x00\x00\x00\x00\x00\x00\x00\x00' result = helpers.xorBytestrings(bstr1, bstr2) self.assertEqual(result, b'\x01\x01\x01\x01\x01\x01\x01\x01') self.assertEqual(helpers.xorBytestrings(b'A', b'A'), b'\x00')
def aesAddRoundkey(ndarray_state, ndarray_key): return xorBytestrings(ndarray_state, ndarray_key)