PRESENT#

An ultra-lightweight block cipher.

This file implements the PRESENT block cipher and the corresponding key schedule as described in [BKLPPRSV2007]. PRESENT is an example of an SP-network and consists of 31 rounds. The block length is 64 bits and two key lengths of 80 and 128 bits are supported.

This implementation is meant for experimental and educational usage only, do not use it in production code!

EXAMPLES:

Encrypt a message:

sage: from sage.crypto.block_cipher.present import PRESENT
sage: present = PRESENT()
sage: present.encrypt(plaintext=0, key=0).hex()
'2844b365c06992a3'

And decrypt it again:

sage: present.decrypt(ciphertext=0x2844b365c06992a3, key=0)
0

Have a look at the used round keys:

sage: from sage.crypto.block_cipher.present import PRESENT_KS
sage: ks = PRESENT_KS()
sage: [k.hex() for k in ks(0)]
['0',
 'c000000000000000',
  ...
 '6dab31744f41d700']

Tweak around with the cipher:

sage: from sage.crypto.sbox import SBox
sage: cipher = PRESENT(rounds=1, doFinalRound=False)
sage: cipher.sbox = SBox(range(16))
sage: cipher.keySchedule = lambda x: [0, 0]  # return the 0 keys as round keys
sage: cipher.encrypt(plaintext=0x1234, key=0x0).hex()
'1234'

AUTHORS:

  • Lukas Stennes (2019-02-01): initial version

class sage.crypto.block_cipher.present.PRESENT(keySchedule=80, rounds=None, doFinalRound=False)#

Bases: SageObject

This class implements PRESENT described in [BKLPPRSV2007].

EXAMPLES:

You can invoke PRESENT encryption/decryption either by calling PRESENT with an appropriate flag:

sage: from sage.crypto.block_cipher.present import PRESENT
sage: present = PRESENT()
sage: P = 0xFFFFFFFFFFFFFFFF
sage: K = 0x0
sage: present(present(P, K, 'encrypt'), K, 'decrypt') == P
True

Or by calling encryption/decryption methods directly:

sage: C = present.encrypt(P, K)
sage: P == present.decrypt(C, K)
True

The number of rounds can be reduced easily:

sage: present = PRESENT(rounds=15)
sage: present(present(P, K, 'encrypt'), K, 'decrypt') == P
True

You can use integers or a list-like bit representation for the inputs. If the input is an integer the output will be too. If it is list-like the output will be a bit vector:

sage: P = ZZ(0).digits(2,padto=64)
sage: K = ZZ(0).digits(2,padto=80)
sage: list(present(present(P, K, 'encrypt'), K, 'decrypt')) == P
True
sage: P = ZZ(0).digits(2,padto=64)
sage: K = 0x0
sage: list(present(present(P, K, 'encrypt'), K, 'decrypt')) == P
True

The 80-bit version of PRESENT is used by default but the 128-bit version is also implemented:

sage: present = PRESENT(128)
sage: P = 0x0123456789abcdef
sage: K = 0x00112233445566778899aabbccddeeff
sage: present(present(P, K, 'encrypt'), K, 'decrypt') == P
True
__init__(keySchedule=80, rounds=None, doFinalRound=False)#

Construct an instance of PRESENT.

INPUT:

  • keySchedule – (default: 80); the key schedule that will be used for encryption and decryption. Use 80 or 128 as a shortcut for the original key schedules from [BKLPPRSV2007].

  • rounds – integer (default: None); the number of rounds. If None the number of rounds of the key schedule is used.

  • doFinalRound – boolean (default: False); flag to control whether the linear layer in the last round should take place or not. Since the last linear layer does not add any security, it usually does not take place in real world implementations for performance reasons.

EXAMPLES:

By default a 80-bit version with 31 rounds is created:

