-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit d6cdfb5
Showing
26 changed files
with
2,108 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |
Oops, something went wrong.