Spaces:
Sleeping
Sleeping
| """ | |
| 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}") | |