Adding a primitive
To implement a new primitive, the easiest way is to take a similar primitive
already implemented and adapt it to the new primitive
(see also BvFunction
and blockcipher
).
If the new primitive contains a bit-vector operation
with no associated property model (see abstractproperty.opmodel.OpModel
),
you need to implement the property model (as in simon
and simon_rf
)
or use a WeakModel
, BranchNumberModel
or WDTModel
(as in aes
or skinny64
).
We will now describe step-by-step how to add a new primitive from scratch using Speck32/64 as example.
Description of Speck
Speck is a family of lightweight block ciphers. A member of the family is denoted by Speck \(2n/m·n\), where the block size is \(2n\) bits for \(n\in \{16,24,32,48,64\}\), and the key size is \(m·n\) bits for \(m \in \{2,3,4\}\), depending on the desired security level.
The round function of Speck \(f(x, y, k) = (x', y')\) receives two words \(x\) and \(y\), and a round key \(k\), all of size \(n\), and outputs two words of bitsize \(n\), \(x'\) and \(y'\), such that
\(x' = (x \ggg \alpha)\boxplus y)\oplus k\)
\(y' = x' \oplus (y \lll \beta))\)
The Speck key-schedule algorithm uses the same round function to generate the round keys. Let \(K = (l_{m-2},...,l_0,k_0)\) be a master key for Speck \(2n/m·n\), where \(l_i, k_0\) are \(n\)-bit words. The sequence of round keys \(k_i\) (with \(i\) starting from 0) is generated as
\(l_{i+m-1} = (l_i \ggg \alpha)\boxplus k_i)\oplus i\)
\(k_{i+1} = l_{i+m-1} \oplus (k_i\lll \beta)\)
The rotation offset \((\alpha, \beta)\) is \((7,2)\) for Speck32/64, and \((8,3)\) for the larger versions.
Implementing the round function
First, we will implement the round function \(f(x, y, k) = (x', y')\) using the bitvector
module.
To implement \(f\), we search in bitvector.operation
and bitvector.secondaryop
the classes
corresponding to the bit-vector operations used in \(f\).
In particular, \(f\) uses the modular addition (corresponding to the class BvAdd
),
the left and right rotations (RotateLeft
and RotateRight
), and the XOR (BvXor
).
Thus, we can implement \(f\) with the following Python function
from cascada.bitvector.operation import BvXor, BvAdd, RotateRight, RotateLeft
def f(x, y, k):
x = BvXor(BvAdd(RotateRight(x, 7), y), k)
y = BvXor(RotateLeft(y, 2), x)
return x, y
In the unlikely case that \(f\) would contain an operation not included in the bitvector
module
or in the case that we would want to group some operations together to treat them as an atomic operation,
new operations can be defined as subclasses of SecondaryOperation
(see for example simon_rf
).
We can test \(f\) by evaluating it with symbolic and constant bit-vectors
(corresponding to the classes Variable
and Constant
).
>>> from cascada.bitvector.core import Constant, Variable
>>> f(Variable("x", 16), Variable("y", 16), Variable("z", 16))
(((x >>> 7) + y) ^ z, (y <<< 2) ^ ((x >>> 7) + y) ^ z)
>>> f(Constant(0, 16), Constant(0, 16), Constant(0, 16))
(0x0000, 0x0000)
Since \(f\) is only meant to be used with bit-vector inputs (Term
objects), we can use
the Python operators +, ^
which are overloaded for Term
objects with
the classes BvAdd
and BvXor
respectively. In other words, we can also implement
\(f\) with the following Python function
def f(x, y, k):
x = RotateRight(x, 7) + y ^ k
y = RotateLeft(y, 2) ^ x
return x, y
Implementing the key-schedule and the encryption functions
We will now implement the key-schedule and the encryption functions of Speck32/64 using two Python functions and bit-vector operations. To do so, we will implement the key-schedule as a function taking the key words and returning the list of round keys, and the encryption as a function taking the plaintext words and the round keys and returning the ciphertext words.
from cascada.bitvector.core import Constant
from cascada.bitvector.operation import RotateRight, RotateLeft
num_rounds = 22
def f(x, y, k):
x = RotateRight(x, 7) + y ^ k
y = RotateLeft(y, 2) ^ x
return x, y
def key_schedule(l2, l1, l0, k0):
l = [None for i in range(num_rounds + 3)]
round_keys = [None for _ in range(num_rounds)]
round_keys[0] = k0
l[0:3] = [l0, l1, l2]
for i in range(num_rounds - 1):
l[i+3], round_keys[i+1] = f(l[i], round_keys[i], Constant(i, 16))
return round_keys
def encryption(x, y, round_keys):
for i in range(num_rounds):
x, y = f(x, y, round_keys[i])
return x, y
There are many ways to implement these key-schedule and encryption functions;
our only restriction is that the output of these two functions are list of
bit-vectors (Term
objects) assuming that the inputs are Term
objects.
We can test these two Python functions with the following Speck32/64 test vector
plaintext = (0x6574, 0x694c)
key = (0x1918, 0x1110, 0x0908, 0x0100)
ciphertext = (0xa868, 0x42f2)
>>> from cascada.bitvector.core import Constant
>>> l2, l1 = Constant(0x1918, 16), Constant(0x1110, 16)
>>> l0, k0 = Constant(0x0908, 16), Constant(0x0100, 16)
>>> round_keys = key_schedule(l2, l1, l0, k0)
>>> x, y = Constant(0x6574, 16), Constant(0x694c, 16)
>>> ciphertext = encryption(x, y, round_keys)
>>> ciphertext
(0xa868, 0x42f2)
Wrapping the key-schedule function
To implement Speck32/64 as a primitive supported by CASCADA
(i.e., as a Cipher
subclass), we need to wrap the key-schedule
into a RoundBasedFunction
subclass.
To do so, we need to set some attributes (input_widths
,
output_widths
, num_rounds
) and implement the methods
eval
and set_num_rounds
(see BvFunction
and RoundBasedFunction
)
as follows
from cascada.bitvector.core import Constant
from cascada.bitvector.ssa import RoundBasedFunction
from cascada.bitvector.operation import RotateRight, RotateLeft
def f(x, y, k):
x = RotateRight(x, 7) + y ^ k
y = RotateLeft(y, 2) ^ x
return x, y
class SpeckKeySchedule(RoundBasedFunction):
input_widths = [16, 16, 16, 16]
output_widths = [16 for _ in range(22)]
num_rounds = 22
@classmethod
def eval(cls, l2, l1, l0, k0):
l = [None for i in range(cls.num_rounds + 3)]
round_keys = [None for _ in range(cls.num_rounds)]
round_keys[0] = k0
l[0:3] = [l0, l1, l2]
for i in range(cls.num_rounds - 1):
l[i+3], round_keys[i+1] = f(l[i], round_keys[i], Constant(i, 16))
return round_keys
@classmethod
def set_num_rounds(cls, new_num_rounds):
cls.num_rounds = new_num_rounds
cls.output_widths = [16 for _ in range(new_num_rounds)]
To implement the eval
method, we took the previous Python function key_schedule
working
on bit-vectors, but using the class attribute cls.num_rounds
so that
we can change the current number of rounds with the method set_num_rounds
.
Similar as before, we can test SpeckKeySchedule
by evaluating it with symbolic and constant bit-vectors.
To avoid long outputs, we will reduce the number of rounds for the test:
>>> from cascada.bitvector.core import Constant, Variable
>>> SpeckKeySchedule.set_num_rounds(3)
>>> masterkey = Variable("l2", 16), Variable("l1", 16), Variable("l0", 16), Variable("k0", 16)
>>> SpeckKeySchedule(*masterkey, symbolic_inputs=True)
(k0, (k0 <<< 2) ^ ((l0 >>> 7) + k0), (((k0 <<< 2) ^ ((l0 >>> 7) + k0)) <<< 2) ^ ((l1 >>> 7) + ((k0 <<< 2) ^ ((l0 >>> 7) + k0))) ^ 0x0001)
>>> masterkey = Constant(0, 16), Constant(0, 16), Constant(0, 16), Constant(0, 16)
>>> SpeckKeySchedule(*masterkey)
(0x0000, 0x0000, 0x0001)
By default evaluating RoundBasedFunction
with symbolic inputs is not allowed,
but this can be enabled by passing the optional argument symbolic_inputs=True
.
Note that we can also use the method add_round_outputs
within the eval
to delimit the end of the rounds (as in speck
), but this is optional and
we will not use it in this tutorial.
Wrapping the encryption function
To implement Speck32/64 as a Cipher
subclass, we also need to wrap the encryption
function into a Encryption
/RoundBasedFunction
subclass.
To do so, we need to set some attributes (input_widths
,
output_widths
, num_rounds
) and implement the methods
eval
and set_num_rounds
:
from cascada.bitvector.ssa import RoundBasedFunction
from cascada.bitvector.operation import RotateRight, RotateLeft
from cascada.primitives.blockcipher import Encryption
def f(x, y, k):
x = RotateRight(x, 7) + y ^ k
y = RotateLeft(y, 2) ^ x
return x, y
class SpeckEncryption(Encryption, RoundBasedFunction):
input_widths = [16, 16]
output_widths = [16, 16]
num_rounds = 22
round_keys = None
@classmethod
def eval(cls, x, y):
for i in range(cls.num_rounds):
x, y = f(x, y, cls.round_keys[i])
return x, y
@classmethod
def set_num_rounds(cls, new_num_rounds):
cls.num_rounds = new_num_rounds
Similar as before, we took the previous Python function encryption
working
on bit-vectors, but using the class attribute cls.num_rounds
so that
we can change the current number of rounds with the method set_num_rounds
.
For the eval
method of the encryption in particular, the round keys are obtained
from the class attribute cls.round_keys
(later after we link
SpeckKeySchedule
and SpeckEncryption
, the class attribute cls.round_keys
will be automatically set to the output of SpeckKeySchedule
).
We can test SpeckEncryption
by evaluating it with symbolic and constant bit-vectors,
but we need to set the round keys manually for testing it individually.
>>> from cascada.bitvector.core import Constant, Variable
>>> num_rounds = 2
>>> round_keys = [Variable("k" + str(i), 16) for i in range(num_rounds)]
>>> SpeckEncryption.set_num_rounds(num_rounds)
>>> SpeckEncryption.round_keys = round_keys
>>> plaintext = [Variable("x", 16), Variable("y", 16)]
>>> SpeckEncryption(*plaintext, symbolic_inputs=True)
((((((x >>> 7) + y) ^ k0) >>> 7) + ((y <<< 2) ^ ((x >>> 7) + y) ^ k0)) ^ k1,
(((y <<< 2) ^ ((x >>> 7) + y) ^ k0) <<< 2) ^ (((((x >>> 7) + y) ^ k0) >>> 7) + ((y <<< 2) ^ ((x >>> 7) + y) ^ k0)) ^ k1)
Alternatively, we can also check it by computing its SSA
(a list of simple instructions representing the bit-vector function)
>>> num_rounds = 2
>>> round_keys = [Variable("k" + str(i), 16) for i in range(num_rounds)]
>>> SpeckEncryption.set_num_rounds(num_rounds)
>>> SpeckEncryption.round_keys = round_keys
>>> SpeckEncryption.to_ssa(["x", "y"], "z")
SSA(input_vars=[x, y], output_vars=[z7_out, z9_out], external_vars=[k0, k1],
assignments=[
(z0, x >>> 7), (z1, z0 + y), (z2, z1 ^ k0), (z3, y <<< 2), (z4, z3 ^ z2),
(z5, z2 >>> 7), (z6, z5 + z4), (z7, z6 ^ k1), (z8, z4 <<< 2), (z9, z8 ^ z7),
(z7_out, Id(z7)), (z9_out, Id(z9))])
Wrapping everything as a Cipher subclass
Finally, we can implement Speck32/64 as a Cipher
subclass
using SpeckKeySchedule
, SpeckEncryption
and setting
some attributes:
from cascada.bitvector.core import Constant
from cascada.bitvector.ssa import RoundBasedFunction
from cascada.bitvector.operation import RotateRight, RotateLeft
from cascada.primitives.blockcipher import Encryption, Cipher
def f(x, y, k):
x = RotateRight(x, 7) + y ^ k
y = RotateLeft(y, 2) ^ x
return x, y
class SpeckKeySchedule(RoundBasedFunction):
input_widths = [16, 16, 16, 16]
output_widths = [16 for _ in range(22)]
num_rounds = 22
@classmethod
def eval(cls, l2, l1, l0, k0):
l = [None for i in range(cls.num_rounds + 3)]
round_keys = [None for _ in range(cls.num_rounds)]
round_keys[0] = k0
l[0:3] = [l0, l1, l2]
for i in range(cls.num_rounds - 1):
l[i+3], round_keys[i+1] = f(l[i], round_keys[i], Constant(i, 16))
return round_keys
@classmethod
def set_num_rounds(cls, new_num_rounds):
cls.num_rounds = new_num_rounds
cls.output_widths = [16 for _ in range(new_num_rounds)]
class SpeckEncryption(Encryption, RoundBasedFunction):
input_widths = [16, 16]
output_widths = [16, 16]
num_rounds = 22
round_keys = None
@classmethod
def eval(cls, x, y):
for i in range(cls.num_rounds):
x, y = f(x, y, cls.round_keys[i])
return x, y
@classmethod
def set_num_rounds(cls, new_num_rounds):
cls.num_rounds = new_num_rounds
class SpeckCipher(Cipher):
key_schedule = SpeckKeySchedule
encryption = SpeckEncryption
@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)
The subclass SpeckCipher
is now a primitive that can be used in the automated methods
implemented by CASCADA (see Usage).
Since the differential
, linear
and algebraic
models are implemented
for BvAdd
, BvXor
, RotateLeft
and RotateRight
(the operations of Speck),
SpeckCipher
can be used directly in the automated methods of CASCADA
(e.g., round_based_ch_search
).
However, if we implement a primitive containing an operation whose property model
is not implemented, then we need to implement it in order to search for characteristics
over this primitive. For example, if the primitive contains S-boxes (LutOperation
)
or binary matrix-vector operations (MatrixOperation
), no default property models
are assigned for these operations, but the generic models WeakModel
,
BranchNumberModel
and WDTModel
can be used (as in aes
or skinny64
).
Note that the previous implementation of Speck32/64 is slightly different to the
implementations of Speck in speck
. In particular,
speck
includes add_round_outputs
calls to delimit the
rounds and considers that a Speck cipher instance with \(r\) rounds contains
\(r\) encryption rounds but \((r-1)\) key-schedule rounds
(since the key-schedule does not perform any operation for the 1-round cipher).