def encrypt(data, key): random_mask = bytearray(32) xor_subroutine(data, random_mask) xor_subroutine(random_mask, key[:32]) state = bytes_to_words(data, 4) keyed_bit_transposition(state, bytes_to_words(key[32:64], 4)) data[:] = words_to_bytes(state, 4) + random_mask
def encrypt(data, key, rounds=2): state = bytes_to_words(data, WORDSIZE) key = bytes_to_words(key, WORDSIZE) for round in range(rounds): permutation512(state, key) data[:] = words_to_bytes(state, WORDSIZE)
def test_permutation512(): data = bytes_to_words(bytearray(8 * WORDSIZE), WORDSIZE) key = bytes_to_words(bytearray(8 * WORDSIZE), WORDSIZE) _data = data[:] permutation512(data, key) invert_permutation512(data, key) assert data == _data
def test_keyed_bit_transposition(): data = bytes_to_words(range(32), 4) _data = data[:] key = bytes_to_words(range(64, 64 + 32), 4) keyed_bit_transposition(data, key) invert_keyed_bit_transposition(data, key) assert data == _data print "Passed keyed_bit_transposition Unit Test"
def decrypt(data, key, parity=1): state = bytes_to_words(data[:32], 4) invert_keyed_bit_transposition(state, bytes_to_words(key[32:64], 4)) data[:32] = words_to_bytes(state, 4) random_mask = data[32:64] if parity: xor_subroutine(random_mask, key[:32]) xor_subroutine(data, random_mask) del data[32:64] return data
def visualize_permutation512(): from crypto.analysis.visualization import test_8x64_function data = bytes_to_words(bytearray(8 * WORDSIZE), WORDSIZE) key = bytes_to_words(bytearray(8 * WORDSIZE), WORDSIZE) data[0] = 1 def test_function(*data): state = list(data) permutation512(state, state[:]) return state test_8x64_function(test_function, data)
def decrypt64(data, key, output_type="bytes"): """ Decrypts ciphertext produced by encrypt64. First, the bit permutation is reversed. Then the padding is xor'd with the message and discarded. It is crucial for the long term security of the secret key that the random padding is not leaked.""" output = invert_bit_permutation128(bytes_to_words(data, 4), key) if output_type == "bytes": return words_to_bytes(output, 4)[:8] else: if output_type != "words": raise ValueError("Invalid output_type '{}'".format(output_type)) return invert_bit_permutation128(bytes_to_words(data, 4), key)
def decrypt(data, key, rounds=2): state = bytes_to_words(data, WORDSIZE) _key = bytes_to_words(key, WORDSIZE) keys = key_schedule(_key, rounds) xor_subroutine(state, keys[0]) for round in reversed(range(rounds)): invert_permutation512(state, keys[round + 1]) xor_subroutine(state, keys[0]) data[:] = words_to_bytes(state, WORDSIZE)
def decrypt(data, key, rounds=2): state = bytes_to_words(data, 4) _key = bytes_to_words(key, 4) keys = key_schedule(_key, rounds) xor_subroutine(state, keys[0]) for round in reversed(range(rounds)): invert_permutation256(state) invert_keyed_bit_transposition(state, keys[round + 1]) xor_subroutine(state, keys[0]) data[:] = words_to_bytes(state, 4)
def test_encrypt_decrypt(): message = "Testing!" key = bytes_to_words(bytearray(urandom(32)), 2) ciphertext = encrypt(message, key) print ciphertext plaintext = decrypt(ciphertext, key) assert plaintext == message, plaintext
def seedgen(padding, byte): output = bytearray(32) try: output[-1] = byte except (ValueError, TypeError): output[-1] = byte[0] return bytes_to_words(output, 4)
def decrypt8(ciphertext, key): output = words_to_bytes(invert_bit_permutation128(bytes_to_words(ciphertext, 4), key), 4) padding = output[1:] permutation(padding) for padding_byte in padding: output[0] ^= padding_byte return output[0]
def test_encrypt_decrypt(): byte_size = 8 data = bytearray(16 * byte_size) key = bytearray(16 * byte_size) data[-1] = 1 rounds = 16 bit_size = byte_size * 8 size = (bit_size, ((2 ** bit_size) - 1), bit_size - (3 * byte_size)) data = bytes_to_words(data, byte_size) key = bytes_to_words(key, byte_size) plaintext = data[:] encrypt(data, key, rounds, size) print ''.join(bytes(integer_to_bytes(block, byte_size)) for block in data) print [byte for byte in data]
def stream_cipher(data, key, seed, modulus=2 ** 32, wordsize=32): #assert isinstance(data, bytearray) state = bytes_to_words(bytearray(data), 4) prng_key, seed_key = key xor_subroutine(seed, seed_key) assert len(seed) == 4, len(seed) a, b, c, d = seed k0, k1, k2, k3 = prng_key a, b, c, d = add_key(a, b, c, d, k0, k1, k2, k3) a, b, c, d = permutation(a, b, c, d, modulus) assert len(state) for index in range(len(state) - 1): a, b, c, d = add_key(a, b, c, d, k0, k1, k2, k3) a, b, c, d = add_key(a, b, c, d, index, index + 1, index + 2, index + 3) b, c, d, a = permutation(a, b, c, d) state[index] ^= (a + b) % modulus state[(index + 1)] ^= (c + d) % modulus _data = words_to_bytes(state, 4) try: data[:] = _data except (ValueError, TypeError): pass return _data
def encrypt64(data, key, output_type="bytes"): """ Encrypt 64 bits of data using key. Encryption is: - Randomized - The randomizing value is kept secret/not sent in the clear - Homomorphic - Supports unlimited fully homomorphic operations - D(E(x) ^ E(y)) == x ^ y - D(E(x) & E(y)) == x & y Encryption is performed by generating a 64-bit random padding value and then concatenating the padding to the 64-bit message, and finally applying a keyed bit permutation on the result. To compute the XOR/AND of 2 ciphertexts, simply XOR/AND the ciphertexts together. - An encryption of 0 should be added to the target of any AND afterwards - This is due to the tendency of AND to set bits to 0. - The noise from the ciphertext will ensure hamming weight on the target ciphertext stays balanced""" padding = new_key(2, 4) # generate 2 32-bit words inputs = tuple(bytes_to_words(bytearray(data), 4)) + padding if output_type == "bytes": return words_to_bytes(bit_permutation128(inputs, key), 4) else: if output_type != "words": raise ValueError("Invalid output_type '{}'".format(output_type)) return bit_permutation128(inputs, key)
def test_permutation256(): state = bytes_to_words(bytearray(32), 4) _state = state[:] permutation256(state) invert_permutation256(state) assert state == _state
def strange_hash(data, key=KEY): output = list() state = 0 data = bytes_to_words(bytearray(pad_input(data, 2)), 2) for index, word in enumerate(data): state ^= key[branch(word ^ index)] output.append(state) return odd_size_to_bytes(output, 12)
def memory_hard_hash(data, rounds, hash_key=HASH_KEY, modulus=WORD_COUNT): output = [0 for count in range(STATE_SIZE)] hash_input = bytes_to_words(bytearray(pad_input(data, STATE_SIZE * WORD_BYTES)), WORD_BYTES) for round in range(rounds): for index, word in enumerate(hash_input): value = hash_key[(word + round + output[index % STATE_SIZE]) % modulus] addition_subroutine(output, value, modulus) return bytes(words_to_bytes(output, WORD_BYTES))
def visualize_permutation256(): data = bytearray(32) key = bytearray(32) key[0] = 1 while not raw_input("Press any key + enter to end, or enter to continue: "): encrypt(data, key) print print '\n'.join(format(word, 'b').zfill(32) for word in bytes_to_words(data, 4))
def test_random_active_sboxes(input_count, word_size): output = [] _word_size = word_size / 8 for count in range(input_count): a0, b0, c0, d0 = bytes_to_words(bytearray(urandom(4 * _word_size)), _word_size) a1, b1, c1, d1 = bytes_to_words(bytearray(urandom(4 * _word_size)), _word_size) active_sboxes = 0 for index in range(word_size): mask = 1 << index word0 = (a0 & mask) | ((b0 & mask) << 1) | ((c0 & mask) << 2) | ((d0 & mask) << 3) word1 = (a1 & mask) | ((b1 & mask) << 1) | ((c1 & mask) << 2) | ((d1 & mask) << 3) if word0 != word1: active_sboxes += 1 output.append(active_sboxes) print("Minimum #: {}".format(min(output))) print("Median #: {}".format(output[len(output) / 2])) print("Average #: {}".format(sum(output) / float(len(output))))
def little_swap(self): word_size = self.word_size words = self.words in_bytes = words_to_bytes(words, 4) shuffled_bytes = [ in_bytes[index] for index in (7, 12, 14, 9, 2, 1, 5, 15, 11, 6, 13, 0, 4, 8, 10, 3) ] self.words[:] = bytes_to_words(shuffled_bytes, 4)
def invert_little_swap(self): word_size = self.word_size words = self.words in_bytes = words_to_bytes(words, 4) shuffled_bytes = [ in_bytes[index] for index in (11, 5, 4, 15, 12, 6, 9, 0, 13, 3, 14, 8, 1, 10, 2, 7) ] self.words[:] = bytes_to_words(shuffled_bytes, 4)
def test_keyed_bit_transposition(): data = bytes_to_words(range(32), 4) _data = data[:] key = range(8) keyed_bit_transposition(data, key) invert_keyed_bit_transposition(data, key) assert data == _data
def test_encrypt_decrypt(): byte_size = 8 data = bytearray(16 * byte_size) key = bytearray(16 * byte_size) data[-1] = 1 rounds = 16 bit_size = byte_size * 8 size = (bit_size, ((2**bit_size) - 1), bit_size - (3 * byte_size)) data = bytes_to_words(data, byte_size) key = bytes_to_words(key, byte_size) plaintext = data[:] encrypt(data, key, rounds, size) print ''.join(bytes(integer_to_bytes(block, byte_size)) for block in data) print[byte for byte in data] decrypt(data, key, rounds, size) assert data == plaintext, (data, plaintext)
def visualize_permutation256(): data = bytearray(32) key = bytearray(32) key[0] = 1 while not raw_input( "Press any key + enter to end, or enter to continue: "): encrypt(data, key) print print '\n'.join( format(word, 'b').zfill(32) for word in bytes_to_words(data, 4))
def memory_hard_hash(data, rounds, hash_key=HASH_KEY, modulus=WORD_COUNT): output = [0 for count in range(STATE_SIZE)] hash_input = bytes_to_words( bytearray(pad_input(data, STATE_SIZE * WORD_BYTES)), WORD_BYTES) for round in range(rounds): for index, word in enumerate(hash_input): value = hash_key[(word + round + output[index % STATE_SIZE]) % modulus] addition_subroutine(output, value, modulus) return bytes(words_to_bytes(output, WORD_BYTES))
def compression_function(data, rounds=ROUNDS): output = [0, 0, 0, 0] for a, b, c, d in slide(bytes_to_words(data, 8), 4): a ^= output[0]; b ^= output[1]; c ^= output[2]; d ^= output[3]; for round in range(rounds): a, b, c, d = sbox(a, b, c, d) a, b, c, d = linear_layer(a, b, c, d) output[0] ^= a; output[1] ^= b; output[2] ^= c; output[3] ^= d; return bytes(words_to_bytes(output, 8))
def generate_key_stream(key, nonce, block_count): for index, byte in enumerate(key): nonce[index] ^= byte state = [block128 for block128 in slide(bytes_to_words(nonce, 4), 4)] output = [] while len(output) < block_count: for round in range(2): state = mix_blocks(*state) output.extend(state[0] + state[1] + state[2] + state[3]) return words_to_bytes(output, 4)
def compression_function(data): o0, o1, o2, o3 = 0, 1, 8 , 64 for a, b, c, d in slide(bytes_to_words(data, 4), 4): a, b, c, d = mix_words(a, b, c, d) s0, s1, s2, s3 = mix_words(a, 1, 8, 64) s0, s1, s2, s3 = mix_words(s0, b, s1, s3) s0, s1, s2, s3 = mix_words(s0, s1, c, s3) s0, s1, s2, s3 = mix_words(s0, s1, s2, d) o0 ^= s0; o1 ^= s1; o2 ^= s2; o3 ^= s3; return words_to_bytes((o0, o1, o2, o3), 4)
def encrypt8(byte, key): padding = bytearray(urandom(15)) permutation(padding) for padding_byte in padding: byte ^= padding_byte invert_permutation(padding) inputs = bytearray() inputs.append(byte) inputs.extend(padding) return words_to_bytes(bit_permutation128(bytes_to_words(inputs, 4), key), 4)
def encrypt(self, data, key, iv, size=(64, (2**64) - 1, 40)): word64 = lambda _data: bytes_to_words(bytearray(_data), 8) output = word64(data) key += "\x00" * (128 - len(key)) key = word64(key) assert len(key) == 16, len(key) iv += "\x00" * (128 - len(iv)) assert len(iv) == 128, len(iv) iv = word64(iv) assert len(iv) == 16, len(iv) encrypt(output, key, iv, size) return bytes(words_to_bytes(output, 8))
def decrypt64v2(data, key, output_type="bytes"): """ Decrypts data encrypted by encrypt64v2. Returns resulting plaintext message. Inverts the bit transposition, unmasks the data, and discards the padding. """ output = list(invert_bit_permutation128(bytes_to_words(data, 4), key)) output[0] ^= output[2] output[1] ^= output[3] if output_type == "bytes": return words_to_bytes(output, 4)[:8] else: if output_type != "words": raise ValueError("Invalid output_type '{}'".format(output_type)) return output
def encrypt(self, data, key, iv, size=(64, (2 ** 64) - 1, 40)): word64 = lambda _data: bytes_to_words(bytearray(_data), 8) output = word64(data) key += "\x00" * (128 - len(key)) key = word64(key) assert len(key) == 16, len(key) iv += "\x00" * (128 - len(iv)) assert len(iv) == 128, len(iv) iv = word64(iv) assert len(iv) == 16, len(iv) encrypt(output, key, iv, size) return bytes(words_to_bytes(output, 8))
def stream_cipher(data, key, nonce): state = bytes_to_words(bytearray(data), 4) nonce = bytes_to_words(bytearray(nonce), 4) key = bytes_to_words(bytearray(key), 4) blocks = len(state) - 4 assert blocks > 0 try: for index in range(blocks): nonce = list(keyed_permutation(*(nonce + key + [index + 1]))) state[(index * 4) + 0] ^= nonce[0] state[(index * 4) + 1] ^= nonce[1] state[(index * 4) + 2] ^= nonce[2] state[(index * 4) + 3] ^= nonce[3] except IndexError: pass _data = words_to_bytes(state, 4) try: data[:] = _data except (TypeError, ValueError): pass return _data
def compression_function(data): o0, o1, o2, o3 = 0, 1, 8, 64 for a, b, c, d in slide(bytes_to_words(data, 4), 4): a, b, c, d = mix_words(a, b, c, d) s0, s1, s2, s3 = mix_words(a, 1, 8, 64) s0, s1, s2, s3 = mix_words(s0, b, s1, s3) s0, s1, s2, s3 = mix_words(s0, s1, c, s3) s0, s1, s2, s3 = mix_words(s0, s1, s2, d) o0 ^= s0 o1 ^= s1 o2 ^= s2 o3 ^= s3 return words_to_bytes((o0, o1, o2, o3), 4)
def compression_function(data, rounds=ROUNDS): data = bytes_to_words(data, 8) # convert 8-bit words to 64-bit words a, b, c, d = (0, 0, 0, 0) counter = 1 for in0, in1, in2, in3 in slide(data, 4): # work on 4 64-bit words at a time (256 bit state) # print "Digesting: ", in0, in1, in2, in3 a, b, c, d = add_block(a, b, c, d, in0, in1, in2, in3, counter) assert counter <= INTEGER64_OVERFLOW for round in range(rounds): a, b, c, d = round_function(a, b, c, d) a, b, c, d = add_block(a, b, c, d, in0, in1, in2, in3, counter) counter += 1 a, b, c, d = round_function(a, b, c, d) return bytes(words_to_bytes((a, b, c, d), 8))
def stream_cipher(plaintext, key, seed, modulus=2**32, wordsize=32): # make state 2x key. 256 bit key, 512 bit state. 8 64-bit words, or 16-32 bit words # 4 instances of 4-32 bit words via SIMD would keep the latency low # 4 instances of 4-8 bit words via SIMD would be scalable? prng_key, seed_key = key seed = seed[:] xor_subroutine(seed, seed_key) key = itertools.cycle(prng_key) size = len(seed) state = bytes_to_words(bytearray(plaintext), 4) plaintext_size = len(state) plaintext_index = 0 break_flag = False # requires one round warmup for full diffusion # a + b, a + c, a + d # b + c, b + d # c + d assert size - 1 for index in range(size - 1): for index2 in range(index + 1, size): permutation(seed, index, index2, key, modulus, wordsize) while True: for index in range(size - 1): for index2 in range(index + 1, size): permutation(seed, index, index2, key, modulus, wordsize) state[plaintext_index] ^= (seed[index] + seed[index2]) % modulus plaintext_index += 1 if plaintext_index == plaintext_size: break_flag = True break if break_flag: break if break_flag: break _plaintext = words_to_bytes(state, 4) try: plaintext[:] = _plaintext except TypeError: pass return _plaintext
def stream_cipher(plaintext, key, seed, modulus=2 ** 32, wordsize=32): # make state 2x key. 256 bit key, 512 bit state. 8 64-bit words, or 16-32 bit words # 4 instances of 4-32 bit words via SIMD would keep the latency low # 4 instances of 4-8 bit words via SIMD would be scalable? prng_key, seed_key = key seed = seed[:] xor_subroutine(seed, seed_key) key = itertools.cycle(prng_key) size = len(seed) state = bytes_to_words(bytearray(plaintext), 4) plaintext_size = len(state) plaintext_index = 0 break_flag = False # requires one round warmup for full diffusion # a + b, a + c, a + d # b + c, b + d # c + d assert size - 1 for index in range(size - 1): for index2 in range(index + 1, size): permutation(seed, index, index2, key, modulus, wordsize) while True: for index in range(size - 1): for index2 in range(index + 1, size): permutation(seed, index, index2, key, modulus, wordsize) state[plaintext_index] ^= (seed[index] + seed[index2]) % modulus plaintext_index += 1 if plaintext_index == plaintext_size: break_flag = True break if break_flag: break if break_flag: break _plaintext = words_to_bytes(state, 4) try: plaintext[:] = _plaintext except TypeError: pass return _plaintext
def rotate_left1(self): # 0 1 2 3 # 4 5 6 7 # 8 9 10 11 #12 13 14 15 # 1 2 3 0 # 5 6 7 4 # 9 10 11 8 #13 14 15 12 word_size = self.word_size words = self.words in_bytes = words_to_bytes(words, 4) shuffled_bytes = [in_bytes[index] for index in (1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12)] self.words[:] = bytes_to_words(shuffled_bytes, 4)
def linear_layer(number): return bytes_to_words(bytearray(urandom(4 * 8)), 8) a, b, c, d = 0, 0, number, 0 a ^= branch32(1) for round in range(2): a ^= b; c ^= d; b ^= c; d ^= a; b = rotate_left(b, 1, 32) c = rotate_left(c, 2, 32) d = rotate_left(d, 3, 32) a ^= b; c ^= d; b ^= c; d ^= a; b = rotate_left(b, 4, 32) c = rotate_left(c, 8, 32) d = rotate_left(d, 12, 32) a ^= b; c ^= d; b ^= c; d ^= a; b = rotate_left(b, 8, 32) c = rotate_left(c, 12, 32) d = rotate_left(d, 16, 32) return a, b, c, d
def test_stream_cipher_diffusion(): wordsize = 1 size = generate_params_for_wordsize(wordsize) seed = bytes_to_words(bytearray(16 * wordsize), wordsize) key = seed[:] seed2 = key[:] seed3 = key[:] seed2[-2] = 1 seed3[-2] = 2 data = seed[:] data2 = data[:] data3 = data[:] stream_cipher(data, seed, key, size) stream_cipher(data2, seed2, key[:15] + [1], size) stream_cipher(data3, seed3, key[:14] + [1, 0], size) _bytes = lambda _data: words_to_bytes(_data, wordsize) bits = lambda _data: ''.join(format(byte, 'b').zfill(8) for byte in _bytes(_data)) print _bytes(data)
def rotate_down(self): # 0 1 2 3 # 4 5 6 7 # 8 9 10 11 #12 13 14 15 # 0 5 10 15 # 4 9 14 3 # 8 13 2 7 #12 1 6 11 # 0 13 10 7 # inverse # 4 1 14 11 # 8 5 2 15 #12 9 6 3 word_size = self.word_size words = self.words in_bytes = words_to_bytes(words, 4) shuffled_bytes = [in_bytes[index] for index in (0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11)] self.words[:] = bytes_to_words(shuffled_bytes, 4)
def rotate_left3(self): # 3 0 1 2 # 7 4 5 6 #11 8 9 10 #15 12 13 14 # 3 4 9 14 # 7 8 13 2 #11 12 1 6 #15 0 5 10 # 3 8 13 10 # 7 12 1 14 #11 0 5 2 #15 4 9 6 word_size = self.word_size words = self.words in_bytes = words_to_bytes(words, 4) shuffled_bytes = [in_bytes[index] for index in (3, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10, 15, 12, 13, 14)] self.words[:] = bytes_to_words(shuffled_bytes, 4)