sage: from sage.crypto.block_cipher.present import PRESENT
sage: PRESENT() # indirect doctest
PRESENT block cipher with 31 rounds, deactivated linear layer in
last round and the following key schedule:
Original PRESENT key schedule with 80-bit keys and 31 rounds

The 128-bit version is also implemented:

sage: PRESENT(128) # indirect doctest
PRESENT block cipher with 31 rounds, deactivated linear layer in
last round and the following key schedule:
Original PRESENT key schedule with 128-bit keys and 31 rounds

Reducing the number of rounds is simple. But increasing it is not possible:

sage: PRESENT(keySchedule=80, rounds=23) # indirect doctest
PRESENT block cipher with 23 rounds, deactivated linear layer in
last round and the following key schedule:
Original PRESENT key schedule with 80-bit keys and 31 rounds
sage: PRESENT(80, 32) # indirect doctest
Traceback (most recent call last):
...
ValueError: number of rounds must be less or equal to the number
of rounds of the key schedule

By default the linear layer operation in the last round is omitted but of course you can enable it:

sage: PRESENT(doFinalRound=True) # indirect doctest
PRESENT block cipher with 31 rounds, activated linear layer in
last round and the following key schedule:
Original PRESENT key schedule with 80-bit keys and 31 rounds

You can use arbitrary key schedules. Since it is the only one implemented here the original key schedule is used for demonstration:

sage: from sage.crypto.block_cipher.present import PRESENT_KS
sage: PRESENT(keySchedule=PRESENT_KS(80, 15)) # indirect doctest
PRESENT block cipher with 15 rounds, deactivated linear layer in
last round and the following key schedule:
Original PRESENT key schedule with 80-bit keys and 15 rounds

See also

PRESENT_KS

__call__(block, key, algorithm='encrypt')#

Apply PRESENT encryption or decryption on block using key. The flag algorithm controls what action is to be performed on block.

INPUT:

  • block – integer or bit list-like; the plaintext or ciphertext

  • K – integer or bit list-like; the key

  • algorithm – string (default: 'encrypt'); a flag to signify whether encryption or decryption is to be applied to B. The encryption flag is 'encrypt' and the decryption flag is 'decrypt'

OUTPUT:

  • The plaintext or ciphertext corresponding to block, obtained using the key. If block is an integer the output will be too. If block is list-like the output will be a bit vector.

EXAMPLES:

sage: from sage.crypto.block_cipher.present import PRESENT
sage: present = PRESENT(doFinalRound=True)
sage: P = 0xFFFFFFFFFFFFFFFF
sage: K = 0x0
sage: present(P, K, 'encrypt').hex()
'a112ffc72f68417b'
decrypt(ciphertext, key)#

Return the plaintext corresponding to the ciphertext, using PRESENT decryption with key.

INPUT:

  • ciphertext – integer or bit list-like; the ciphertext that will be decrypted

  • key – integer or bit list-like; the key

OUTPUT:

  • The plaintext corresponding to ciphertext, obtained using the key. If ciphertext is an integer the output will be too. If ciphertext is list-like the output will be a bit vector.

EXAMPLES:

The test vectors from [BKLPPRSV2007] are checked here:

sage: from sage.crypto.block_cipher.present import PRESENT
sage: present = PRESENT(doFinalRound=True)
sage: p1 = 0x0
sage: k1 = 0x0
sage: c1 = 0x5579C1387B228445
sage: present.decrypt(c1, k1) == p1
True
sage: p2 = 0x0
sage: k2 = 0xFFFFFFFFFFFFFFFFFFFF
sage: c2 = 0xE72C46C0F5945049
sage: present.decrypt(c2, k2) == p2
True
sage: p3 = 0xFFFFFFFFFFFFFFFF
sage: k3 = 0x0
sage: c3 = 0xA112FFC72F68417B
sage: present.decrypt(c3, k3) == p3
True
sage: p4 = 0xFFFFFFFFFFFFFFFF
sage: k4 = 0xFFFFFFFFFFFFFFFFFFFF
sage: c4 = 0x3333DCD3213210D2
sage: present.decrypt(c4, k4) == p4
True
encrypt(plaintext, key)#

