Source code for cascada.primitives.skinny128

"""SKINNY-128-128.

TK1 version, where the tweakey size is equal to the block size.

A `WeakModel` is used to model the XOR and linear models of the S-box.
"""
from cascada.bitvector.core import Constant
from cascada.bitvector.operation import BvIdentity
from cascada.bitvector.secondaryop import LutOperation
from cascada.bitvector.ssa import RoundBasedFunction
from cascada.differential.difference import XorDiff
from cascada.differential.opmodel import get_weak_model as get_differential_weak_model
from cascada.linear.opmodel import get_weak_model as get_linear_weak_model
from cascada.primitives.blockcipher import Encryption, Cipher


from cascada.primitives.aes_like import LoggingMode, AESLikeFunction


default_num_rounds = 40


[docs]class SKINNYTweakeySchedule(RoundBasedFunction): """Key schedule of SKINNY-128-128.""" num_rounds = default_num_rounds input_widths = [8 for _ in range(4*4)] output_widths = [8 for _ in range(4*2 * default_num_rounds)] # 2 rows per round _min_num_rounds = 2
[docs] @classmethod def set_num_rounds(cls, new_num_rounds): cls.num_rounds = new_num_rounds cls.output_widths = [8 for _ in range(4*2 * new_num_rounds)]
@classmethod def permutation_Pt(cls, input_state): # TK1[i] ← TK1[PT[i]] Pt = [9, 15, 8, 13, 10, 14, 12, 11, 0, 1, 2, 3, 4, 5, 6, 7] assert len(input_state) == 16 == len(Pt) output_state = [] for i in range(16): output_state.append(input_state[Pt[i]]) return output_state
[docs] @classmethod def eval(cls, *master_key): tweakey_state = master_key list_tweakey_bytes = list(tweakey_state[:4*2]) # first 2 rows for i in range(cls.num_rounds - 1): # no LFSR in TK1 tweakey_state = cls.permutation_Pt(tweakey_state) list_tweakey_bytes.extend(tweakey_state[:4*2]) return list_tweakey_bytes
_lut = [ 101, 76, 106, 66, 75, 99, 67, 107, 85, 117, 90, 122, 83, 115, 91, 123, 53, 140, 58, 129, 137, 51, 128, 59, 149, 37, 152, 42, 144, 35, 153, 43, 229, 204, 232, 193, 201, 224, 192, 233, 213, 245, 216, 248, 208, 240, 217, 249, 165, 28, 168, 18, 27, 160, 19, 169, 5, 181, 10, 184, 3, 176, 11, 185, 50, 136, 60, 133, 141, 52, 132, 61, 145, 34, 156, 44, 148, 36, 157, 45, 98, 74, 108, 69, 77, 100, 68, 109, 82, 114, 92, 124, 84, 116, 93, 125, 161, 26, 172, 21, 29, 164, 20, 173, 2, 177, 12, 188, 4, 180, 13, 189, 225, 200, 236, 197, 205, 228, 196, 237, 209, 241, 220, 252, 212, 244, 221, 253, 54, 142, 56, 130, 139, 48, 131, 57, 150, 38, 154, 40, 147, 32, 155, 41, 102, 78, 104, 65, 73, 96, 64, 105, 86, 118, 88, 120, 80, 112, 89, 121, 166, 30, 170, 17, 25, 163, 16, 171, 6, 182, 8, 186, 0, 179, 9, 187, 230, 206, 234, 194, 203, 227, 195, 235, 214, 246, 218, 250, 211, 243, 219, 251, 49, 138, 62, 134, 143, 55, 135, 63, 146, 33, 158, 46, 151, 39, 159, 47, 97, 72, 110, 70, 79, 103, 71, 111, 81, 113, 94, 126, 87, 119, 95, 127, 162, 24, 174, 22, 31, 167, 23, 175, 1, 178, 14, 190, 7, 183, 15, 191, 226, 202, 238, 198, 207, 231, 199, 239, 210, 242, 222, 254, 215, 247, 223, 255 ]
[docs]class SboxLut(LutOperation): """The 8-bit S-box of SKINNY-128-128.""" lut = [Constant(x, 8) for x in _lut]
# weight 1 to count number of active S-boxes SboxLut.xor_model = get_differential_weak_model(SboxLut, XorDiff, 1) SboxLut.linear_model = get_linear_weak_model(SboxLut, 1)
[docs]class SKINNYEncryption(Encryption, AESLikeFunction): """Encryption function of SKINNY-128-128.""" num_rounds = default_num_rounds num_rows, num_columns = 4, 4 cell_width = 8 ignore_first_sub_cells = False sbox = SboxLut name_add_round_constants, name_sub_cells = "AddRoundTweakey", "SubCells" logging_mode = LoggingMode.Silent round_constants = [ 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, 0x09, 0x13, 0x26, 0x0c, 0x19, 0x32, 0x25, 0x0a, 0x15, 0x2a, 0x14, 0x28, 0x10, 0x20 ] @classmethod def shift_rows(cls, my_matrix_state): new_matrix_state = [row[:] for row in my_matrix_state] for r in range(cls.num_rows): offset = r # shift offset (0 for r=0, 1 for r=1, ...) for c in range(cls.num_columns): # rotate to the right new_matrix_state[r][c] = my_matrix_state[r][(c - offset) % cls.num_columns] if cls.logging_mode not in [LoggingMode.Silent, LoggingMode.RoundOutputs]: cls.log_msg(f"output of {getattr(cls, 'name_shift_rows', 'ShiftRows')}:") cls.log_activity_matrix_state(new_matrix_state) return new_matrix_state @classmethod def mix_columns(cls, my_matrix_state): new_matrix_state = [row[:] for row in my_matrix_state] for index_column in range(cls.num_columns): input_column = [my_matrix_state[r][index_column] for r in range(cls.num_rows)] # M = # 1 0 1 1 # 1 0 0 0 # 0 1 1 0 # 1 0 1 0 output_column = [ input_column[0] ^ input_column[2] ^ input_column[3], input_column[0], input_column[1] ^ input_column[2], input_column[0] ^ input_column[2] ] for r in range(cls.num_rows): new_matrix_state[r][index_column] = BvIdentity(output_column[r]) # copy with new name if cls.logging_mode not in [LoggingMode.Silent, LoggingMode.RoundOutputs]: cls.log_msg(f"output of {getattr(cls, 'name_mix_columns', 'MixColumns')}:") cls.log_activity_matrix_state(new_matrix_state) return new_matrix_state
[docs] @classmethod def eval(cls, *plaintext): matrix_state = cls.list2matrix(plaintext, column_order=False) if cls.logging_mode != LoggingMode.Silent: cls.log_msg("\nplaintext:") cls.log_activity_matrix_state(matrix_state) for r in range(cls.num_rounds): if r == 0 and cls.ignore_first_sub_cells is True: pass else: matrix_state = cls.sub_cells(matrix_state) # AddConstants round_constant = cls.round_constants[r] c0 = Constant(round_constant & 0xF, cls.cell_width) c1 = Constant(round_constant >> 4, cls.cell_width) c2 = Constant(0x2, cls.cell_width) matrix_state[0][0] ^= c0 matrix_state[1][0] ^= c1 matrix_state[2][0] ^= c2 if cls.logging_mode not in [LoggingMode.Silent, LoggingMode.RoundOutputs]: cls.log_msg("output of AddConstants:") cls.log_activity_matrix_state(matrix_state) current_round_key = list(cls.round_keys[r*(4*2): (r+1)*(4*2)]) # first 2 rows current_round_key += [Constant(0, cls.cell_width) for _ in range(4*2)] # last 2 rows matrix_round_keys = cls.list2matrix(current_round_key, column_order=False) matrix_state = cls.add_round_constant(matrix_state, matrix_round_keys) matrix_state = cls.shift_rows(matrix_state) matrix_state = cls.mix_columns(matrix_state) if cls.logging_mode != LoggingMode.Silent: cls.log_msg(f"output of round {r+1}:") cls.log_activity_matrix_state(matrix_state) cls.log_msg("", []) cls.add_round_outputs(*cls.matrix2list(matrix_state)) return tuple(cls.matrix2list(matrix_state, column_order=False))
[docs]class SKINNYCipher(Cipher): """The block cipher SKINNY-128-128.""" key_schedule = SKINNYTweakeySchedule encryption = SKINNYEncryption
[docs] @classmethod def set_num_rounds(cls, new_num_rounds): cls.key_schedule.set_num_rounds(new_num_rounds) cls.encryption.set_num_rounds(new_num_rounds)
@classmethod def test(cls): old_num_rounds = cls.num_rounds old_ignore = cls.encryption.ignore_first_sub_cells cls.encryption.ignore_first_sub_cells = False cls.set_num_rounds(default_num_rounds) key = [0x4f, 0x55, 0xcf, 0xb0, 0x52, 0x0c, 0xac, 0x52, 0xfd, 0x92, 0xc1, 0x5f, 0x37, 0x07, 0x3e, 0x93] plaintext = [0xf2, 0x0a, 0xdb, 0x0e, 0xb0, 0x8b, 0x64, 0x8a, 0x3b, 0x2e, 0xee, 0xd1, 0xf0, 0xad, 0xda, 0x14] assert list(cls(plaintext, key)) == [ 0x22, 0xff, 0x30, 0xd4, 0x98, 0xea, 0x62, 0xd7, 0xe4, 0x5b, 0x47, 0x6e, 0x33, 0x67, 0x5b, 0x74] cls.set_num_rounds(old_num_rounds) cls.encryption.ignore_first_sub_cells = old_ignore