Speck Cipher

Implementation of Speck cipher

# Reference: https://github.com/inmcm/Simon_Speck_Ciphers/blob/master/Python/simonspeckciphers/speck/speck.py
# For the sake of simplicity, we modified some functions of the original implementation

class SpeckCipher(object):
    """Speck Block Cipher Object"""
    # valid cipher configurations stored:
    # block_size:{key_size:number_rounds}
    __valid_setups = {32: {64: 22},
                      48: {72: 22, 96: 23},
                      64: {96: 26, 128: 27},
                      96: {96: 28, 144: 29},
                      128: {128: 32, 192: 33, 256: 34}}

    def __init__(self, key, key_size=128, block_size=128):

        # Setup block/word size
        self.possible_setups = self.__valid_setups[block_size]
        self.block_size = block_size
        self.word_size = self.block_size >> 1

        # Setup Number of Rounds and Key Size
        self.rounds = self.possible_setups[key_size]
        self.key_size = key_size

        # Create Properly Sized bit mask for truncating addition and left shift outputs
        self.mod_mask = (2 ** self.word_size) - 1

        # Mod mask for modular subtraction
        self.mod_mask_sub = (2 ** self.word_size)

        # Setup Circular Shift Parameters
        if self.block_size == 32:
            self.beta_shift = 2
            self.alpha_shift = 7
        else:
            self.beta_shift = 3
            self.alpha_shift = 8

        assert len(key) == (self.key_size // self.word_size)
        self.key = key

        # Pre-compile key schedule
        self.key_schedule()

    def key_schedule(self):
        self.round_keys = [self.key[-1]]
        l_schedule = [self.key[i] for i in reversed(range(self.key_size // self.word_size - 1))]

        for x in range(self.rounds - 1):
            new_l_k = self.encrypt_round(l_schedule[x], self.round_keys[x], x)
            l_schedule.append(new_l_k[0])
            self.round_keys.append(new_l_k[1])

        return self.round_keys

    def encrypt_round(self, x, y, k):
        """Complete One Round of Feistel Operation"""
        rs_x = ((x << (self.word_size - self.alpha_shift)) + (x >> self.alpha_shift)) & self.mod_mask

        add_sxy = (rs_x + y) & self.mod_mask

        new_x = k ^ add_sxy

        ls_y = ((y >> (self.word_size - self.beta_shift)) + (y << self.beta_shift)) & self.mod_mask

        new_y = new_x ^ ls_y

        return new_x, new_y

    def decrypt_round(self, x, y, k):
        """Complete One Round of Inverse Feistel Operation"""

        xor_xy = x ^ y

        new_y = ((xor_xy << (self.word_size - self.beta_shift)) + (xor_xy >> self.beta_shift)) & self.mod_mask

        xor_xk = x ^ k

        msub = ((xor_xk - new_y) + self.mod_mask_sub) % self.mod_mask_sub

        new_x = ((msub >> (self.word_size - self.alpha_shift)) + (msub << self.alpha_shift)) & self.mod_mask

        return new_x, new_y

    def encrypt(self, x, y):
        # Run Encryption Steps For Appropriate Number of Rounds
        for k in self.round_keys:
            x, y = self.encrypt_round(x, y, k)

        return x, y

    def decrypt(self, x, y):
        # Run Encryption Steps For Appropriate Number of Rounds
        for k in reversed(self.round_keys):
            x, y = self.decrypt_round(x, y, k)

        return x, y

Then, we can test the above implementation with some test vectors given by the authors of Speck

k3264 = [0x1918, 0x1110, 0x0908, 0x0100]
m3264 = [0x6574, 0x694c]
c3264 = SpeckCipher(k3264, 64, 32)
t3264 = c3264.encrypt(m3264[0], m3264[1])
assert t3264 == (0xa868, 0x42f2)

k128256 = [0x1f1e1d1c1b1a1918, 0x1716151413121110, 0x0f0e0d0c0b0a0908, 0x0706050403020100]
m128256 = [0x65736f6874206e49, 0x202e72656e6f6f70]
c128256 = SpeckCipher(k128256, 256, 128)
t128256 = c128256.encrypt(m128256[0], m128256[1])
assert t128256 == (0x4109010405c0f53e, 0x4eeeb48d9c188f43)

Build a circuit for Simon cipher

from circkit.bitwise import BitwiseCircuit

C = BitwiseCircuit(word_size=16)
key = C.add_inputs(n=4, format="k%d")
msg = C.add_inputs(n=2, format="m%d")
simon = SpeckCipher(key, key_size=64, block_size=32)
cpt = simon.encrypt(msg[0], msg[1])
C.add_output(cpt)

Then, we can evaluate the circuit to test its correctness.

k3264 = [0x1918, 0x1110, 0x0908, 0x0100]
m3264 = [0x6574, 0x694c]
inp = k3264 + m3264
out = C.evaluate(inp)
print(out)
[43112, 17138]

Note that we are using bitwise circuit and the truncation x & self.mod_mask is done automatically in the circuit. Therefore it is not necessary to have this truncation in the circuit. In the above Simon cipher, we still put it there because it is needed for the verification of the Simon implementation before building its circuit.