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
See also
- __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. Use80
or128
as a shortcut for the original key schedules from [BKLPPRSV2007].rounds
– integer (default:None
); the number of rounds. IfNone
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
- __call__(block, key, algorithm='encrypt')#
Apply PRESENT encryption or decryption on
block
usingkey
. The flagalgorithm
controls what action is to be performed onblock
.INPUT:
block
– integer or bit list-like; the plaintext or ciphertextK
– integer or bit list-like; the keyalgorithm
– string (default:'encrypt'
); a flag to signify whether encryption or decryption is to be applied toB
. The encryption flag is'encrypt'
and the decryption flag is'decrypt'
OUTPUT:
The plaintext or ciphertext corresponding to
block
, obtained using thekey
. Ifblock
is an integer the output will be too. Ifblock
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 withkey
.INPUT:
ciphertext
– integer or bit list-like; the ciphertext that will be decryptedkey
– integer or bit list-like; the key
OUTPUT:
The plaintext corresponding to
ciphertext
, obtained using thekey
. Ifciphertext
is an integer the output will be too. Ifciphertext
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 withkey
.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 thekey
. Ifplaintext
is an integer the output will be too. Ifplaintext
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 fromsage.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 orsbox_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.
See also
Note
sage.crypto.sbox
uses big endian by default whereas most of Sage uses little endian. So to use the big endian PRESENT Sbox fromsage.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 roundsself
can create keys formaster_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 omitmaster_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 berounds + 1
round keys. IfK
is an integer the elements of the output list will be too. IfK
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 omitmaster_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 lengthL
.INPUT:
I
– integer or bit list-likeL
– integer; the desired bit length of the ouput
OUTPUT:
the
L
-bit vector representation ofI
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)