Module cryptanalysis.spn
The spn
module implements the Substitution-Permutation Network (SPN) encryption algorithm.
SPN is a symmetric-key block cipher that operates on fixed-size blocks of plaintext and produces corresponding blocks of ciphertext. It consists of several rounds of substitutions and permutations and key additions applied to the input data.
Classes: - SPN: Implementation of the SPN encryption algorithm.
Usage:
Initialize an instance of the SPN
class with the required parameters, then use the provided methods
to perform encryption and decryption operations.
Expand source code
"""
The `spn` module implements the Substitution-Permutation Network (SPN) encryption algorithm.
SPN is a symmetric-key block cipher that operates on fixed-size blocks of plaintext and produces
corresponding blocks of ciphertext. It consists of several rounds of substitutions and permutations
and key additions applied to the input data.
Classes:
- SPN: Implementation of the SPN encryption algorithm.
Usage:
Initialize an instance of the `SPN` class with the required parameters, then use the provided methods
to perform encryption and decryption operations.
"""
from math import log2
__all__ = ["rotate_left", "gen_pbox", "SPN"]
def rotate_left(val, shift, mod):
"""
Rotate the bits of the value to the left by the shift amount.
Parameters
----------
val : int
The value to be rotated.
shift : int
The number of places to shift the value to the left.
mod : int
The modulo to be applied on the result.
Returns
-------
int
The rotated value.
Notes
-----
The function rotates the bits of the value to the left by the shift amount,
wrapping the bits that overflow. The result is then masked by (1<<mod)-1
to only keep the mod number of least significant bits.
"""
shift = shift % mod
return (val << shift | val >> (mod - shift)) & ((1 << mod) - 1)
def gen_pbox(s, n):
"""
Generate a balanced permutation box for an SPN.
Parameters
----------
s : int
Number of bits per S-box.
n : int
Number of S-boxes.
Returns
-------
list of int
The generated P-box.
"""
return [(s * i + j) % (n * s) for j in range(s) for i in range(n)]
class SPN:
"""
Class representing the SPN (Substitution-Permutation Network) encryption algorithm.
Methods
-------
perm(inp)
Apply the P-box permutation on the input.
inv_perm(inp)
Apply the inverse P-box permutation on the input.
sub(inp)
Apply the S-box substitution on the input.
inv_sub(inp)
Apply the inverse S-box substitution on the input.
int_to_list(inp)
Convert a len(pbox)-sized integer to a list of S-box sized integers.
list_to_int(lst)
Convert a list of S-box sized integers to a len(pbox)-sized integer.
expand_key(key, rounds)
Derive round keys deterministically from the given key.
_enc_last_noperm(pt)
Encrypt plaintext using the SPN, where the last round doesn't contain the permute operation.
_enc_last_withperm(ct)
Encrypt plaintext using the SPN, where the last round contains the permute operation.
_dec_last_noperm(ct)
Decrypt ciphertext using the SPN, where the last round doesn't contain the permute operation.
_dec_last_withperm(ct)
Decrypt ciphertext using the SPN, where the last round contains the permute operation.
"""
def __init__(self, sbox, pbox, key, rounds, implementation=0):
"""
Initialize the SPN class with the provided parameters.
Parameters
----------
sbox : list of int
List of integers representing the S-box.
pbox : list of int
List of integers representing the P-box.
key : list of int or bytes or bytearray
List of integers, bytes, or bytearray representing the key.
LSB block_size bits will be used.
rounds : int
Number of rounds for the SPN.
implementation : int, optional
Implementation option. Default is 0.
0: Last round doesn't contain the permute operation.
1: Last round contains the permute operation.
"""
self.sbox = sbox
self.pbox = pbox
self.sinv = [sbox.index(i) for i in range(len(sbox))]
self.pinv = [pbox.index(i) for i in range(len(pbox))]
self.block_size = len(pbox)
self.box_size = int(log2(len(sbox)))
self.num_sbox = len(pbox) // self.box_size
self.rounds = rounds
self.round_keys = self.expand_key(key, rounds)
if implementation == 0:
self.encrypt = self._enc_last_noperm
self.decrypt = self._dec_last_noperm
else:
self.encrypt = self._enc_last_withperm
self.decrypt = self._dec_last_withperm
def perm(self, inp: int) -> int:
"""
Apply the P-box permutation on the input.
Parameters
----------
inp : int
The input value to apply the P-box permutation on.
Returns
-------
int
The permuted value after applying the P-box.
"""
ct = 0
for i, v in enumerate(self.pbox):
ct |= (inp >> (self.block_size - 1 - i) & 1) << (
self.block_size - 1 - v)
return ct
def inv_perm(self, inp: int) -> int:
"""
Apply the inverse P-box permutation on the input.
Parameters
----------
inp : int
The input value to apply the inverse P-box permutation on.
Returns
-------
int
The permuted value after applying the inverse P-box.
"""
ct = 0
for i, v in enumerate(self.pinv):
ct |= (inp >> (self.block_size - 1 - i) & 1) << (
self.block_size - 1 - v)
return ct
def sub(self, inp: int) -> int:
"""
Apply the S-box substitution on the input.
Parameters
----------
inp : int
The input value to apply the S-box substitution on.
Returns
-------
int
The substituted value after applying the S-box.
"""
ct, bs = 0, self.box_size
for i in range(self.num_sbox):
ct |= self.sbox[(inp >> (i * bs)) & ((1 << bs) - 1)] << (bs * i)
return ct
def inv_sub(self, inp: int) -> int:
"""
Apply the inverse S-box substitution on the input.
Parameters
----------
inp : int
The input value to apply the inverse S-box substitution on.
Returns
-------
int
The substituted value after applying the inverse S-box.
"""
ct, bs = 0, self.box_size
for i in range(self.num_sbox):
ct |= self.sinv[(inp >> (i * bs)) & ((1 << bs) - 1)] << (bs * i)
return ct
def int_to_list(self, inp):
"""
Convert a len(pbox)-sized integer to a list of S-box sized integers.
Parameters
----------
inp : int
An integer representing a len(pbox)-sized input.
Returns
-------
list of int
A list of integers, each representing an S-box sized input.
"""
bs = self.box_size
return [(inp >> (i * bs)) & ((1 << bs) - 1)
for i in range(self.num_sbox - 1, -1, -1)]
def list_to_int(self, lst):
"""
Convert a list of S-box sized integers to a len(pbox)-sized integer.
Parameters
----------
lst : list of int
A list of integers, each representing an S-box sized input.
Returns
-------
int
An integer representing the combined input as a len(pbox)-sized integer.
"""
res = 0
for i, v in enumerate(lst[::-1]):
res |= v << (i * self.box_size)
return res
def expand_key(self, key, rounds):
"""
Derive round keys deterministically from the given key.
Parameters
----------
key : list of int or bytes or bytearray
A list of integers, bytes, or bytearray representing the key.
rounds : int
The number of rounds for the SPN.
Returns
-------
list of int
A list of integers representing the derived round keys.
"""
if isinstance(key, list):
key = self.list_to_int(key)
elif isinstance(key, (bytes, bytearray)):
key = int.from_bytes(key, 'big')
block_mask = (1 << self.block_size) - 1
key = key & block_mask
keys = [key]
for _ in range(rounds):
keys.append(self.sub(rotate_left(
keys[-1], self.box_size + 1, self.block_size)))
return keys
def _enc_last_noperm(self, pt: int) -> int:
"""
Encrypt plaintext using the SPN, where the last round doesn't contain the permute operation.
Parameters
----------
pt : int
The plaintext input to be encrypted.
Returns
-------
int
The ciphertext after encryption.
"""
ct = pt ^ self.round_keys[0]
for round_key in self.round_keys[1:-1]:
ct = self.sub(ct)
ct = self.perm(ct)
ct ^= round_key
ct = self.sub(ct)
return ct ^ self.round_keys[-1]
def _enc_last_withperm(self, ct: int) -> int:
"""
Encrypt plaintext using the SPN, where the last round contains the permute operation.
Note, the last permutation provides no additional security.
Parameters
----------
ct : int
The plaintext input to be encrypted.
Returns
-------
int
The ciphertext after encryption.
"""
for round_key in self.round_keys[:-1]:
ct ^= round_key
ct = self.sub(ct)
ct = self.perm(ct)
return ct ^ self.round_keys[-1]
def _dec_last_noperm(self, ct: int) -> int:
"""
Decrypt ciphertext using the SPN, where the last round doesn't contain the permute operation.
Parameters
----------
ct : int
The ciphertext input to be decrypted.
Returns
-------
int
The plaintext after decryption.
"""
ct = ct ^ self.round_keys[-1]
ct = self.inv_sub(ct)
for rk in self.round_keys[-2:0:-1]:
ct ^= rk
ct = self.inv_perm(ct)
ct = self.inv_sub(ct)
return ct ^ self.round_keys[0]
def _dec_last_withperm(self, ct: int) -> int:
"""
Decrypt ciphertext using the SPN, where the last round contains the permute operation.
Parameters
----------
ct : int
The ciphertext input to be decrypted.
Returns
-------
int
The plaintext after decryption.
"""
ct = ct ^ self.round_keys[-1]
for rk in self.round_keys[-2::-1]:
ct = self.inv_perm(ct)
ct = self.inv_sub(ct)
ct ^= rk
return ct
Functions
def gen_pbox(s, n)
-
Generate a balanced permutation box for an SPN.
Parameters
s
:int
- Number of bits per S-box.
n
:int
- Number of S-boxes.
Returns
list
ofint
- The generated P-box.
Expand source code
def gen_pbox(s, n): """ Generate a balanced permutation box for an SPN. Parameters ---------- s : int Number of bits per S-box. n : int Number of S-boxes. Returns ------- list of int The generated P-box. """ return [(s * i + j) % (n * s) for j in range(s) for i in range(n)]
def rotate_left(val, shift, mod)
-
Rotate the bits of the value to the left by the shift amount.
Parameters
val
:int
- The value to be rotated.
shift
:int
- The number of places to shift the value to the left.
mod
:int
- The modulo to be applied on the result.
Returns
int
- The rotated value.
Notes
The function rotates the bits of the value to the left by the shift amount, wrapping the bits that overflow. The result is then masked by (1<<mod)-1 to only keep the mod number of least significant bits.
Expand source code
def rotate_left(val, shift, mod): """ Rotate the bits of the value to the left by the shift amount. Parameters ---------- val : int The value to be rotated. shift : int The number of places to shift the value to the left. mod : int The modulo to be applied on the result. Returns ------- int The rotated value. Notes ----- The function rotates the bits of the value to the left by the shift amount, wrapping the bits that overflow. The result is then masked by (1<<mod)-1 to only keep the mod number of least significant bits. """ shift = shift % mod return (val << shift | val >> (mod - shift)) & ((1 << mod) - 1)
Classes
class SPN (sbox, pbox, key, rounds, implementation=0)
-
Class representing the SPN (Substitution-Permutation Network) encryption algorithm.
Methods
perm(inp) Apply the P-box permutation on the input. inv_perm(inp) Apply the inverse P-box permutation on the input. sub(inp) Apply the S-box substitution on the input. inv_sub(inp) Apply the inverse S-box substitution on the input. int_to_list(inp) Convert a len(pbox)-sized integer to a list of S-box sized integers. list_to_int(lst) Convert a list of S-box sized integers to a len(pbox)-sized integer. expand_key(key, rounds) Derive round keys deterministically from the given key. _enc_last_noperm(pt) Encrypt plaintext using the SPN, where the last round doesn't contain the permute operation. _enc_last_withperm(ct) Encrypt plaintext using the SPN, where the last round contains the permute operation. _dec_last_noperm(ct) Decrypt ciphertext using the SPN, where the last round doesn't contain the permute operation. _dec_last_withperm(ct) Decrypt ciphertext using the SPN, where the last round contains the permute operation.
Initialize the SPN class with the provided parameters.
Parameters
sbox
:list
ofint
- List of integers representing the S-box.
pbox
:list
ofint
- List of integers representing the P-box.
key
:list
ofint
orbytes
orbytearray
- List of integers, bytes, or bytearray representing the key. LSB block_size bits will be used.
rounds
:int
- Number of rounds for the SPN.
implementation
:int
, optional- Implementation option. Default is 0. 0: Last round doesn't contain the permute operation. 1: Last round contains the permute operation.
Expand source code
class SPN: """ Class representing the SPN (Substitution-Permutation Network) encryption algorithm. Methods ------- perm(inp) Apply the P-box permutation on the input. inv_perm(inp) Apply the inverse P-box permutation on the input. sub(inp) Apply the S-box substitution on the input. inv_sub(inp) Apply the inverse S-box substitution on the input. int_to_list(inp) Convert a len(pbox)-sized integer to a list of S-box sized integers. list_to_int(lst) Convert a list of S-box sized integers to a len(pbox)-sized integer. expand_key(key, rounds) Derive round keys deterministically from the given key. _enc_last_noperm(pt) Encrypt plaintext using the SPN, where the last round doesn't contain the permute operation. _enc_last_withperm(ct) Encrypt plaintext using the SPN, where the last round contains the permute operation. _dec_last_noperm(ct) Decrypt ciphertext using the SPN, where the last round doesn't contain the permute operation. _dec_last_withperm(ct) Decrypt ciphertext using the SPN, where the last round contains the permute operation. """ def __init__(self, sbox, pbox, key, rounds, implementation=0): """ Initialize the SPN class with the provided parameters. Parameters ---------- sbox : list of int List of integers representing the S-box. pbox : list of int List of integers representing the P-box. key : list of int or bytes or bytearray List of integers, bytes, or bytearray representing the key. LSB block_size bits will be used. rounds : int Number of rounds for the SPN. implementation : int, optional Implementation option. Default is 0. 0: Last round doesn't contain the permute operation. 1: Last round contains the permute operation. """ self.sbox = sbox self.pbox = pbox self.sinv = [sbox.index(i) for i in range(len(sbox))] self.pinv = [pbox.index(i) for i in range(len(pbox))] self.block_size = len(pbox) self.box_size = int(log2(len(sbox))) self.num_sbox = len(pbox) // self.box_size self.rounds = rounds self.round_keys = self.expand_key(key, rounds) if implementation == 0: self.encrypt = self._enc_last_noperm self.decrypt = self._dec_last_noperm else: self.encrypt = self._enc_last_withperm self.decrypt = self._dec_last_withperm def perm(self, inp: int) -> int: """ Apply the P-box permutation on the input. Parameters ---------- inp : int The input value to apply the P-box permutation on. Returns ------- int The permuted value after applying the P-box. """ ct = 0 for i, v in enumerate(self.pbox): ct |= (inp >> (self.block_size - 1 - i) & 1) << ( self.block_size - 1 - v) return ct def inv_perm(self, inp: int) -> int: """ Apply the inverse P-box permutation on the input. Parameters ---------- inp : int The input value to apply the inverse P-box permutation on. Returns ------- int The permuted value after applying the inverse P-box. """ ct = 0 for i, v in enumerate(self.pinv): ct |= (inp >> (self.block_size - 1 - i) & 1) << ( self.block_size - 1 - v) return ct def sub(self, inp: int) -> int: """ Apply the S-box substitution on the input. Parameters ---------- inp : int The input value to apply the S-box substitution on. Returns ------- int The substituted value after applying the S-box. """ ct, bs = 0, self.box_size for i in range(self.num_sbox): ct |= self.sbox[(inp >> (i * bs)) & ((1 << bs) - 1)] << (bs * i) return ct def inv_sub(self, inp: int) -> int: """ Apply the inverse S-box substitution on the input. Parameters ---------- inp : int The input value to apply the inverse S-box substitution on. Returns ------- int The substituted value after applying the inverse S-box. """ ct, bs = 0, self.box_size for i in range(self.num_sbox): ct |= self.sinv[(inp >> (i * bs)) & ((1 << bs) - 1)] << (bs * i) return ct def int_to_list(self, inp): """ Convert a len(pbox)-sized integer to a list of S-box sized integers. Parameters ---------- inp : int An integer representing a len(pbox)-sized input. Returns ------- list of int A list of integers, each representing an S-box sized input. """ bs = self.box_size return [(inp >> (i * bs)) & ((1 << bs) - 1) for i in range(self.num_sbox - 1, -1, -1)] def list_to_int(self, lst): """ Convert a list of S-box sized integers to a len(pbox)-sized integer. Parameters ---------- lst : list of int A list of integers, each representing an S-box sized input. Returns ------- int An integer representing the combined input as a len(pbox)-sized integer. """ res = 0 for i, v in enumerate(lst[::-1]): res |= v << (i * self.box_size) return res def expand_key(self, key, rounds): """ Derive round keys deterministically from the given key. Parameters ---------- key : list of int or bytes or bytearray A list of integers, bytes, or bytearray representing the key. rounds : int The number of rounds for the SPN. Returns ------- list of int A list of integers representing the derived round keys. """ if isinstance(key, list): key = self.list_to_int(key) elif isinstance(key, (bytes, bytearray)): key = int.from_bytes(key, 'big') block_mask = (1 << self.block_size) - 1 key = key & block_mask keys = [key] for _ in range(rounds): keys.append(self.sub(rotate_left( keys[-1], self.box_size + 1, self.block_size))) return keys def _enc_last_noperm(self, pt: int) -> int: """ Encrypt plaintext using the SPN, where the last round doesn't contain the permute operation. Parameters ---------- pt : int The plaintext input to be encrypted. Returns ------- int The ciphertext after encryption. """ ct = pt ^ self.round_keys[0] for round_key in self.round_keys[1:-1]: ct = self.sub(ct) ct = self.perm(ct) ct ^= round_key ct = self.sub(ct) return ct ^ self.round_keys[-1] def _enc_last_withperm(self, ct: int) -> int: """ Encrypt plaintext using the SPN, where the last round contains the permute operation. Note, the last permutation provides no additional security. Parameters ---------- ct : int The plaintext input to be encrypted. Returns ------- int The ciphertext after encryption. """ for round_key in self.round_keys[:-1]: ct ^= round_key ct = self.sub(ct) ct = self.perm(ct) return ct ^ self.round_keys[-1] def _dec_last_noperm(self, ct: int) -> int: """ Decrypt ciphertext using the SPN, where the last round doesn't contain the permute operation. Parameters ---------- ct : int The ciphertext input to be decrypted. Returns ------- int The plaintext after decryption. """ ct = ct ^ self.round_keys[-1] ct = self.inv_sub(ct) for rk in self.round_keys[-2:0:-1]: ct ^= rk ct = self.inv_perm(ct) ct = self.inv_sub(ct) return ct ^ self.round_keys[0] def _dec_last_withperm(self, ct: int) -> int: """ Decrypt ciphertext using the SPN, where the last round contains the permute operation. Parameters ---------- ct : int The ciphertext input to be decrypted. Returns ------- int The plaintext after decryption. """ ct = ct ^ self.round_keys[-1] for rk in self.round_keys[-2::-1]: ct = self.inv_perm(ct) ct = self.inv_sub(ct) ct ^= rk return ct
Subclasses
Methods
def expand_key(self, key, rounds)
-
Derive round keys deterministically from the given key.
Parameters
key
:list
ofint
orbytes
orbytearray
- A list of integers, bytes, or bytearray representing the key.
rounds
:int
- The number of rounds for the SPN.
Returns
list
ofint
- A list of integers representing the derived round keys.
Expand source code
def expand_key(self, key, rounds): """ Derive round keys deterministically from the given key. Parameters ---------- key : list of int or bytes or bytearray A list of integers, bytes, or bytearray representing the key. rounds : int The number of rounds for the SPN. Returns ------- list of int A list of integers representing the derived round keys. """ if isinstance(key, list): key = self.list_to_int(key) elif isinstance(key, (bytes, bytearray)): key = int.from_bytes(key, 'big') block_mask = (1 << self.block_size) - 1 key = key & block_mask keys = [key] for _ in range(rounds): keys.append(self.sub(rotate_left( keys[-1], self.box_size + 1, self.block_size))) return keys
def int_to_list(self, inp)
-
Convert a len(pbox)-sized integer to a list of S-box sized integers.
Parameters
inp
:int
- An integer representing a len(pbox)-sized input.
Returns
list
ofint
- A list of integers, each representing an S-box sized input.
Expand source code
def int_to_list(self, inp): """ Convert a len(pbox)-sized integer to a list of S-box sized integers. Parameters ---------- inp : int An integer representing a len(pbox)-sized input. Returns ------- list of int A list of integers, each representing an S-box sized input. """ bs = self.box_size return [(inp >> (i * bs)) & ((1 << bs) - 1) for i in range(self.num_sbox - 1, -1, -1)]
def inv_perm(self, inp: int) ‑> int
-
Apply the inverse P-box permutation on the input.
Parameters
inp
:int
- The input value to apply the inverse P-box permutation on.
Returns
int
- The permuted value after applying the inverse P-box.
Expand source code
def inv_perm(self, inp: int) -> int: """ Apply the inverse P-box permutation on the input. Parameters ---------- inp : int The input value to apply the inverse P-box permutation on. Returns ------- int The permuted value after applying the inverse P-box. """ ct = 0 for i, v in enumerate(self.pinv): ct |= (inp >> (self.block_size - 1 - i) & 1) << ( self.block_size - 1 - v) return ct
def inv_sub(self, inp: int) ‑> int
-
Apply the inverse S-box substitution on the input.
Parameters
inp
:int
- The input value to apply the inverse S-box substitution on.
Returns
int
- The substituted value after applying the inverse S-box.
Expand source code
def inv_sub(self, inp: int) -> int: """ Apply the inverse S-box substitution on the input. Parameters ---------- inp : int The input value to apply the inverse S-box substitution on. Returns ------- int The substituted value after applying the inverse S-box. """ ct, bs = 0, self.box_size for i in range(self.num_sbox): ct |= self.sinv[(inp >> (i * bs)) & ((1 << bs) - 1)] << (bs * i) return ct
def list_to_int(self, lst)
-
Convert a list of S-box sized integers to a len(pbox)-sized integer.
Parameters
lst
:list
ofint
- A list of integers, each representing an S-box sized input.
Returns
int
- An integer representing the combined input as a len(pbox)-sized integer.
Expand source code
def list_to_int(self, lst): """ Convert a list of S-box sized integers to a len(pbox)-sized integer. Parameters ---------- lst : list of int A list of integers, each representing an S-box sized input. Returns ------- int An integer representing the combined input as a len(pbox)-sized integer. """ res = 0 for i, v in enumerate(lst[::-1]): res |= v << (i * self.box_size) return res
def perm(self, inp: int) ‑> int
-
Apply the P-box permutation on the input.
Parameters
inp
:int
- The input value to apply the P-box permutation on.
Returns
int
- The permuted value after applying the P-box.
Expand source code
def perm(self, inp: int) -> int: """ Apply the P-box permutation on the input. Parameters ---------- inp : int The input value to apply the P-box permutation on. Returns ------- int The permuted value after applying the P-box. """ ct = 0 for i, v in enumerate(self.pbox): ct |= (inp >> (self.block_size - 1 - i) & 1) << ( self.block_size - 1 - v) return ct
def sub(self, inp: int) ‑> int
-
Apply the S-box substitution on the input.
Parameters
inp
:int
- The input value to apply the S-box substitution on.
Returns
int
- The substituted value after applying the S-box.
Expand source code
def sub(self, inp: int) -> int: """ Apply the S-box substitution on the input. Parameters ---------- inp : int The input value to apply the S-box substitution on. Returns ------- int The substituted value after applying the S-box. """ ct, bs = 0, self.box_size for i in range(self.num_sbox): ct |= self.sbox[(inp >> (i * bs)) & ((1 << bs) - 1)] << (bs * i) return ct