Return the ciphertext corresponding to plaintext, using PRESENT encryption with key.

INPUT:

  • plaintext – integer or bit list-like; the plaintext that will be encrypted.

  • key – integer or bit list-like; the key

OUTPUT:

  • The ciphertext corresponding to plaintext, obtained using the key. If plaintext is an integer the output will be too. If plaintext is list-like the output will be a bit vector.

EXAMPLES:

The test vectors from [BKLPPRSV2007] are checked here:

sage: from sage.crypto.block_cipher.present import PRESENT
sage: present = PRESENT(doFinalRound=True)
sage: p1 = 0x0
sage: k1 = 0x0
sage: c1 = 0x5579C1387B228445
sage: present.encrypt(p1, k1) == c1
True
sage: p2 = 0x0
sage: k2 = 0xFFFFFFFFFFFFFFFFFFFF
sage: c2 = 0xE72C46C0F5945049
sage: present.encrypt(p2, k2) == c2
True
sage: p3 = 0xFFFFFFFFFFFFFFFF
sage: k3 = 0x0
sage: c3 = 0xA112FFC72F68417B
sage: present.encrypt(p3, k3) == c3
True
sage: p4 = 0xFFFFFFFFFFFFFFFF
sage: k4 = 0xFFFFFFFFFFFFFFFFFFFF
sage: c4 = 0x3333DCD3213210D2
sage: present.encrypt(p4, k4) == c4
True

ALGORITHM:

Description of the encryption function based on [BKLPPRSV2007]:

A top-level algorithmic description of PRESENT encryption:

generateRoundKeys()
for i = 1 to 31 do
    addRoundkey(STATE, K_i)
    sBoxLayer(STATE)
    pLayer(STATE)
end for
addRoundkey(STATE, K_{32})

Each of the 31 rounds consists of an XOR operation to introduce a round key \(K_i\) for \(1 \leq i \leq 32\), where \(K_{32}\) is used for post-whitening, a linear bitwise permutation and a non-linear substitution layer. The non-linear layer uses a single 4-bit S-box which is applied 16 times in parallel in each round. Each stage but addRoundkey is specified in its corresponding function.

addRoundkey: Given round key \(K_i = \kappa^i_{63} \dots \kappa^i_0\) for \(1 \leq i \leq 32\) and current STATE \(b_{63} \dots b_0\), addRoundkey consists of the operation for \(0 \leq j \leq 63\), \(b_j = b_j \oplus \kappa^i_j\).

linear_layer(state, inverse=False)#

Apply the pLayer of PRESENT to the bit vector state and return the result.

The bit permutation used in PRESENT is given by the following table. Bit \(i\) of STATE is moved to bit position \(P(i)\).

i

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

P(i)

0

16

32

48

1

17

33

49

2

18

34

50

3

19

35

51

i

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

P(i)

4

20

36

52

5

21

37

53

6

22

38

54

7

23

39

55

i

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

P(i)

8

24

40

56

9

25

41

57

10

26

42

58

11

27

43

59

i

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

P(i)

12

28

44

60

13

29

45

61

14

30

46

62

15

31

47

63

EXAMPLES:

sage: from sage.crypto.block_cipher.present import PRESENT
sage: present = PRESENT()
sage: state = vector(GF(2), 64, [0, 1]+[0]*62)
sage: present.linear_layer(state)
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
round(state, round_counter, round_key, inverse=False)#

Apply one round of PRESENT to state and return the result.

EXAMPLES:

