purepython-aes 0.3.0__py3-none-any.whl → 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- purepython_aes/__init__.py +6 -0
- purepython_aes/aes/__init__.py +13 -3
- purepython_aes/aes/core/__init__.py +19 -2
- purepython_aes/aes/core/fast/__init__.py +4 -0
- purepython_aes/aes/core/fast/aes.py +214 -0
- purepython_aes/aes/core/fast/algorithms/__init__.py +5 -0
- purepython_aes/aes/{algorithms → core/fast/algorithms}/aes128.py +2 -2
- purepython_aes/aes/{algorithms → core/fast/algorithms}/aes192.py +2 -2
- purepython_aes/aes/{algorithms → core/fast/algorithms}/aes256.py +2 -2
- purepython_aes/aes/core/fast/expansion.py +156 -0
- purepython_aes/aes/core/fast/keys.py +49 -0
- purepython_aes/aes/core/fast/ttables.py +533 -0
- purepython_aes/aes/core/reference/__init__.py +13 -0
- purepython_aes/aes/core/{aes.py → reference/aes.py} +3 -3
- purepython_aes/aes/core/reference/algorithms/__init__.py +5 -0
- purepython_aes/aes/core/reference/algorithms/aes128.py +13 -0
- purepython_aes/aes/core/reference/algorithms/aes192.py +13 -0
- purepython_aes/aes/core/reference/algorithms/aes256.py +13 -0
- purepython_aes/aes/core/{state.py → reference/state.py} +1 -3
- purepython_aes/aes/modes/_base.py +1 -1
- purepython_aes/types/__init__.py +2 -2
- purepython_aes/types/finite/__init__.py +2 -2
- purepython_aes/types/finite/aliases.py +7 -0
- {purepython_aes-0.3.0.dist-info → purepython_aes-0.3.1.dist-info}/METADATA +1 -1
- purepython_aes-0.3.1.dist-info/RECORD +51 -0
- {purepython_aes-0.3.0.dist-info → purepython_aes-0.3.1.dist-info}/scm_file_list.json +40 -18
- purepython_aes-0.3.1.dist-info/scm_version.json +8 -0
- purepython_aes/aes/algorithms/__init__.py +0 -5
- purepython_aes-0.3.0.dist-info/RECORD +0 -41
- purepython_aes-0.3.0.dist-info/scm_version.json +0 -8
- /purepython_aes/aes/{interface.py → core/interface.py} +0 -0
- /purepython_aes/aes/core/{expansion.py → reference/expansion.py} +0 -0
- /purepython_aes/aes/core/{operations.py → reference/operations.py} +0 -0
- {purepython_aes-0.3.0.dist-info → purepython_aes-0.3.1.dist-info}/WHEEL +0 -0
- {purepython_aes-0.3.0.dist-info → purepython_aes-0.3.1.dist-info}/top_level.txt +0 -0
purepython_aes/__init__.py
CHANGED
|
@@ -14,6 +14,9 @@ from purepython_aes.aes import (
|
|
|
14
14
|
NoPadding,
|
|
15
15
|
PcbcMode,
|
|
16
16
|
Pkcs7Padding,
|
|
17
|
+
ReferenceAes128,
|
|
18
|
+
ReferenceAes192,
|
|
19
|
+
ReferenceAes256,
|
|
17
20
|
ZeroPadding,
|
|
18
21
|
)
|
|
19
22
|
|
|
@@ -33,5 +36,8 @@ __all__: list[str] = [
|
|
|
33
36
|
'NoPadding',
|
|
34
37
|
'PcbcMode',
|
|
35
38
|
'Pkcs7Padding',
|
|
39
|
+
'ReferenceAes128',
|
|
40
|
+
'ReferenceAes192',
|
|
41
|
+
'ReferenceAes256',
|
|
36
42
|
'ZeroPadding',
|
|
37
43
|
]
|
purepython_aes/aes/__init__.py
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
from purepython_aes.aes.
|
|
2
|
-
|
|
1
|
+
from purepython_aes.aes.core import (
|
|
2
|
+
Aes,
|
|
3
|
+
Aes128,
|
|
4
|
+
Aes192,
|
|
5
|
+
Aes256,
|
|
6
|
+
ReferenceAes128,
|
|
7
|
+
ReferenceAes192,
|
|
8
|
+
ReferenceAes256,
|
|
9
|
+
)
|
|
3
10
|
from purepython_aes.aes.modes import (
|
|
4
11
|
AesMode,
|
|
5
12
|
BlockCipherMode,
|
|
@@ -18,10 +25,13 @@ from purepython_aes.aes.padding import (
|
|
|
18
25
|
)
|
|
19
26
|
|
|
20
27
|
__all__: list[str] = [
|
|
28
|
+
'Aes',
|
|
21
29
|
'Aes128',
|
|
22
30
|
'Aes192',
|
|
23
31
|
'Aes256',
|
|
24
|
-
'
|
|
32
|
+
'ReferenceAes128',
|
|
33
|
+
'ReferenceAes192',
|
|
34
|
+
'ReferenceAes256',
|
|
25
35
|
'AesMode',
|
|
26
36
|
'BlockCipherMode',
|
|
27
37
|
'CbcMode',
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
-
from purepython_aes.aes.core.
|
|
1
|
+
from purepython_aes.aes.core.fast import Aes128, Aes192, Aes256, FastAesCore
|
|
2
|
+
from purepython_aes.aes.core.interface import Aes
|
|
3
|
+
from purepython_aes.aes.core.reference import (
|
|
4
|
+
AesCore,
|
|
5
|
+
ReferenceAes128,
|
|
6
|
+
ReferenceAes192,
|
|
7
|
+
ReferenceAes256,
|
|
8
|
+
)
|
|
2
9
|
|
|
3
|
-
__all__: list[str] = [
|
|
10
|
+
__all__: list[str] = [
|
|
11
|
+
'FastAesCore',
|
|
12
|
+
'Aes128',
|
|
13
|
+
'Aes192',
|
|
14
|
+
'Aes256',
|
|
15
|
+
'Aes',
|
|
16
|
+
'AesCore',
|
|
17
|
+
'ReferenceAes128',
|
|
18
|
+
'ReferenceAes192',
|
|
19
|
+
'ReferenceAes256',
|
|
20
|
+
]
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from purepython_aes.aes.core.fast.expansion import (
|
|
4
|
+
expand_aes128_key,
|
|
5
|
+
expand_aes192_key,
|
|
6
|
+
expand_aes256_key,
|
|
7
|
+
)
|
|
8
|
+
from purepython_aes.aes.core.fast.keys import (
|
|
9
|
+
build_decryption_round_keys,
|
|
10
|
+
RoundKey,
|
|
11
|
+
RoundKeys,
|
|
12
|
+
)
|
|
13
|
+
from purepython_aes.aes.core.fast.ttables import (
|
|
14
|
+
DECRYPTION_TABLE_0,
|
|
15
|
+
DECRYPTION_TABLE_1,
|
|
16
|
+
DECRYPTION_TABLE_2,
|
|
17
|
+
DECRYPTION_TABLE_3,
|
|
18
|
+
ENCRYPTION_TABLE_0,
|
|
19
|
+
ENCRYPTION_TABLE_1,
|
|
20
|
+
ENCRYPTION_TABLE_2,
|
|
21
|
+
ENCRYPTION_TABLE_3,
|
|
22
|
+
)
|
|
23
|
+
from purepython_aes.aes.core.interface import Aes
|
|
24
|
+
from purepython_aes.const import (
|
|
25
|
+
AES_128_KEY_SIZE,
|
|
26
|
+
AES_192_KEY_SIZE,
|
|
27
|
+
AES_256_KEY_SIZE,
|
|
28
|
+
AES_BLOCK_SIZE,
|
|
29
|
+
INVERSE_SBOX,
|
|
30
|
+
SBOX,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(init=False, slots=True)
|
|
35
|
+
class FastAesCore(Aes):
|
|
36
|
+
"""Implement optimized `encrypt_block` and `decrypt_block` methods."""
|
|
37
|
+
|
|
38
|
+
__decryption_round_keys: RoundKeys
|
|
39
|
+
__encryption_round_keys: RoundKeys
|
|
40
|
+
|
|
41
|
+
def __init__(self, key: bytes) -> None:
|
|
42
|
+
if len(key) != self.__key_size__:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f'expected len(key) == {self.__key_size__}, got {len(key)}'
|
|
45
|
+
)
|
|
46
|
+
key_size: int = len(key)
|
|
47
|
+
if key_size == AES_128_KEY_SIZE:
|
|
48
|
+
self.__encryption_round_keys = expand_aes128_key(key)
|
|
49
|
+
if key_size == AES_192_KEY_SIZE:
|
|
50
|
+
self.__encryption_round_keys = expand_aes192_key(key)
|
|
51
|
+
if key_size == AES_256_KEY_SIZE:
|
|
52
|
+
self.__encryption_round_keys = expand_aes256_key(key)
|
|
53
|
+
self.__decryption_round_keys = build_decryption_round_keys(
|
|
54
|
+
self.__encryption_round_keys
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def encrypt_block(self, plaintext: bytes) -> bytes:
|
|
58
|
+
"""Encrypt one 16-byte block using fused T-table rounds."""
|
|
59
|
+
|
|
60
|
+
if len(plaintext) != AES_BLOCK_SIZE:
|
|
61
|
+
raise ValueError(
|
|
62
|
+
f'expected len(plaintext) == {AES_BLOCK_SIZE}, got {len(plaintext)}'
|
|
63
|
+
)
|
|
64
|
+
round_keys: RoundKeys = self.__encryption_round_keys
|
|
65
|
+
plaintext_integer: int = int.from_bytes(plaintext, byteorder='big')
|
|
66
|
+
initial_round_key: RoundKey = round_keys[0]
|
|
67
|
+
state_0: int = (plaintext_integer >> 96) ^ initial_round_key[0]
|
|
68
|
+
state_1: int = ((plaintext_integer >> 64) & 0xFFFFFFFF) ^ initial_round_key[1]
|
|
69
|
+
state_2: int = ((plaintext_integer >> 32) & 0xFFFFFFFF) ^ initial_round_key[2]
|
|
70
|
+
state_3: int = (plaintext_integer & 0xFFFFFFFF) ^ initial_round_key[3]
|
|
71
|
+
for round_index in range(1, self.__round_count__):
|
|
72
|
+
round_key: RoundKey = round_keys[round_index]
|
|
73
|
+
next_state_0: int = (
|
|
74
|
+
ENCRYPTION_TABLE_0[(state_0 >> 24) & 0xFF]
|
|
75
|
+
^ ENCRYPTION_TABLE_1[(state_1 >> 16) & 0xFF]
|
|
76
|
+
^ ENCRYPTION_TABLE_2[(state_2 >> 8) & 0xFF]
|
|
77
|
+
^ ENCRYPTION_TABLE_3[state_3 & 0xFF]
|
|
78
|
+
^ round_key[0]
|
|
79
|
+
)
|
|
80
|
+
next_state_1: int = (
|
|
81
|
+
ENCRYPTION_TABLE_0[(state_1 >> 24) & 0xFF]
|
|
82
|
+
^ ENCRYPTION_TABLE_1[(state_2 >> 16) & 0xFF]
|
|
83
|
+
^ ENCRYPTION_TABLE_2[(state_3 >> 8) & 0xFF]
|
|
84
|
+
^ ENCRYPTION_TABLE_3[state_0 & 0xFF]
|
|
85
|
+
^ round_key[1]
|
|
86
|
+
)
|
|
87
|
+
next_state_2: int = (
|
|
88
|
+
ENCRYPTION_TABLE_0[(state_2 >> 24) & 0xFF]
|
|
89
|
+
^ ENCRYPTION_TABLE_1[(state_3 >> 16) & 0xFF]
|
|
90
|
+
^ ENCRYPTION_TABLE_2[(state_0 >> 8) & 0xFF]
|
|
91
|
+
^ ENCRYPTION_TABLE_3[state_1 & 0xFF]
|
|
92
|
+
^ round_key[2]
|
|
93
|
+
)
|
|
94
|
+
next_state_3: int = (
|
|
95
|
+
ENCRYPTION_TABLE_0[(state_3 >> 24) & 0xFF]
|
|
96
|
+
^ ENCRYPTION_TABLE_1[(state_0 >> 16) & 0xFF]
|
|
97
|
+
^ ENCRYPTION_TABLE_2[(state_1 >> 8) & 0xFF]
|
|
98
|
+
^ ENCRYPTION_TABLE_3[state_2 & 0xFF]
|
|
99
|
+
^ round_key[3]
|
|
100
|
+
)
|
|
101
|
+
state_0 = next_state_0
|
|
102
|
+
state_1 = next_state_1
|
|
103
|
+
state_2 = next_state_2
|
|
104
|
+
state_3 = next_state_3
|
|
105
|
+
final_round_key: RoundKey = round_keys[self.__round_count__]
|
|
106
|
+
output_0: int = (
|
|
107
|
+
(SBOX[(state_0 >> 24) & 0xFF] << 24)
|
|
108
|
+
| (SBOX[(state_1 >> 16) & 0xFF] << 16)
|
|
109
|
+
| (SBOX[(state_2 >> 8) & 0xFF] << 8)
|
|
110
|
+
| SBOX[state_3 & 0xFF]
|
|
111
|
+
) ^ final_round_key[0]
|
|
112
|
+
output_1: int = (
|
|
113
|
+
(SBOX[(state_1 >> 24) & 0xFF] << 24)
|
|
114
|
+
| (SBOX[(state_2 >> 16) & 0xFF] << 16)
|
|
115
|
+
| (SBOX[(state_3 >> 8) & 0xFF] << 8)
|
|
116
|
+
| SBOX[state_0 & 0xFF]
|
|
117
|
+
) ^ final_round_key[1]
|
|
118
|
+
output_2: int = (
|
|
119
|
+
(SBOX[(state_2 >> 24) & 0xFF] << 24)
|
|
120
|
+
| (SBOX[(state_3 >> 16) & 0xFF] << 16)
|
|
121
|
+
| (SBOX[(state_0 >> 8) & 0xFF] << 8)
|
|
122
|
+
| SBOX[state_1 & 0xFF]
|
|
123
|
+
) ^ final_round_key[2]
|
|
124
|
+
output_3: int = (
|
|
125
|
+
(SBOX[(state_3 >> 24) & 0xFF] << 24)
|
|
126
|
+
| (SBOX[(state_0 >> 16) & 0xFF] << 16)
|
|
127
|
+
| (SBOX[(state_1 >> 8) & 0xFF] << 8)
|
|
128
|
+
| SBOX[state_2 & 0xFF]
|
|
129
|
+
) ^ final_round_key[3]
|
|
130
|
+
output_integer: int = (
|
|
131
|
+
(output_0 << 96) | (output_1 << 64) | (output_2 << 32) | output_3
|
|
132
|
+
)
|
|
133
|
+
return output_integer.to_bytes(AES_BLOCK_SIZE, byteorder='big')
|
|
134
|
+
|
|
135
|
+
def decrypt_block(self, ciphertext: bytes) -> bytes:
|
|
136
|
+
"""Decrypt one block using equivalent-inverse T-table rounds."""
|
|
137
|
+
|
|
138
|
+
if len(ciphertext) != AES_BLOCK_SIZE:
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f'expected len(ciphertext) == {AES_BLOCK_SIZE}, got {len(ciphertext)}'
|
|
141
|
+
)
|
|
142
|
+
round_keys: RoundKeys = self.__decryption_round_keys
|
|
143
|
+
ciphertext_integer: int = int.from_bytes(
|
|
144
|
+
ciphertext,
|
|
145
|
+
byteorder='big',
|
|
146
|
+
)
|
|
147
|
+
initial_round_key: RoundKey = round_keys[0]
|
|
148
|
+
state_0: int = (ciphertext_integer >> 96) ^ initial_round_key[0]
|
|
149
|
+
state_1: int = ((ciphertext_integer >> 64) & 0xFFFFFFFF) ^ initial_round_key[1]
|
|
150
|
+
state_2: int = ((ciphertext_integer >> 32) & 0xFFFFFFFF) ^ initial_round_key[2]
|
|
151
|
+
state_3: int = (ciphertext_integer & 0xFFFFFFFF) ^ initial_round_key[3]
|
|
152
|
+
for round_index in range(1, self.__round_count__):
|
|
153
|
+
round_key: RoundKey = round_keys[round_index]
|
|
154
|
+
next_state_0: int = (
|
|
155
|
+
DECRYPTION_TABLE_0[(state_0 >> 24) & 0xFF]
|
|
156
|
+
^ DECRYPTION_TABLE_1[(state_3 >> 16) & 0xFF]
|
|
157
|
+
^ DECRYPTION_TABLE_2[(state_2 >> 8) & 0xFF]
|
|
158
|
+
^ DECRYPTION_TABLE_3[state_1 & 0xFF]
|
|
159
|
+
^ round_key[0]
|
|
160
|
+
)
|
|
161
|
+
next_state_1: int = (
|
|
162
|
+
DECRYPTION_TABLE_0[(state_1 >> 24) & 0xFF]
|
|
163
|
+
^ DECRYPTION_TABLE_1[(state_0 >> 16) & 0xFF]
|
|
164
|
+
^ DECRYPTION_TABLE_2[(state_3 >> 8) & 0xFF]
|
|
165
|
+
^ DECRYPTION_TABLE_3[state_2 & 0xFF]
|
|
166
|
+
^ round_key[1]
|
|
167
|
+
)
|
|
168
|
+
next_state_2: int = (
|
|
169
|
+
DECRYPTION_TABLE_0[(state_2 >> 24) & 0xFF]
|
|
170
|
+
^ DECRYPTION_TABLE_1[(state_1 >> 16) & 0xFF]
|
|
171
|
+
^ DECRYPTION_TABLE_2[(state_0 >> 8) & 0xFF]
|
|
172
|
+
^ DECRYPTION_TABLE_3[state_3 & 0xFF]
|
|
173
|
+
^ round_key[2]
|
|
174
|
+
)
|
|
175
|
+
next_state_3: int = (
|
|
176
|
+
DECRYPTION_TABLE_0[(state_3 >> 24) & 0xFF]
|
|
177
|
+
^ DECRYPTION_TABLE_1[(state_2 >> 16) & 0xFF]
|
|
178
|
+
^ DECRYPTION_TABLE_2[(state_1 >> 8) & 0xFF]
|
|
179
|
+
^ DECRYPTION_TABLE_3[state_0 & 0xFF]
|
|
180
|
+
^ round_key[3]
|
|
181
|
+
)
|
|
182
|
+
state_0 = next_state_0
|
|
183
|
+
state_1 = next_state_1
|
|
184
|
+
state_2 = next_state_2
|
|
185
|
+
state_3 = next_state_3
|
|
186
|
+
final_round_key: RoundKey = round_keys[self.__round_count__]
|
|
187
|
+
output_0: int = (
|
|
188
|
+
(INVERSE_SBOX[(state_0 >> 24) & 0xFF] << 24)
|
|
189
|
+
| (INVERSE_SBOX[(state_3 >> 16) & 0xFF] << 16)
|
|
190
|
+
| (INVERSE_SBOX[(state_2 >> 8) & 0xFF] << 8)
|
|
191
|
+
| INVERSE_SBOX[state_1 & 0xFF]
|
|
192
|
+
) ^ final_round_key[0]
|
|
193
|
+
output_1: int = (
|
|
194
|
+
(INVERSE_SBOX[(state_1 >> 24) & 0xFF] << 24)
|
|
195
|
+
| (INVERSE_SBOX[(state_0 >> 16) & 0xFF] << 16)
|
|
196
|
+
| (INVERSE_SBOX[(state_3 >> 8) & 0xFF] << 8)
|
|
197
|
+
| INVERSE_SBOX[state_2 & 0xFF]
|
|
198
|
+
) ^ final_round_key[1]
|
|
199
|
+
output_2: int = (
|
|
200
|
+
(INVERSE_SBOX[(state_2 >> 24) & 0xFF] << 24)
|
|
201
|
+
| (INVERSE_SBOX[(state_1 >> 16) & 0xFF] << 16)
|
|
202
|
+
| (INVERSE_SBOX[(state_0 >> 8) & 0xFF] << 8)
|
|
203
|
+
| INVERSE_SBOX[state_3 & 0xFF]
|
|
204
|
+
) ^ final_round_key[2]
|
|
205
|
+
output_3: int = (
|
|
206
|
+
(INVERSE_SBOX[(state_3 >> 24) & 0xFF] << 24)
|
|
207
|
+
| (INVERSE_SBOX[(state_2 >> 16) & 0xFF] << 16)
|
|
208
|
+
| (INVERSE_SBOX[(state_1 >> 8) & 0xFF] << 8)
|
|
209
|
+
| INVERSE_SBOX[state_0 & 0xFF]
|
|
210
|
+
) ^ final_round_key[3]
|
|
211
|
+
output_integer: int = (
|
|
212
|
+
(output_0 << 96) | (output_1 << 64) | (output_2 << 32) | output_3
|
|
213
|
+
)
|
|
214
|
+
return output_integer.to_bytes(AES_BLOCK_SIZE, byteorder='big')
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from typing import ClassVar
|
|
3
3
|
|
|
4
|
-
from purepython_aes.aes.core import
|
|
4
|
+
from purepython_aes.aes.core.fast.aes import FastAesCore
|
|
5
5
|
from purepython_aes.const import AES_128_KEY_SIZE, AES_128_ROUND_COUNT
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
@dataclass(init=False, slots=True)
|
|
9
|
-
class Aes128(
|
|
9
|
+
class Aes128(FastAesCore):
|
|
10
10
|
"""AES-128 encryption algorithm."""
|
|
11
11
|
|
|
12
12
|
__key_size__: ClassVar[int] = AES_128_KEY_SIZE
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from typing import ClassVar
|
|
3
3
|
|
|
4
|
-
from purepython_aes.aes.core import
|
|
4
|
+
from purepython_aes.aes.core.fast.aes import FastAesCore
|
|
5
5
|
from purepython_aes.const import AES_192_KEY_SIZE, AES_192_ROUND_COUNT
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
@dataclass(init=False, slots=True)
|
|
9
|
-
class Aes192(
|
|
9
|
+
class Aes192(FastAesCore):
|
|
10
10
|
"""AES-192 encryption algorithm."""
|
|
11
11
|
|
|
12
12
|
__key_size__: ClassVar[int] = AES_192_KEY_SIZE
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from typing import ClassVar
|
|
3
3
|
|
|
4
|
-
from purepython_aes.aes.core import
|
|
4
|
+
from purepython_aes.aes.core.fast.aes import FastAesCore
|
|
5
5
|
from purepython_aes.const import AES_256_KEY_SIZE, AES_256_ROUND_COUNT
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
@dataclass(init=False, slots=True)
|
|
9
|
-
class Aes256(
|
|
9
|
+
class Aes256(FastAesCore):
|
|
10
10
|
"""AES-256 encryption algorithm."""
|
|
11
11
|
|
|
12
12
|
__key_size__: ClassVar[int] = AES_256_KEY_SIZE
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
from struct import unpack
|
|
2
|
+
from typing import Final
|
|
3
|
+
|
|
4
|
+
from purepython_aes.const import AES_128_ROUND_COUNT, AES_256_ROUND_COUNT, SBOX
|
|
5
|
+
from purepython_aes.types import RoundKey, RoundKeys
|
|
6
|
+
|
|
7
|
+
AES_192_EXPANSION_COUNT: Final[int] = 8
|
|
8
|
+
|
|
9
|
+
ROUND_CONSTANT_WORDS: Final[tuple[int, ...]] = (
|
|
10
|
+
0x00000000,
|
|
11
|
+
0x01000000,
|
|
12
|
+
0x02000000,
|
|
13
|
+
0x04000000,
|
|
14
|
+
0x08000000,
|
|
15
|
+
0x10000000,
|
|
16
|
+
0x20000000,
|
|
17
|
+
0x40000000,
|
|
18
|
+
0x80000000,
|
|
19
|
+
0x1B000000,
|
|
20
|
+
0x36000000,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def expand_aes128_key(key: bytes) -> RoundKeys:
|
|
25
|
+
"""Expand one AES-128 key directly into four-word round keys."""
|
|
26
|
+
|
|
27
|
+
initial_words: tuple[int, ...] = unpack('>4I', key)
|
|
28
|
+
word_0: int = initial_words[0]
|
|
29
|
+
word_1: int = initial_words[1]
|
|
30
|
+
word_2: int = initial_words[2]
|
|
31
|
+
word_3: int = initial_words[3]
|
|
32
|
+
round_keys: list[RoundKey] = [initial_words] # type: ignore[list-item]
|
|
33
|
+
for round_index in range(1, AES_128_ROUND_COUNT + 1):
|
|
34
|
+
transformed_word: int = (
|
|
35
|
+
(SBOX[(word_3 >> 16) & 0xFF] << 24)
|
|
36
|
+
| (SBOX[(word_3 >> 8) & 0xFF] << 16)
|
|
37
|
+
| (SBOX[word_3 & 0xFF] << 8)
|
|
38
|
+
| SBOX[(word_3 >> 24) & 0xFF]
|
|
39
|
+
) ^ ROUND_CONSTANT_WORDS[round_index]
|
|
40
|
+
word_0 ^= transformed_word
|
|
41
|
+
word_1 ^= word_0
|
|
42
|
+
word_2 ^= word_1
|
|
43
|
+
word_3 ^= word_2
|
|
44
|
+
round_keys.append((word_0, word_1, word_2, word_3))
|
|
45
|
+
return round_keys
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def expand_aes192_key(key: bytes) -> RoundKeys:
|
|
49
|
+
"""Expand one AES-192 key directly into four-word round keys."""
|
|
50
|
+
|
|
51
|
+
(
|
|
52
|
+
source_word_0,
|
|
53
|
+
source_word_1,
|
|
54
|
+
source_word_2,
|
|
55
|
+
source_word_3,
|
|
56
|
+
source_word_4,
|
|
57
|
+
source_word_5,
|
|
58
|
+
) = unpack('>6I', key)
|
|
59
|
+
leftover_word_0: int = source_word_4
|
|
60
|
+
leftover_word_1: int = source_word_5
|
|
61
|
+
round_keys: list[RoundKey] = [
|
|
62
|
+
(source_word_0, source_word_1, source_word_2, source_word_3),
|
|
63
|
+
]
|
|
64
|
+
for round_constant_index in range(1, AES_192_EXPANSION_COUNT):
|
|
65
|
+
transformed_word: int = (
|
|
66
|
+
(SBOX[(source_word_5 >> 16) & 0xFF] << 24)
|
|
67
|
+
| (SBOX[(source_word_5 >> 8) & 0xFF] << 16)
|
|
68
|
+
| (SBOX[source_word_5 & 0xFF] << 8)
|
|
69
|
+
| SBOX[(source_word_5 >> 24) & 0xFF]
|
|
70
|
+
) ^ ROUND_CONSTANT_WORDS[round_constant_index]
|
|
71
|
+
next_word_0: int = source_word_0 ^ transformed_word
|
|
72
|
+
next_word_1: int = source_word_1 ^ next_word_0
|
|
73
|
+
next_word_2: int = source_word_2 ^ next_word_1
|
|
74
|
+
next_word_3: int = source_word_3 ^ next_word_2
|
|
75
|
+
next_word_4: int = source_word_4 ^ next_word_3
|
|
76
|
+
next_word_5: int = source_word_5 ^ next_word_4
|
|
77
|
+
if round_constant_index % 2 == 1:
|
|
78
|
+
round_keys.append(
|
|
79
|
+
(leftover_word_0, leftover_word_1, next_word_0, next_word_1),
|
|
80
|
+
)
|
|
81
|
+
round_keys.append((next_word_2, next_word_3, next_word_4, next_word_5))
|
|
82
|
+
else:
|
|
83
|
+
round_keys.append((next_word_0, next_word_1, next_word_2, next_word_3))
|
|
84
|
+
leftover_word_0 = next_word_4
|
|
85
|
+
leftover_word_1 = next_word_5
|
|
86
|
+
source_word_0 = next_word_0
|
|
87
|
+
source_word_1 = next_word_1
|
|
88
|
+
source_word_2 = next_word_2
|
|
89
|
+
source_word_3 = next_word_3
|
|
90
|
+
source_word_4 = next_word_4
|
|
91
|
+
source_word_5 = next_word_5
|
|
92
|
+
final_transformed_word: int = (
|
|
93
|
+
(SBOX[(source_word_5 >> 16) & 0xFF] << 24)
|
|
94
|
+
| (SBOX[(source_word_5 >> 8) & 0xFF] << 16)
|
|
95
|
+
| (SBOX[source_word_5 & 0xFF] << 8)
|
|
96
|
+
| SBOX[(source_word_5 >> 24) & 0xFF]
|
|
97
|
+
) ^ ROUND_CONSTANT_WORDS[AES_192_EXPANSION_COUNT]
|
|
98
|
+
final_word_0: int = source_word_0 ^ final_transformed_word
|
|
99
|
+
final_word_1: int = source_word_1 ^ final_word_0
|
|
100
|
+
final_word_2: int = source_word_2 ^ final_word_1
|
|
101
|
+
round_keys.append(
|
|
102
|
+
(final_word_0, final_word_1, final_word_2, source_word_3 ^ final_word_2),
|
|
103
|
+
)
|
|
104
|
+
return round_keys
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def expand_aes256_key(key: bytes) -> RoundKeys:
|
|
108
|
+
"""Expand one AES-256 key directly into four-word round keys."""
|
|
109
|
+
|
|
110
|
+
(
|
|
111
|
+
source_word_0,
|
|
112
|
+
source_word_1,
|
|
113
|
+
source_word_2,
|
|
114
|
+
source_word_3,
|
|
115
|
+
previous_word_0,
|
|
116
|
+
previous_word_1,
|
|
117
|
+
previous_word_2,
|
|
118
|
+
previous_word_3,
|
|
119
|
+
) = unpack('>8I', key)
|
|
120
|
+
round_keys: list[RoundKey] = [
|
|
121
|
+
(source_word_0, source_word_1, source_word_2, source_word_3),
|
|
122
|
+
(previous_word_0, previous_word_1, previous_word_2, previous_word_3),
|
|
123
|
+
]
|
|
124
|
+
for round_index in range(2, AES_256_ROUND_COUNT + 1):
|
|
125
|
+
transformed_word: int = (
|
|
126
|
+
(
|
|
127
|
+
(
|
|
128
|
+
(SBOX[(previous_word_3 >> 16) & 0xFF] << 24)
|
|
129
|
+
| (SBOX[(previous_word_3 >> 8) & 0xFF] << 16)
|
|
130
|
+
| (SBOX[previous_word_3 & 0xFF] << 8)
|
|
131
|
+
| SBOX[(previous_word_3 >> 24) & 0xFF]
|
|
132
|
+
)
|
|
133
|
+
^ ROUND_CONSTANT_WORDS[round_index >> 1]
|
|
134
|
+
)
|
|
135
|
+
if round_index & 0x01 == 0
|
|
136
|
+
else (
|
|
137
|
+
(SBOX[(previous_word_3 >> 24) & 0xFF] << 24)
|
|
138
|
+
| (SBOX[(previous_word_3 >> 16) & 0xFF] << 16)
|
|
139
|
+
| (SBOX[(previous_word_3 >> 8) & 0xFF] << 8)
|
|
140
|
+
| SBOX[previous_word_3 & 0xFF]
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
next_word_0: int = source_word_0 ^ transformed_word
|
|
144
|
+
next_word_1: int = source_word_1 ^ next_word_0
|
|
145
|
+
next_word_2: int = source_word_2 ^ next_word_1
|
|
146
|
+
next_word_3: int = source_word_3 ^ next_word_2
|
|
147
|
+
round_keys.append((next_word_0, next_word_1, next_word_2, next_word_3))
|
|
148
|
+
source_word_0 = previous_word_0
|
|
149
|
+
source_word_1 = previous_word_1
|
|
150
|
+
source_word_2 = previous_word_2
|
|
151
|
+
source_word_3 = previous_word_3
|
|
152
|
+
previous_word_0 = next_word_0
|
|
153
|
+
previous_word_1 = next_word_1
|
|
154
|
+
previous_word_2 = next_word_2
|
|
155
|
+
previous_word_3 = next_word_3
|
|
156
|
+
return round_keys
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from purepython_aes.aes.core.fast.ttables import (
|
|
2
|
+
DECRYPTION_TABLE_0,
|
|
3
|
+
DECRYPTION_TABLE_1,
|
|
4
|
+
DECRYPTION_TABLE_2,
|
|
5
|
+
DECRYPTION_TABLE_3,
|
|
6
|
+
)
|
|
7
|
+
from purepython_aes.const import SBOX
|
|
8
|
+
from purepython_aes.types import RoundKey, RoundKeys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def build_decryption_round_keys(
|
|
12
|
+
encryption_round_keys: RoundKeys,
|
|
13
|
+
) -> RoundKeys:
|
|
14
|
+
"""Build the equivalent-inverse decryption key schedule."""
|
|
15
|
+
|
|
16
|
+
round_count: int = len(encryption_round_keys) - 1
|
|
17
|
+
decryption_round_keys: list[RoundKey] = [encryption_round_keys[round_count]]
|
|
18
|
+
for encryption_round_index in range(round_count - 1, 0, -1):
|
|
19
|
+
word_0, word_1, word_2, word_3 = encryption_round_keys[encryption_round_index]
|
|
20
|
+
decryption_round_keys.append(
|
|
21
|
+
(
|
|
22
|
+
(
|
|
23
|
+
DECRYPTION_TABLE_0[SBOX[(word_0 >> 24) & 0xFF]]
|
|
24
|
+
^ DECRYPTION_TABLE_1[SBOX[(word_0 >> 16) & 0xFF]]
|
|
25
|
+
^ DECRYPTION_TABLE_2[SBOX[(word_0 >> 8) & 0xFF]]
|
|
26
|
+
^ DECRYPTION_TABLE_3[SBOX[word_0 & 0xFF]]
|
|
27
|
+
),
|
|
28
|
+
(
|
|
29
|
+
DECRYPTION_TABLE_0[SBOX[(word_1 >> 24) & 0xFF]]
|
|
30
|
+
^ DECRYPTION_TABLE_1[SBOX[(word_1 >> 16) & 0xFF]]
|
|
31
|
+
^ DECRYPTION_TABLE_2[SBOX[(word_1 >> 8) & 0xFF]]
|
|
32
|
+
^ DECRYPTION_TABLE_3[SBOX[word_1 & 0xFF]]
|
|
33
|
+
),
|
|
34
|
+
(
|
|
35
|
+
DECRYPTION_TABLE_0[SBOX[(word_2 >> 24) & 0xFF]]
|
|
36
|
+
^ DECRYPTION_TABLE_1[SBOX[(word_2 >> 16) & 0xFF]]
|
|
37
|
+
^ DECRYPTION_TABLE_2[SBOX[(word_2 >> 8) & 0xFF]]
|
|
38
|
+
^ DECRYPTION_TABLE_3[SBOX[word_2 & 0xFF]]
|
|
39
|
+
),
|
|
40
|
+
(
|
|
41
|
+
DECRYPTION_TABLE_0[SBOX[(word_3 >> 24) & 0xFF]]
|
|
42
|
+
^ DECRYPTION_TABLE_1[SBOX[(word_3 >> 16) & 0xFF]]
|
|
43
|
+
^ DECRYPTION_TABLE_2[SBOX[(word_3 >> 8) & 0xFF]]
|
|
44
|
+
^ DECRYPTION_TABLE_3[SBOX[word_3 & 0xFF]]
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
decryption_round_keys.append(encryption_round_keys[0])
|
|
49
|
+
return decryption_round_keys
|