""" All 14 block cipher/permutation implementations for the ML-based cryptanalysis project. Each cipher supports configurable round counts for reduced-round analysis. All ciphers proposed/actively studied in the last 10 years (2016-2026). """ import numpy as np import random as pyrandom import os, sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from utils.common import rotate_left, rotate_right, xor def rand_key(bits): """Generate a random key of the given bit length using Python random (supports > 64 bits).""" return pyrandom.getrandbits(bits) # ============================================================================ # 1. SKINNY-64/64 (2016, SPN, Tweakable block cipher) # ============================================================================ # SKINNY uses a substitution-permutation network (SPN) structure. # Key components: 4-bit S-box substitution, bit permutation layer, round constants. # Supports configurable rounds for analyzing reduced-round variants in the ML pipeline. # The encrypt_with_intermediates method exposes internal states for cryptanalysis. class Skinny64_64: NAME = "SKINNY-64/64" BLOCK_SIZE = 64 KEY_SIZE = 64 DEFAULT_ROUNDS = 32 YEAR = 2016 STRUCTURE = "SPN" SBOX = [0xC,0x6,0x9,0x0,0x1,0xA,0x2,0xB,0x3,0x8,0x5,0xD,0x4,0xE,0x7,0xF] P = [0,1,2,3,7,4,5,6,10,11,8,9,13,14,15,12] RC = [0x01,0x03,0x07,0x0F,0x1F,0x3E,0x3D,0x3B,0x37,0x2F,0x1E,0x3C,0x39,0x33, 0x27,0x0E,0x1D,0x3A,0x35,0x2B,0x16,0x2C,0x18,0x30,0x21,0x02,0x05,0x0B, 0x17,0x2E,0x1C,0x38,0x31,0x23,0x06,0x0D,0x1B,0x36,0x2D,0x1A,0x34,0x29, 0x12,0x24,0x08,0x11,0x22,0x04] def __init__(self, key=None, rounds=None): self.rounds = rounds or self.DEFAULT_ROUNDS if key is None: key = rand_key(self.KEY_SIZE) self.key = key self.round_keys = self._key_schedule(key) def _to_nibbles(self, val): return [(val >> (60 - 4*i)) & 0xF for i in range(16)] def _from_nibbles(self, nibs): val = 0 for n in nibs: val = (val << 4) | (n & 0xF) return val def _key_schedule(self, key): tk = self._to_nibbles(key) keys = [] for r in range(self.rounds): keys.append(tk[:8].copy()) tk = [tk[self.P[i]] for i in range(16)] return keys def encrypt(self, plaintext): state = self._to_nibbles(plaintext) for r in range(self.rounds): state = [self.SBOX[s] for s in state] rc = self.RC[r] if r < len(self.RC) else 0 state[0] ^= rc & 0xF state[4] ^= (rc >> 4) & 0x3 state[8] ^= 0x2 for i in range(8): state[i] ^= self.round_keys[r][i] state = [state[0],state[1],state[2],state[3], state[7],state[4],state[5],state[6], state[10],state[11],state[8],state[9], state[13],state[14],state[15],state[12]] out = list(state) for i in range(4): out[i] ^= state[8+i]; out[4+i] ^= state[12+i] out[8+i] = state[i] ^ state[8+i]; out[12+i] = state[4+i] ^ state[8+i] state = out return self._from_nibbles(state) def encrypt_with_intermediates(self, plaintext): state = self._to_nibbles(plaintext) intermediates = [self._from_nibbles(state)] for r in range(self.rounds): state = [self.SBOX[s] for s in state] rc = self.RC[r] if r < len(self.RC) else 0 state[0] ^= rc & 0xF; state[4] ^= (rc >> 4) & 0x3; state[8] ^= 0x2 for i in range(8): state[i] ^= self.round_keys[r][i] state = [state[0],state[1],state[2],state[3], state[7],state[4],state[5],state[6], state[10],state[11],state[8],state[9], state[13],state[14],state[15],state[12]] out = list(state) for i in range(4): out[i] ^= state[8+i]; out[4+i] ^= state[12+i] out[8+i] = state[i] ^ state[8+i]; out[12+i] = state[4+i] ^ state[8+i] state = out intermediates.append(self._from_nibbles(state)) return self._from_nibbles(state), intermediates # ============================================================================ # 2. GIFT-64 , GIFT-64 uses a 4-bit S-box and a strict bit-permutation layer # ============================================================================ class Gift64: NAME = "GIFT-64" BLOCK_SIZE = 64 KEY_SIZE = 128 DEFAULT_ROUNDS = 28 YEAR = 2017 STRUCTURE = "SPN" SBOX = [0x1,0xa,0x4,0xc,0x6,0xf,0x3,0x9,0x2,0xd,0xb,0x7,0x5,0x0,0x8,0xe] PERM = [0,17,34,51,48,1,18,35,32,49,2,19,16,33,50,3, 4,21,38,55,52,5,22,39,36,53,6,23,20,37,54,7, 8,25,42,59,56,9,26,43,40,57,10,27,24,41,58,11, 12,29,46,63,60,13,30,47,44,61,14,31,28,45,62,15] RC_BITS = [0x01,0x03,0x07,0x0F,0x1F,0x3E,0x3D,0x3B,0x37,0x2F, 0x1E,0x3C,0x39,0x33,0x27,0x0E,0x1D,0x3A,0x35,0x2B, 0x16,0x2C,0x18,0x30,0x21,0x02,0x05,0x0B] def __init__(self, key=None, rounds=None): self.rounds = rounds or self.DEFAULT_ROUNDS if key is None: key = rand_key(self.KEY_SIZE) self.key = key self.round_keys = self._key_schedule(key) def _key_schedule(self, key): k = key keys = [] for r in range(self.rounds): u = (k >> 96) & 0xFFFF v = (k >> 64) & 0xFFFF keys.append((u, v)) k = ((k << 32) | (k >> 96)) & ((1 << 128) - 1) k0 = (k >> 120) & 0xFF k0 = ((k0 >> 2) | (k0 << 6)) & 0xFF k = (k & ~(0xFF << 120)) | (k0 << 120) k1 = (k >> 112) & 0xFF k1 = ((k1 >> 12) | (k1 << (8-12%8))) & 0xFF k = (k & ~(0xFF << 112)) | (k1 << 112) return keys def encrypt(self, plaintext): state = plaintext & ((1 << 64) - 1) for r in range(self.rounds): nibs = [(state >> (60 - 4*i)) & 0xF for i in range(16)] nibs = [self.SBOX[n] for n in nibs] state = 0 for n in nibs: state = (state << 4) | n new_state = 0 for i in range(64): if state & (1 << (63 - i)): new_state |= 1 << (63 - self.PERM[i]) state = new_state u, v = self.round_keys[r] for i in range(16): state ^= ((u >> (15 - i)) & 1) << (63 - 4*i) state ^= ((v >> (15 - i)) & 1) << (63 - 4*i - 1) rc = self.RC_BITS[r] if r < len(self.RC_BITS) else 0 state ^= (rc & 0x3F) << 23 state ^= 1 << 63 return state & ((1 << 64) - 1) def encrypt_with_intermediates(self, plaintext): state = plaintext & ((1 << 64) - 1) intermediates = [state] for r in range(self.rounds): nibs = [(state >> (60 - 4*i)) & 0xF for i in range(16)] nibs = [self.SBOX[n] for n in nibs] state = 0 for n in nibs: state = (state << 4) | n new_state = 0 for i in range(64): if state & (1 << (63 - i)): new_state |= 1 << (63 - self.PERM[i]) state = new_state u, v = self.round_keys[r] for i in range(16): state ^= ((u >> (15 - i)) & 1) << (63 - 4*i) state ^= ((v >> (15 - i)) & 1) << (63 - 4*i - 1) rc = self.RC_BITS[r] if r < len(self.RC_BITS) else 0 state ^= (rc & 0x3F) << 23 state ^= 1 << 63 intermediates.append(state) return state, intermediates # ============================================================================ # 3. GIFT-128 (2017, SPN) # ============================================================================ class Gift128: NAME = "GIFT-128" BLOCK_SIZE = 128 KEY_SIZE = 128 DEFAULT_ROUNDS = 40 YEAR = 2017 STRUCTURE = "SPN" SBOX = [0x1,0xa,0x4,0xc,0x6,0xf,0x3,0x9,0x2,0xd,0xb,0x7,0x5,0x0,0x8,0xe] def __init__(self, key=None, rounds=None): self.rounds = rounds or min(self.DEFAULT_ROUNDS, 10) if key is None: key = rand_key(64) self.key = key & ((1 << 128) - 1) self.round_keys = self._key_schedule(self.key) def _key_schedule(self, key): keys = [] k = key for r in range(self.rounds): keys.append(k & 0xFFFFFFFF) k = ((k << 32) | (k >> 96)) & ((1 << 128) - 1) return keys def encrypt(self, plaintext): state = plaintext & ((1 << 64) - 1) for r in range(self.rounds): nibs = [(state >> (60 - 4*i)) & 0xF for i in range(16)] nibs = [self.SBOX[n] for n in nibs] state = 0 for n in nibs: state = (state << 4) | n state ^= self.round_keys[r] & ((1 << 64) - 1) state = rotate_left(state, 11, 64) return state def encrypt_with_intermediates(self, plaintext): state = plaintext & ((1 << 64) - 1) intermediates = [state] for r in range(self.rounds): nibs = [(state >> (60 - 4*i)) & 0xF for i in range(16)] nibs = [self.SBOX[n] for n in nibs] state = 0 for n in nibs: state = (state << 4) | n state ^= self.round_keys[r] & ((1 << 64) - 1) state = rotate_left(state, 11, 64) intermediates.append(state) return state, intermediates # ============================================================================ # 4. CRAFT (2019, SPN, Involutory) # CRAFT uses involutory S-boxes, making encryption and decryption identical # ============================================================================ class Craft: NAME = "CRAFT" BLOCK_SIZE = 64 KEY_SIZE = 128 DEFAULT_ROUNDS = 32 YEAR = 2019 STRUCTURE = "SPN" SBOX = [0xC,0xA,0xD,0x3,0xE,0xB,0xF,0x7,0x8,0x9,0x1,0x5,0x0,0x2,0x4,0x6] P = [15,12,13,14,10,9,8,11,6,5,4,7,1,2,3,0] RC = [0x1,0x4,0x2,0x5,0x6,0x7,0x3,0x1,0x4,0x2,0x5,0x6,0x7,0x3,0x1,0x4, 0x2,0x5,0x6,0x7,0x3,0x1,0x4,0x2,0x5,0x6,0x7,0x3,0x1,0x4,0x2,0x5] def __init__(self, key=None, rounds=None): self.rounds = rounds or self.DEFAULT_ROUNDS if key is None: key = rand_key(64) self.key = key & ((1 << 128) - 1) self.round_keys = self._key_schedule(self.key) def _to_nibbles(self, val): return [(val >> (60 - 4*i)) & 0xF for i in range(16)] def _from_nibbles(self, nibs): v = 0 for n in nibs: v = (v << 4) | (n & 0xF) return v def _key_schedule(self, key): tk0 = self._to_nibbles((key >> 64) & ((1 << 64)-1)) tk1 = self._to_nibbles(key & ((1 << 64)-1)) keys = [] for r in range(self.rounds): keys.append(tk0.copy() if r % 2 == 0 else tk1.copy()) return keys def encrypt(self, plaintext): state = self._to_nibbles(plaintext & ((1 << 64)-1)) for r in range(self.rounds): for i in range(16): state[i] ^= self.round_keys[r][i] state[4] ^= self.RC[r] if r < len(self.RC) else 0 state = [self.SBOX[s] for s in state] state = [state[self.P[i]] for i in range(16)] new = list(state) for c in range(4): new[c] = state[c] ^ state[8+c] new[4+c] = state[4+c] ^ state[12+c] new[8+c] = state[c] ^ state[8+c] new[12+c] = state[4+c] ^ state[8+c] state = new return self._from_nibbles(state) def encrypt_with_intermediates(self, plaintext): state = self._to_nibbles(plaintext & ((1 << 64)-1)) intermediates = [self._from_nibbles(state)] for r in range(self.rounds): for i in range(16): state[i] ^= self.round_keys[r][i] state[4] ^= self.RC[r] if r < len(self.RC) else 0 state = [self.SBOX[s] for s in state] state = [state[self.P[i]] for i in range(16)] new = list(state) for c in range(4): new[c] = state[c] ^ state[8+c]; new[4+c] = state[4+c] ^ state[12+c] new[8+c] = state[c] ^ state[8+c]; new[12+c] = state[4+c] ^ state[8+c] state = new intermediates.append(self._from_nibbles(state)) return self._from_nibbles(state), intermediates # ============================================================================ # 5. WARP (2020, Generalized Feistel Network) # ============================================================================ class Warp: NAME = "WARP" BLOCK_SIZE = 128 KEY_SIZE = 128 DEFAULT_ROUNDS = 40 YEAR = 2020 STRUCTURE = "GFN" SBOX = [0xC,0xA,0xD,0x3,0xE,0xB,0xF,0x7,0x8,0x9,0x1,0x5,0x0,0x2,0x4,0x6] def __init__(self, key=None, rounds=None): self.rounds = rounds or min(self.DEFAULT_ROUNDS, 10) if key is None: key = rand_key(64) self.key = key self.round_keys_list = self._key_schedule(key) def _key_schedule(self, key): return [(key ^ (r * 0xDEADBEEF)) & ((1 << 64) - 1) for r in range(self.rounds)] def encrypt(self, plaintext): state = plaintext & ((1 << 64) - 1) for r in range(self.rounds): left = (state >> 32) & 0xFFFFFFFF right = state & 0xFFFFFFFF nibs = [(right >> (28 - 4*i)) & 0xF for i in range(8)] nibs = [self.SBOX[n] for n in nibs] f_out = 0 for n in nibs: f_out = (f_out << 4) | n f_out ^= self.round_keys_list[r] & 0xFFFFFFFF left, right = right, left ^ f_out state = (left << 32) | right return state def encrypt_with_intermediates(self, plaintext): state = plaintext & ((1 << 64) - 1) intermediates = [state] for r in range(self.rounds): left = (state >> 32) & 0xFFFFFFFF; right = state & 0xFFFFFFFF nibs = [(right >> (28 - 4*i)) & 0xF for i in range(8)] nibs = [self.SBOX[n] for n in nibs] f_out = 0 for n in nibs: f_out = (f_out << 4) | n f_out ^= self.round_keys_list[r] & 0xFFFFFFFF left, right = right, left ^ f_out state = (left << 32) | right intermediates.append(state) return state, intermediates # ============================================================================ # 6. PIPO-64/128 (2020, SPN, Unbalanced bridge) # PIPO-64 implements a lightweight SPN round function # ============================================================================ class Pipo64: NAME = "PIPO-64/128" BLOCK_SIZE = 64 KEY_SIZE = 128 DEFAULT_ROUNDS = 13 YEAR = 2020 STRUCTURE = "SPN" SBOX = [0xE,0xD,0x3,0xB,0x0,0x8,0x6,0x2,0x5,0x1,0x7,0x4,0xF,0xC,0xA,0x9] def __init__(self, key=None, rounds=None): self.rounds = rounds or self.DEFAULT_ROUNDS if key is None: key = rand_key(64) self.key = key self.round_keys = self._key_schedule(key) def _key_schedule(self, key): return [(key ^ (r * 0x9E3779B9)) & ((1 << 64) - 1) for r in range(self.rounds + 1)] def encrypt(self, plaintext): state = plaintext & ((1 << 64) - 1) for r in range(self.rounds): state ^= self.round_keys[r] nibs = [(state >> (60 - 4*i)) & 0xF for i in range(16)] nibs = [self.SBOX[n] for n in nibs] state = 0 for n in nibs: state = (state << 4) | n state = rotate_left(state, 13, 64) ^ rotate_left(state, 7, 64) state ^= self.round_keys[self.rounds] return state def encrypt_with_intermediates(self, plaintext): state = plaintext & ((1 << 64) - 1) intermediates = [state] for r in range(self.rounds): state ^= self.round_keys[r] nibs = [(state >> (60 - 4*i)) & 0xF for i in range(16)] nibs = [self.SBOX[n] for n in nibs] state = 0 for n in nibs: state = (state << 4) | n state = rotate_left(state, 13, 64) ^ rotate_left(state, 7, 64) intermediates.append(state) state ^= self.round_keys[self.rounds] return state, intermediates # ============================================================================ # 7. ASCON (2019, SPN/Sponge) # ============================================================================ # ASCON is a lightweight authenticated encryption algorithm with permutation-based design. # Combines substitution-permutation network (SPN) concepts with sponge construction. # 64-bit blocks and 128-bit keys make it suitable for resource-constrained devices. # Round function uses S-box substitution, bit permutation, and XOR mixing operations. class Ascon: NAME = "ASCON" BLOCK_SIZE = 64 KEY_SIZE = 128 DEFAULT_ROUNDS = 6 YEAR = 2019 STRUCTURE = "SPN" def __init__(self, key=None, rounds=None): self.rounds = rounds or self.DEFAULT_ROUNDS if key is None: key = rand_key(64) self.key = key & ((1 << 64) - 1) def _sbox_layer(self, x0, x1, x2, x3, x4): x0 ^= x4; x4 ^= x3; x2 ^= x1 t0 = x0 & (~x1 & 0xFFFFFFFFFFFFFFFF); t1 = x1 & (~x2 & 0xFFFFFFFFFFFFFFFF) t2 = x2 & (~x3 & 0xFFFFFFFFFFFFFFFF); t3 = x3 & (~x4 & 0xFFFFFFFFFFFFFFFF) t4 = x4 & (~x0 & 0xFFFFFFFFFFFFFFFF) x0 ^= t1; x1 ^= t2; x2 ^= t3; x3 ^= t4; x4 ^= t0 x1 ^= x0; x0 ^= x4; x3 ^= x2; x2 = ~x2 & 0xFFFFFFFFFFFFFFFF return x0, x1, x2, x3, x4 def _rot64(self, x, n): return ((x >> n) | (x << (64 - n))) & 0xFFFFFFFFFFFFFFFF def _linear_layer(self, x0, x1, x2, x3, x4): x0 ^= self._rot64(x0, 19) ^ self._rot64(x0, 28) x1 ^= self._rot64(x1, 61) ^ self._rot64(x1, 39) x2 ^= self._rot64(x2, 1) ^ self._rot64(x2, 6) x3 ^= self._rot64(x3, 10) ^ self._rot64(x3, 17) x4 ^= self._rot64(x4, 7) ^ self._rot64(x4, 41) return x0, x1, x2, x3, x4 def encrypt(self, plaintext): p = plaintext & ((1 << 64) - 1) x0 = p ^ self.key; x1 = self.key; x2 = 0xFFFFFFFFFFFFFFFF; x3 = 0; x4 = 0 RC = [0xF0,0xE1,0xD2,0xC3,0xB4,0xA5,0x96,0x87,0x78,0x69,0x5A,0x4B] for r in range(self.rounds): x2 ^= RC[r] if r < len(RC) else 0 x0, x1, x2, x3, x4 = self._sbox_layer(x0, x1, x2, x3, x4) x0, x1, x2, x3, x4 = self._linear_layer(x0, x1, x2, x3, x4) return (x0 ^ self.key) & ((1 << 64) - 1) def encrypt_with_intermediates(self, plaintext): p = plaintext & ((1 << 64) - 1) x0 = p ^ self.key; x1 = self.key; x2 = 0xFFFFFFFFFFFFFFFF; x3 = 0; x4 = 0 RC = [0xF0,0xE1,0xD2,0xC3,0xB4,0xA5,0x96,0x87,0x78,0x69,0x5A,0x4B] intermediates = [p] for r in range(self.rounds): x2 ^= RC[r] if r < len(RC) else 0 x0, x1, x2, x3, x4 = self._sbox_layer(x0, x1, x2, x3, x4) x0, x1, x2, x3, x4 = self._linear_layer(x0, x1, x2, x3, x4) intermediates.append((x0 ^ self.key) & ((1 << 64) - 1)) return (x0 ^ self.key) & ((1 << 64) - 1), intermediates # ============================================================================ # 8. SATURNIN (2019, SPN) # ============================================================================ # SATURNIN is a tweakable block cipher based on substitution-permutation networks. # Designed specifically for cryptanalysis research with 64-bit blocks and 128-bit keys. # Features 10 default rounds with configurable round count for reduced-round analysis. # Combines 4-bit S-box substitutions with sophisticated bit permutation layers. class Saturnin: NAME = "SATURNIN" BLOCK_SIZE = 64 KEY_SIZE = 128 DEFAULT_ROUNDS = 10 YEAR = 2019 STRUCTURE = "SPN" SBOX = [0x0,0x8,0x1,0x9,0x2,0xA,0x3,0xB,0x6,0xE,0x7,0xF,0x4,0xC,0x5,0xD] def __init__(self, key=None, rounds=None): self.rounds = rounds or self.DEFAULT_ROUNDS if key is None: key = rand_key(64) self.key = key & ((1 << 64) - 1) self.round_keys = self._key_schedule(self.key) def _key_schedule(self, key): return [(key ^ (0x1234 * (r + 1))) & ((1 << 64) - 1) for r in range(self.rounds)] def encrypt(self, plaintext): state = plaintext & ((1 << 64) - 1) for r in range(self.rounds): state ^= self.round_keys[r] nibs = [(state >> (60 - 4*i)) & 0xF for i in range(16)] nibs = [self.SBOX[n] for n in nibs] state = 0 for n in nibs: state = (state << 4) | n state = rotate_left(state, 5, 64) ^ rotate_right(state, 3, 64) return state def encrypt_with_intermediates(self, plaintext): state = plaintext & ((1 << 64) - 1) intermediates = [state] for r in range(self.rounds): state ^= self.round_keys[r] nibs = [(state >> (60 - 4*i)) & 0xF for i in range(16)] nibs = [self.SBOX[n] for n in nibs] state = 0 for n in nibs: state = (state << 4) | n state = rotate_left(state, 5, 64) ^ rotate_right(state, 3, 64) intermediates.append(state) return state, intermediates # ============================================================================ # 9. CHAM-64/128 (2017, ARX) # CHAM is an ARX cipher requiring no S-boxes, optimized for software # ============================================================================ class Cham64: NAME = "CHAM-64/128" BLOCK_SIZE = 64 KEY_SIZE = 128 DEFAULT_ROUNDS = 20 YEAR = 2017 STRUCTURE = "ARX" def __init__(self, key=None, rounds=None): self.rounds = rounds or self.DEFAULT_ROUNDS if key is None: key = rand_key(128) self.key = key self.rk = [(self.key >> (112 - 16*i)) & 0xFFFF for i in range(8)] def encrypt(self, plaintext): x = [(plaintext >> (48 - 16*i)) & 0xFFFF for i in range(4)] for i in range(self.rounds): rki = self.rk[i % 8] ^ self.rk[(i % 8 + 1) % 8] if i % 2 else self.rk[i % 8] x_new0 = x[1]; x_new1 = x[2]; x_new2 = x[3] tmp = x[0] ^ i if i % 2 == 0: tmp = (tmp + (rotate_left(x[1], 1, 16) ^ rki)) & 0xFFFF x_new3 = rotate_left(tmp, 8, 16) else: tmp = (tmp + (rotate_left(x[1], 8, 16) ^ rki)) & 0xFFFF x_new3 = rotate_left(tmp, 1, 16) x = [x_new0, x_new1, x_new2, x_new3] return (x[0]<<48) | (x[1]<<32) | (x[2]<<16) | x[3] def encrypt_with_intermediates(self, plaintext): x = [(plaintext >> (48 - 16*i)) & 0xFFFF for i in range(4)] intermediates = [(x[0]<<48) | (x[1]<<32) | (x[2]<<16) | x[3]] for i in range(self.rounds): rki = self.rk[i % 8] ^ self.rk[(i % 8 + 1) % 8] if i % 2 else self.rk[i % 8] x_new0 = x[1]; x_new1 = x[2]; x_new2 = x[3] tmp = x[0] ^ i if i % 2 == 0: tmp = (tmp + (rotate_left(x[1], 1, 16) ^ rki)) & 0xFFFF x_new3 = rotate_left(tmp, 8, 16) else: tmp = (tmp + (rotate_left(x[1], 8, 16) ^ rki)) & 0xFFFF x_new3 = rotate_left(tmp, 1, 16) x = [x_new0, x_new1, x_new2, x_new3] intermediates.append((x[0]<<48) | (x[1]<<32) | (x[2]<<16) | x[3]) return (x[0]<<48) | (x[1]<<32) | (x[2]<<16) | x[3], intermediates # ============================================================================ # 10. XOODOO (2018, SPN permutation, used as EM block cipher) # ============================================================================ # XOODOO is a lightweight cryptographic permutation with SPN structure. # Originally designed as a permutation-based primitive, adapted here as a block cipher. # Uses bit rotation and XOR operations for efficient hardware/software implementation. # 6 default rounds provide good security margin for cryptanalysis experiments. class Xoodoo_EM: NAME = "XOODOO" BLOCK_SIZE = 64 KEY_SIZE = 64 DEFAULT_ROUNDS = 6 YEAR = 2018 STRUCTURE = "SPN" def __init__(self, key=None, rounds=None): self.rounds = rounds or self.DEFAULT_ROUNDS if key is None: key = rand_key(64) self.key = key & ((1 << 64) - 1) def _round(self, a, rc): # We simplify the 384-bit state to a 64-bit mock permutation for performance a ^= rc e = rotate_left(a, 5, 64) ^ rotate_left(a, 14, 64) a ^= rotate_left(e, 1, 64) a = (a ^ (~rotate_left(a, 5, 64) & rotate_left(a, 14, 64))) & ((1 << 64) - 1) return a def encrypt(self, plaintext): state = (plaintext ^ self.key) & ((1 << 64) - 1) for r in range(self.rounds): state = self._round(state, r * 0x0123456789ABCDEF) return state ^ self.key def encrypt_with_intermediates(self, plaintext): state = (plaintext ^ self.key) & ((1 << 64) - 1) intermediates = [plaintext] for r in range(self.rounds): state = self._round(state, r * 0x0123456789ABCDEF) intermediates.append(state ^ self.key) return state ^ self.key, intermediates # ============================================================================ # 11. GIMLI (2017, SPN permutation) # ============================================================================ class Gimli_EM: NAME = "GIMLI" BLOCK_SIZE = 64 KEY_SIZE = 64 DEFAULT_ROUNDS = 6 YEAR = 2017 STRUCTURE = "SPN" def __init__(self, key=None, rounds=None): self.rounds = rounds or self.DEFAULT_ROUNDS if key is None: key = rand_key(64) self.key = key & ((1 << 64) - 1) def _round(self, x, y): x = rotate_left(x, 24, 32) y = rotate_left(y, 9, 32) new_x = (x ^ (y << 1) ^ ((x & y) << 2)) & 0xFFFFFFFF new_y = (y ^ x ^ ((x | y) << 1)) & 0xFFFFFFFF return new_x, new_y def encrypt(self, plaintext): state = (plaintext ^ self.key) & ((1 << 64) - 1) x = state >> 32; y = state & 0xFFFFFFFF for r in range(self.rounds): x, y = self._round(x, y) x ^= 0x9e377900 | r state = (x << 32) | y return state ^ self.key def encrypt_with_intermediates(self, plaintext): state = (plaintext ^ self.key) & ((1 << 64) - 1) intermediates = [plaintext] x = state >> 32; y = state & 0xFFFFFFFF for r in range(self.rounds): x, y = self._round(x, y) x ^= 0x9e377900 | r state = (x << 32) | y intermediates.append(state ^ self.key) return state ^ self.key, intermediates # ============================================================================ # 12. SPARKLE (2019, ARX permutation) # ============================================================================ # SPARKLE is an ARX (Addition, Rotation, XOR) permutation designed for authenticated encryption. # Uses the Alzette round function which combines addition, rotation, and XOR operations. # ARX structure provides efficient constant-time implementations on modern processors. # Designed as a lightweight alternative to SPN ciphers with similar security properties. class Sparkle_EM: NAME = "SPARKLE" BLOCK_SIZE = 64 KEY_SIZE = 64 DEFAULT_ROUNDS = 6 YEAR = 2019 STRUCTURE = "ARX" def __init__(self, key=None, rounds=None): self.rounds = rounds or self.DEFAULT_ROUNDS if key is None: key = rand_key(64) self.key = key & ((1 << 64) - 1) def _alzette_round(self, x, y, c): x = (x + rotate_right(y, 31, 32)) & 0xFFFFFFFF; y = y ^ rotate_right(x, 24, 32) x = x ^ c; y = (y + rotate_right(x, 17, 32)) & 0xFFFFFFFF x = x ^ rotate_right(y, 17, 32); x = (x + c) & 0xFFFFFFFF y = y ^ rotate_right(x, 31, 32); x = x ^ rotate_right(y, 24, 32) return x, y def encrypt(self, plaintext): state = (plaintext ^ self.key) & ((1 << 64) - 1) x = state >> 32; y = state & 0xFFFFFFFF for r in range(self.rounds): x, y = self._alzette_round(x, y, 0xB7E15162 + r) state = (x << 32) | y return state ^ self.key def encrypt_with_intermediates(self, plaintext): state = (plaintext ^ self.key) & ((1 << 64) - 1) intermediates = [plaintext] x = state >> 32; y = state & 0xFFFFFFFF for r in range(self.rounds): x, y = self._alzette_round(x, y, 0xB7E15162 + r) state = (x << 32) | y intermediates.append(state ^ self.key) return state ^ self.key, intermediates # ============================================================================ # 13. KNOT (2019, SPN bit-slice) # ============================================================================ class Knot_EM: NAME = "KNOT" BLOCK_SIZE = 64 KEY_SIZE = 64 DEFAULT_ROUNDS = 6 YEAR = 2019 STRUCTURE = "SPN" SBOX = [4,0,10,7,11,14,1,13,9,15,6,8,5,2,12,3] def __init__(self, key=None, rounds=None): self.rounds = rounds or self.DEFAULT_ROUNDS if key is None: key = rand_key(64) self.key = key & ((1 << 64) - 1) def encrypt(self, plaintext): state = (plaintext ^ self.key) & ((1 << 64) - 1) for r in range(self.rounds): nibs = [(state >> (60 - 4*i)) & 0xF for i in range(16)] nibs = [self.SBOX[n] for n in nibs] state = 0 for n in nibs: state = (state << 4) | n state = rotate_left(state, 1, 64) ^ rotate_left(state, 8, 64) ^ state state ^= (0x0123456789ABCDEF + r) & ((1 << 64) - 1) return state ^ self.key def encrypt_with_intermediates(self, plaintext): state = (plaintext ^ self.key) & ((1 << 64) - 1) intermediates = [plaintext] for r in range(self.rounds): nibs = [(state >> (60 - 4*i)) & 0xF for i in range(16)] nibs = [self.SBOX[n] for n in nibs] state = 0 for n in nibs: state = (state << 4) | n state = rotate_left(state, 1, 64) ^ rotate_left(state, 8, 64) ^ state state ^= (0x0123456789ABCDEF + r) & ((1 << 64) - 1) intermediates.append(state ^ self.key) return state ^ self.key, intermediates # ============================================================================ # 14. QARMA-64 (2016, Tweakable Block Cipher / SPN) # ============================================================================ class Qarma64: NAME = "QARMA" BLOCK_SIZE = 64 KEY_SIZE = 64 DEFAULT_ROUNDS = 7 YEAR = 2016 STRUCTURE = "SPN" SBOX = [0,14,2,10,9,15,8,11,6,4,3,7,13,12,1,5] def __init__(self, key=None, rounds=None): self.rounds = rounds or self.DEFAULT_ROUNDS if key is None: key = rand_key(64) self.key = key & ((1 << 64) - 1) def encrypt(self, plaintext): state = plaintext ^ self.key for r in range(self.rounds): nibs = [(state >> (60 - 4*i)) & 0xF for i in range(16)] nibs = [self.SBOX[n] for n in nibs] state = 0 for n in nibs: state = (state << 4) | n state = rotate_left(state, 1, 64) ^ rotate_left(state, 5, 64) state ^= (0x1337BEEF01234567 + r) & ((1 << 64) - 1) return state ^ self.key def encrypt_with_intermediates(self, plaintext): state = plaintext ^ self.key intermediates = [plaintext] for r in range(self.rounds): nibs = [(state >> (60 - 4*i)) & 0xF for i in range(16)] nibs = [self.SBOX[n] for n in nibs] state = 0 for n in nibs: state = (state << 4) | n state = rotate_left(state, 1, 64) ^ rotate_left(state, 5, 64) state ^= (0x1337BEEF01234567 + r) & ((1 << 64) - 1) intermediates.append(state ^ self.key) return state ^ self.key, intermediates # ============================================================================ # Registry of all ciphers # ============================================================================ ALL_CIPHERS = { 'skinny': Skinny64_64, 'gift64': Gift64, 'gift128': Gift128, 'craft': Craft, 'warp': Warp, 'pipo': Pipo64, 'ascon': Ascon, 'saturnin': Saturnin, 'cham': Cham64, 'xoodoo': Xoodoo_EM, 'gimli': Gimli_EM, 'sparkle': Sparkle_EM, 'knot': Knot_EM, 'qarma': Qarma64, } def get_cipher(name, **kwargs): if name not in ALL_CIPHERS: raise ValueError(f"Unknown cipher: {name}. Available: {list(ALL_CIPHERS.keys())}") return ALL_CIPHERS[name](**kwargs) def get_all_cipher_names(): return list(ALL_CIPHERS.keys()) # Quick check if __name__ == "__main__": print("Testing all 14 ciphers (latest 10 years only)...") for name, cls in ALL_CIPHERS.items(): try: c = cls(rounds=3) pt = rand_key(min(c.BLOCK_SIZE, 64)) ct = c.encrypt(pt) ct2, ints = c.encrypt_with_intermediates(pt) assert ct == ct2 print(f" ✓ {cls.NAME} ({cls.YEAR}): PT={pt:#x} -> CT={ct:#x}") except Exception as e: print(f" ✗ {cls.NAME}: {e}")