sage: from sage.crypto.block_cipher.present import PRESENT
sage: from sage.crypto.block_cipher.present import convert_to_vector
sage: present = PRESENT(128)
sage: k = convert_to_vector(0x0011223344556677, 64)
sage: p = convert_to_vector(0x0123456789abcdef, 64)
sage: ZZ(list(present.round(p, 0, k)), 2).hex()
'ad0ed4ca386b6559'
sbox_layer(state, inverse=False)#

Apply the sBoxLayer of PRESENT to the bit vector state and return the result.

The S-box used in PRESENT is a 4-bit to 4-bit S-box. The action of this box in hexadecimal notation is given by the following table.

x

0

1

2

3

4

5

6

7

8

9

A

B

C

D

E

F

S[x]

C

5

6

B

9

0

A

D

3

E

F

8

4

7

1

2

For sBoxLayer the current STATE \(b_{63} \dots b_0\) is considered as sixteen 4-bit words \(w_{15} \dots w_0\) where \(w_i = b_{4i+3}||b_{4i+2}||b_{4i+1}||b_{4i}\) for \(0 \leq i \leq 15\) and the output nibble S[\(w_i\)] provides the updated state values in the obvious way.

EXAMPLES:

sage: from sage.crypto.block_cipher.present import PRESENT
sage: present = PRESENT()
sage: state = vector(GF(2), 64, [0]*64)
sage: present.sbox_layer(state)
(0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1)
sage: state = vector(GF(2), 64, [1]+[0]*63)
sage: present.sbox_layer(state)
(1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1)

Note

sage.crypto.sbox uses big endian by default whereas most of Sage uses little endian. So to use the big endian PRESENT Sbox from sage.crypto.sboxes sbox_layer() has to do some endian conversion (i.e. reverse input and ouput of the Sbox). Keep this in mind if you change the Sbox or sbox_layer().

class sage.crypto.block_cipher.present.PRESENT_KS(keysize=80, rounds=31, master_key=None)#

Bases: SageObject

This class implements the PRESENT key schedules for both 80-bit and 128-bit keys as described in [BKLPPRSV2007].

EXAMPLES:

Initialise the key schedule with a \(master\_key\) to use it as an iterable:

sage: from sage.crypto.block_cipher.present import PRESENT_KS
sage: ks = PRESENT_KS(master_key=0)
sage: ks[0] == 0x0
True
sage: ks[31] == 0x6dab31744f41d700
True

Or omit the \(master\_key\) and pass a key when calling the key schedule:

sage: ks = PRESENT_KS(keysize=128)
sage: K = ks(0x00112233445566778899aabbccddeeff)
sage: K[0] == 0x0011223344556677
True
sage: K[31] == 0x091989a5ae8eab21
True

ALGORITHM:

Description of the key schedule for 64-bit and 128-bit keys from [BKLPPRSV2007]:

The key schedule for 64-bit keys works as follows:

At round \(i\) the 64-bit round key \(K_i = \kappa_{63}\kappa_{62} \dots \kappa_0\) consists of the 64 leftmost bits of the current contents of register \(K\). Thus at round \(i\) we have that:

\[K_i = \kappa_{63}\kappa_{62}\dots \kappa_0 = k_{79}k_{78}\dots k_{16}.\]

After extracting the round key \(K_i\), the key register \(K = k_{79}k_{78} \dots k_0\) is updated as follows:

\[\begin{split}\begin{aligned} \ [k_{79}k_{78}\dots k_{1}k_{0}] &= [k_{18}k_{17}\dots k_{20}k_{19}] \\ [k_{79}k_{78}k_{77}k_{76}] &= S[k_{79}k_{78}k_{77}k_{76}] \\ [k_{19}k_{18}k_{17}k_{16}k_{15}] &= [k_{19}k_{18}k_{17}k_{16}k_{15}] \oplus round\_counter \end{aligned}\end{split}\]

Thus, the key register is rotated by 61 bit positions to the left, the left-most four bits are passed through the PRESENT S-box, and the round_counter value \(i\) is exclusive-ored with bits \(k_{19}k_{18}k_{17} k_{16}k_{15}\) of \(K\) with the least significant bit of round_counter on the right.

