Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
yhxnf committed May 14, 2023
0 parents commit d6cdfb5
Show file tree
Hide file tree
Showing 26 changed files with 2,108 additions and 0 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Structure
This repository contains the code for the paper "On Perfect Linear Approximations and Differentials over Two-Round SPNs".
The code is structured into three directories.
[ciphers](./ciphers) contains basic implementations (sboxes and linear layers) of the examined ciphers.
[linear](./linear) contains our code for the linear and [differential](./differential) the code for the differential case.
[main.sage](main.sage) runs all our experiments.

# Dependencies
Our code is based on [sage](https://www.sagemath.org/).
We also make use of a sage [module for linear layers](./ciphers/linearlayer.py) which is not part of sage.
Its original version can be found [here](https://git.sagemath.org/sage.git/tree/src/sage/crypto/linearlayer.py?h=u/asante/linear_layer_module&id=9155aa93f62e2c1bab51a095137b62367c1e7fbb).
Further, we use [tabulate](https://pypi.org/project/tabulate/) to generate nice
tables and [tqdm](https://pypi.org/project/tqdm/) as a progress bar.
To install them, run `pip install tabulate` and `pip install tqdm` inside sage.

# Executing the Code
Run `sage main.sage` to run the experiments.
We used `SageMath version 9.6`.
Earlier versions might not work!
Please note: in some cases sage is not good with Gröbner basis computations. Hence, sometimes we generate magma files instead.
14 changes: 14 additions & 0 deletions ciphers/aes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from sage.crypto.sboxes import AES as S
from ciphers.cipher import AESLikeCipher
from ciphers.linearlayer import AES as L
from ciphers.linearlayer import ff_matrix_to_binary



class AES(AESLikeCipher):

def __init__(self):
MC = ff_matrix_to_binary(L._mc)
SR = ff_matrix_to_binary(L._sc)
super().__init__(S, MC, SR, 16, 4, "AES")

44 changes: 44 additions & 0 deletions ciphers/ascon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from sage.crypto.sboxes import Ascon as S
from sage.matrix.constructor import Matrix
from sage.modules.free_module_element import vector
from sage.modules.free_module import VectorSpace
from sage.rings.finite_rings.finite_field_constructor import GF

from ciphers.cipher import Cipher
class Ascon(Cipher):

def __init__(self):
# build matrix for linear layer
M_ROR = [(19*[0] + [1] + (64-19-1)*[0],
28*[0] + [1] + (64-28-1)*[0]),
(61*[0] + [1] + (64-61-1)*[0],
39*[0] + [1] + (64-39-1)*[0]),
( 1*[0] + [1] + (64- 1-1)*[0],
6*[0] + [1] + (64- 6-1)*[0]),
(10*[0] + [1] + (64-10-1)*[0],
17*[0] + [1] + (64-17-1)*[0]),
( 7*[0] + [1] + (64- 7-1)*[0],
41*[0] + [1] + (64-41-1)*[0])]
M_ROR = list(map(lambda x: (Matrix.circulant(vector(GF(2), x[0])),
Matrix.circulant(vector(GF(2), x[1]))), M_ROR))
def build_L(state):
# state vector -> array of rows
x = [state[i::5] for i in range(5)]
# apply linear layer
x[0] += M_ROR[0][0] * x[0] + M_ROR[0][1] * x[0]
x[1] += M_ROR[1][0] * x[1] + M_ROR[1][1] * x[1]
x[2] += M_ROR[2][0] * x[2] + M_ROR[2][1] * x[2]
x[3] += M_ROR[3][0] * x[3] + M_ROR[3][1] * x[3]
x[4] += M_ROR[4][0] * x[4] + M_ROR[4][1] * x[4]
# transform back
state_ = vector(state.base_ring(), 320)
for i in range(5):
state_[i::5] = x[i]
return state_
L = Matrix(GF(2), 320, 320)
for i, e in enumerate(VectorSpace(GF(2), 320).basis()):
L[:, i] = build_L(e)
super().__init__(S, L, 64, "Ascon")



36 changes: 36 additions & 0 deletions ciphers/boomslang.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from sage.all import *
from sage.crypto.sbox import SBox
from sage.matrix.constructor import Matrix
from ciphers.linearlayer import ff_matrix_to_binary
from sage.combinat.permutation import Permutation
from ciphers.cipher import AESLikeCipher

S = SBox([8, 2, 4, 0xa, 5, 0xf, 7, 6, 0, 0xc, 0xb, 9, 0xe, 0xd, 1, 3])

class Boomslang(AESLikeCipher):
def __init__(self):
I = Matrix(GF(2), 4, 4, [[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]])
X = Matrix(GF(2), 4, 4, [[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
[1, 0, 0, 0]])
X2 = Matrix(GF(2), 4, 4, [[0, 0, 1, 0],
[0, 0, 0, 1],
[1, 0, 0, 0],
[0, 1, 0, 0]])
MC = block_matrix(GF(2), 4, 4, [[ 0, I, X, X2],
[X2, 0, I, X],
[ X, X2, 0, I],
[ I, X, X2, 0]])
SC = Permutation([1, 30, 27, 24, 5, 2, 31, 28, 9, 6, 3, 32, 13, 10, 7,
4, 17, 14, 11, 8, 21, 18, 15, 12, 25, 22, 19, 16, 29,
26, 23, 20])
SC = ff_matrix_to_binary(Matrix(GF(2**4), 32, 32,
list(map(GF(2**4).fetch_int,
SC.to_matrix().list()))))
super().__init__(S, mc_binary_matrix=MC, sc_binary_matrix=SC,
nbr_sboxes=32, nbr_superboxes=8, name="Boomslang")

116 changes: 116 additions & 0 deletions ciphers/cipher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from abc import ABC, abstractmethod
from sage.matrix.special import block_diagonal_matrix
from sage.modules.free_module_element import vector
from sage.crypto.sbox import SBox
from sage.rings.integer_ring import ZZ


class Cipher(ABC):
# basic SPN cipher (without key and constant addition)

def __init__(self, S, L, nbr_sboxes, name):
self.S = S
self.S_inverse = S.inverse()
# make sure that bit order is not messed up
S_hat = SBox([ZZ((S(ZZ(x).digits(2, padto=len(S)))), 2) for x in range(2**len(S))])
self.S_ = [S_hat.component_function(1 << i).algebraic_normal_form() for i in range(len(S))]
self.S_inverse_ = [S_hat.inverse().component_function(1 << i).algebraic_normal_form()
for i in range(len(S))]
self.nbr_sboxes = nbr_sboxes
self.L = L
self.L_inverse = L.inverse()
self.name = name

def __repr__(self):
return self.name

def inverse(self):
return Cipher(self.S_inverse, self.L_inverse, self.nbr_sboxes, self.name + " (inverse)")

def sbox_layer(self, state, nbr_sboxes=None):
# slow but allows evaluation of polynomials
if nbr_sboxes is None:
nbr_sboxes = self.nbr_sboxes
s_ = vector(state.base_ring(), len(state))
b = len(self.S)
for j in range(nbr_sboxes): # iterate sboxes
x = state[b*j:b*(j+1)]
for i in range(b): # iterate output bits of sboxs
s_[b*j+i] = self.S_[i](*x)
return s_

def sbox_layer_inverse(self, state, nbr_sboxes=None):
if nbr_sboxes is None:
nbr_sboxes = self.nbr_sboxes
s_ = vector(state.base_ring(), len(state))
b = len(self.S)
for j in range(nbr_sboxes): # iterate sboxes
x = state[b*j:b*(j+1)]
for i in range(b): # iterate output bits of sboxs
s_[b*j+i] = self.S_inverse_[i](*x)
return s_

def sbox_layer_faster(self, state, nbr_sboxes=None):
# a bit faster but only GF(2)
if nbr_sboxes is None:
nbr_sboxes = self.nbr_sboxes
s_ = vector(state.base_ring(), len(state))
b = len(self.S)
for j in range(nbr_sboxes): # iterate sboxes
s_[b*j:b*(j+1)] = self.S(state[b*j:b*(j+1)])
return s_

def sbox_layer_inverse_faster(self, state, nbr_sboxes=None):
# a bit faster but only GF(2)
if nbr_sboxes is None:
nbr_sboxes = self.nbr_sboxes
s_ = vector(state.base_ring(), len(state))
b = len(self.S)
for j in range(nbr_sboxes): # iterate sboxes
s_[b*j:b*(j+1)] = self.S_inverse(state[b*j:b*(j+1)])
return s_


def linear_layer(self, state):
return self.L*state

def linear_layer_inverse(self, state):
return self.L_inverse*state

class AESLikeCipher(Cipher):

def __init__(self, S, mc_binary_matrix, sc_binary_matrix, nbr_sboxes, nbr_superboxes, name, sc_first=False):
self.nbr_superboxes = nbr_superboxes
self.mc_binary_matrix = mc_binary_matrix # only one superbox
self.mc_inverse_binary_matrix = mc_binary_matrix.inverse()
self.mc_layer_binary_matrix = block_diagonal_matrix(*[mc_binary_matrix for _ in range(nbr_superboxes)])
self.mc_layer_binary_matrix_inverse = self.mc_layer_binary_matrix.inverse()
self.sc_binary_matrix = sc_binary_matrix
self.sc_inverse_binary_matrix = sc_binary_matrix.inverse()
self.sc_first = sc_first
L = self.mc_layer_binary_matrix*self.sc_binary_matrix if sc_first \
else self.sc_binary_matrix*self.mc_layer_binary_matrix
super().__init__(S, L, nbr_sboxes, name)

def inverse(self):
return AESLikeCipher(
self.S_inverse, self.mc_inverse_binary_matrix, self.sc_inverse_binary_matrix,
self.nbr_sboxes, self.nbr_superboxes, self.name + " (inverse)", not self.sc_first
)

def superbox(self, state):
nbr_sboxes_per_superbox = self.nbr_sboxes // self.nbr_superboxes
state = self.sbox_layer(state, nbr_sboxes=nbr_sboxes_per_superbox)
state = self.mc_binary_matrix * state
state = self.sbox_layer(state, nbr_sboxes=nbr_sboxes_per_superbox)
return state

def mc(self, state):
return self.mc_layer_binary_matrix * state
def mc_inverse(self, state):
return self.mc_layer_binary_matrix_inverse * state
def sc(self, state):
return self.sc_binary_matrix * state
def sc_inverse(self, state):
return self.sc_inverse_binary_matrix * state

19 changes: 19 additions & 0 deletions ciphers/craft.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from sage.crypto.sboxes import CRAFT as S
from ciphers.cipher import AESLikeCipher
from sage.matrix.constructor import Matrix
from ciphers.linearlayer import ff_matrix_to_binary
from sage.rings.finite_rings.finite_field_constructor import GF
from sage.combinat.permutation import Permutation

class Craft(AESLikeCipher):
def __init__(self):
MC = Matrix(GF(2**4), 4, 4, map(GF(2**4).fetch_int, [1, 0, 1, 1,
0, 1, 0, 1,
0, 0, 1, 0,
0, 0, 0, 1]))
MC = ff_matrix_to_binary(MC)
SC = Permutation([16, 11, 10, 5, 4, 7, 6, 9, 8, 3, 2, 13, 12, 15, 14, 1])
SC = ff_matrix_to_binary(Matrix(GF(2**4), 16, 16,
list(map(GF(2**4).fetch_int,
SC.to_matrix().list()))))
super().__init__(S, MC, SC, 16, 4, "Craft")
36 changes: 36 additions & 0 deletions ciphers/gift.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from sage.crypto.sboxes import GIFT as S
from ciphers.cipher import AESLikeCipher
from ciphers.linearlayer import GIFT64, GIFT128, ff_matrix_to_binary
from sage.matrix.constructor import Matrix
from sage.rings.finite_rings.finite_field_constructor import GF
from sage.combinat.permutation import Permutation


class Gift(AESLikeCipher):

def __init__(self, n=64):
if n not in [64, 128]:
raise ValueError("n must be in [64, 128]")

mc_binary_matrix = Matrix(GF(2), Permutation([
1, 6, 11, 16,
13, 2, 7, 12,
9, 14, 3, 8,
5, 10, 15, 4
]).to_matrix())

if n == 64:
sc_binary_matrix = ff_matrix_to_binary(Matrix(GF(2**4), Permutation([
1 + (i//4) + ((i*4) % 16) for i in range(16)
]).to_matrix()))
nbr_sboxes = 16
nbr_superboxes = 4
else:
sc_binary_matrix = ff_matrix_to_binary(Matrix(GF(2**4), Permutation([
1 + (i//4) + ((i*8) % 32) for i in range(32)
]).to_matrix()))
nbr_sboxes = 32
nbr_superboxes = 8

super().__init__(S, mc_binary_matrix, sc_binary_matrix, nbr_sboxes, nbr_superboxes, f"GIFT-{n}")
assert self.L == (GIFT64 if n == 64 else GIFT128).binary_matrix()
38 changes: 38 additions & 0 deletions ciphers/iscream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from sage.crypto.sboxes import iScream as S
from ciphers.cipher import Cipher
from sage.rings.finite_rings.finite_field_constructor import GF
from sage.matrix.constructor import Matrix
from sage.modules.free_module_element import vector
from sage.modules.free_module import VectorSpace

class iScream(Cipher):
def __init__(self):
M_L = [[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1],
[1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
[1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1],
[1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0],
[1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0],
[1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1],
[1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1],
[1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1],
[1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0],
[1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0],
[1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0],
[1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0],
[1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0]]
M_L = Matrix(GF(2), 16, 16, M_L)
def build_L(state):
# state vector -> array of rows
x = [state[i::8] for i in range(8)]
y = [M_L * xx for xx in x]
state_ = vector(state.base_ring(), 128)
for i in range(8):
state_[i::8] = y[i]
return state_
L = Matrix(GF(2), 128, 128)
for i, e in enumerate(VectorSpace(GF(2), 128).basis()):
L[:, i] = build_L(e)
super().__init__(S, L, 16, "iScream")
Loading

0 comments on commit d6cdfb5

Please sign in to comment.