The key schedule for 128-bit keys works as follows:

At round \(i\) the 64-bit round key \(K_i = \kappa_{63}\kappa_{62} \dots \kappa_0\) consists of the 64 leftmost bits fo the current contents of register \(K\). Thus at round \(i\) we have that:

\[K_i = \kappa_{63}\kappa_{62}\dots \kappa_0 = k_{127}k_{126}\dots k_{64}\]

After extracting the round key \(K_i\), the key register \(K = k_{127}k_{126} \dots k_0\) is updated as follows:

\[\begin{split}\begin{aligned} \ [k_{127}k_{126}\dots k_{1}k_{0}] &= [k_{66}k_{65}\dots k_{68}k_{67}] \\ [k_{127}k_{126}k_{125}k_{124}] &= S[k_{127}k_{126}k_{125}k_{124}]\\ [k_{123}k_{122}k_{121}k_{120}] &= S[k_{123}k_{122}k_{121}k_{120}]\\ [k_{66}k_{65}k_{64}k_{63}k_{62}] &= [k_{66}k_{65}k_{64}k_{63}k_{62}] \oplus round\_counter \end{aligned}\end{split}\]

Thus, the key register is rotated by 61 bit positions to the left, the left-most eight bits are passed through two PRESENT S-boxes, and the round_counter value \(i\) is exclusive-ored with bits \(k_{66}k_{65}k_{64} k_{63}k_{62}\) of \(K\) with the least significant bit of round_counter on the right.

Note

sage.crypto.sbox uses big endian by default whereas most of Sage uses little endian. So to use the big endian PRESENT Sbox from sage.crypto.sboxes PRESENT_KS has to do some endian conversion (i.e. reverse input and ouput of the Sbox). Keep this in mind if you change the Sbox or __call__().

__init__(keysize=80, rounds=31, master_key=None)#

Construct an instance of PRESENT_KS.

INPUT:

  • keysize – integer (default: 80); the size of the keys that will be used in bits. It must be either 80 or 128.

  • rounds – integer (default: 31); the number of rounds self can create keys for

  • master_key – integer or bit list-like (default: None); the key that will be used

EXAMPLES:

sage: from sage.crypto.block_cipher.present import PRESENT_KS
sage: PRESENT_KS()
Original PRESENT key schedule with 80-bit keys and 31 rounds

Note

If you want to use a PRESENT_KS object as an iterable you have to pass a master_key value on initialisation. Otherwise you can omit master_key and pass a key when you call the object.

__call__(K)#

Return all round keys in a list.

INPUT:

  • K – integer or bit list-like; the key

OUTPUT:

  • A list containing rounds + 1 round keys. Since addRoundkey takes place in every round and after the last round there must be rounds + 1 round keys. If K is an integer the elements of the output list will be too. If K is list-like the element of the output list will be bit vectors.

EXAMPLES:

sage: from sage.crypto.block_cipher.present import PRESENT_KS
sage: ks = PRESENT_KS()
sage: ks(0)[31] == 0x6dab31744f41d700
True

Note

If you want to use a PRESENT_KS object as an iterable you have to pass a master_key value on initialisation. Otherwise you can omit master_key and pass a key when you call the object.

sage.crypto.block_cipher.present.convert_to_vector(I, L)#

Convert I to a bit vector of length L.

INPUT:

  • I – integer or bit list-like

  • L – integer; the desired bit length of the ouput

OUTPUT:

  • the L-bit vector representation of I

EXAMPLES:

sage: from sage.crypto.block_cipher.present import convert_to_vector
sage: convert_to_vector(0x1F, 8)
(1, 1, 1, 1, 1, 0, 0, 0)
sage: v = vector(GF(2), 4, [1,0,1,0])
sage: convert_to_vector(v, 4)
(1, 0, 1, 0)