purepython-aes 0.1.0__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 +33 -0
- purepython_aes/aes/__init__.py +29 -0
- purepython_aes/aes/algorithms/__init__.py +5 -0
- purepython_aes/aes/algorithms/aes128.py +13 -0
- purepython_aes/aes/algorithms/aes192.py +13 -0
- purepython_aes/aes/algorithms/aes256.py +13 -0
- purepython_aes/aes/core/__init__.py +3 -0
- purepython_aes/aes/core/aes.py +54 -0
- purepython_aes/aes/core/expansion.py +44 -0
- purepython_aes/aes/core/operations.py +64 -0
- purepython_aes/aes/core/state.py +210 -0
- purepython_aes/aes/interface.py +28 -0
- purepython_aes/aes/modes/__init__.py +4 -0
- purepython_aes/aes/modes/_base.py +14 -0
- purepython_aes/aes/modes/block/__init__.py +4 -0
- purepython_aes/aes/modes/block/_base.py +35 -0
- purepython_aes/aes/modes/block/ecb.py +21 -0
- purepython_aes/aes/modes/operations.py +16 -0
- purepython_aes/aes/padding/__init__.py +17 -0
- purepython_aes/aes/padding/_base.py +17 -0
- purepython_aes/aes/padding/ansix923.py +23 -0
- purepython_aes/aes/padding/iso10126.py +20 -0
- purepython_aes/aes/padding/iso7816.py +27 -0
- purepython_aes/aes/padding/no.py +16 -0
- purepython_aes/aes/padding/pkcs7.py +19 -0
- purepython_aes/aes/padding/zero.py +22 -0
- purepython_aes/const/__init__.py +26 -0
- purepython_aes/const/aes.py +40 -0
- purepython_aes/const/sbox.py +105 -0
- purepython_aes/py.typed +0 -0
- purepython_aes/types/__init__.py +5 -0
- purepython_aes/types/finite/__init__.py +3 -0
- purepython_aes/types/finite/aliases.py +37 -0
- purepython_aes-0.1.0.dist-info/METADATA +53 -0
- purepython_aes-0.1.0.dist-info/RECORD +39 -0
- purepython_aes-0.1.0.dist-info/WHEEL +5 -0
- purepython_aes-0.1.0.dist-info/scm_file_list.json +76 -0
- purepython_aes-0.1.0.dist-info/scm_version.json +8 -0
- purepython_aes-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from purepython_aes.aes import (
|
|
2
|
+
Aes,
|
|
3
|
+
Aes128,
|
|
4
|
+
Aes192,
|
|
5
|
+
Aes256,
|
|
6
|
+
AesMode,
|
|
7
|
+
AnsiX923Padding,
|
|
8
|
+
BasePadding,
|
|
9
|
+
BlockCipherMode,
|
|
10
|
+
EcbMode,
|
|
11
|
+
Iso7816Padding,
|
|
12
|
+
Iso10126Padding,
|
|
13
|
+
NoPadding,
|
|
14
|
+
Pkcs7Padding,
|
|
15
|
+
ZeroPadding,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__: list[str] = [
|
|
19
|
+
'Aes',
|
|
20
|
+
'Aes128',
|
|
21
|
+
'Aes192',
|
|
22
|
+
'Aes256',
|
|
23
|
+
'AesMode',
|
|
24
|
+
'AnsiX923Padding',
|
|
25
|
+
'BasePadding',
|
|
26
|
+
'BlockCipherMode',
|
|
27
|
+
'EcbMode',
|
|
28
|
+
'Iso7816Padding',
|
|
29
|
+
'Iso10126Padding',
|
|
30
|
+
'NoPadding',
|
|
31
|
+
'Pkcs7Padding',
|
|
32
|
+
'ZeroPadding',
|
|
33
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from purepython_aes.aes.algorithms import Aes128, Aes192, Aes256
|
|
2
|
+
from purepython_aes.aes.interface import Aes
|
|
3
|
+
from purepython_aes.aes.modes import AesMode, BlockCipherMode, EcbMode
|
|
4
|
+
from purepython_aes.aes.padding import (
|
|
5
|
+
AnsiX923Padding,
|
|
6
|
+
BasePadding,
|
|
7
|
+
Iso7816Padding,
|
|
8
|
+
Iso10126Padding,
|
|
9
|
+
NoPadding,
|
|
10
|
+
Pkcs7Padding,
|
|
11
|
+
ZeroPadding,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__: list[str] = [
|
|
15
|
+
'Aes128',
|
|
16
|
+
'Aes192',
|
|
17
|
+
'Aes256',
|
|
18
|
+
'Aes',
|
|
19
|
+
'AesMode',
|
|
20
|
+
'BlockCipherMode',
|
|
21
|
+
'EcbMode',
|
|
22
|
+
'AnsiX923Padding',
|
|
23
|
+
'BasePadding',
|
|
24
|
+
'Iso7816Padding',
|
|
25
|
+
'Iso10126Padding',
|
|
26
|
+
'NoPadding',
|
|
27
|
+
'Pkcs7Padding',
|
|
28
|
+
'ZeroPadding',
|
|
29
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar
|
|
3
|
+
|
|
4
|
+
from purepython_aes.aes.core import AesCore
|
|
5
|
+
from purepython_aes.const import AES_128_KEY_SIZE, AES_128_ROUND_COUNT
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(init=False, slots=True)
|
|
9
|
+
class Aes128(AesCore):
|
|
10
|
+
"""AES-128 encryption algorithm."""
|
|
11
|
+
|
|
12
|
+
__key_size__: ClassVar[int] = AES_128_KEY_SIZE
|
|
13
|
+
__round_count__: ClassVar[int] = AES_128_ROUND_COUNT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar
|
|
3
|
+
|
|
4
|
+
from purepython_aes.aes.core import AesCore
|
|
5
|
+
from purepython_aes.const import AES_192_KEY_SIZE, AES_192_ROUND_COUNT
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(init=False, slots=True)
|
|
9
|
+
class Aes192(AesCore):
|
|
10
|
+
"""AES-192 encryption algorithm."""
|
|
11
|
+
|
|
12
|
+
__key_size__: ClassVar[int] = AES_192_KEY_SIZE
|
|
13
|
+
__round_count__: ClassVar[int] = AES_192_ROUND_COUNT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar
|
|
3
|
+
|
|
4
|
+
from purepython_aes.aes.core import AesCore
|
|
5
|
+
from purepython_aes.const import AES_256_KEY_SIZE, AES_256_ROUND_COUNT
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(init=False, slots=True)
|
|
9
|
+
class Aes256(AesCore):
|
|
10
|
+
"""AES-256 encryption algorithm."""
|
|
11
|
+
|
|
12
|
+
__key_size__: ClassVar[int] = AES_256_KEY_SIZE
|
|
13
|
+
__round_count__: ClassVar[int] = AES_256_ROUND_COUNT
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from purepython_aes.aes.core.expansion import expand_key
|
|
4
|
+
from purepython_aes.aes.core.state import AesState
|
|
5
|
+
from purepython_aes.aes.interface import Aes
|
|
6
|
+
from purepython_aes.const import AES_BLOCK_SIZE
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(init=False, slots=True)
|
|
10
|
+
class AesCore(Aes):
|
|
11
|
+
"""Implement `encrypt_block` and `decrypt_block` methods."""
|
|
12
|
+
|
|
13
|
+
__round_keys: tuple[bytes, ...]
|
|
14
|
+
|
|
15
|
+
def __init__(self, key: bytes) -> None:
|
|
16
|
+
if len(key) != self.__key_size__:
|
|
17
|
+
raise ValueError(
|
|
18
|
+
f'expected len(key) == {self.__key_size__}, got {len(key)}'
|
|
19
|
+
)
|
|
20
|
+
self.__round_keys = expand_key(key, self.__round_count__)
|
|
21
|
+
|
|
22
|
+
def encrypt_block(self, plaintext: bytes) -> bytes:
|
|
23
|
+
if len(plaintext) != AES_BLOCK_SIZE:
|
|
24
|
+
raise ValueError(
|
|
25
|
+
f'Expected len(plaintext) == {AES_BLOCK_SIZE}, got {len(plaintext)}'
|
|
26
|
+
)
|
|
27
|
+
state: AesState = AesState.from_bytes(plaintext)
|
|
28
|
+
state.add_round_key(self.__round_keys[0])
|
|
29
|
+
for round_index in range(1, self.__round_count__):
|
|
30
|
+
state.subs_bytes()
|
|
31
|
+
state.shift_rows()
|
|
32
|
+
state.mix_columns()
|
|
33
|
+
state.add_round_key(self.__round_keys[round_index])
|
|
34
|
+
state.subs_bytes()
|
|
35
|
+
state.shift_rows()
|
|
36
|
+
state.add_round_key(self.__round_keys[self.__round_count__])
|
|
37
|
+
return state.to_bytes()
|
|
38
|
+
|
|
39
|
+
def decrypt_block(self, ciphertext: bytes) -> bytes:
|
|
40
|
+
if len(ciphertext) != AES_BLOCK_SIZE:
|
|
41
|
+
raise ValueError(
|
|
42
|
+
f'Expected len(ciphertext) == {AES_BLOCK_SIZE}, got {len(ciphertext)}'
|
|
43
|
+
)
|
|
44
|
+
state: AesState = AesState.from_bytes(ciphertext)
|
|
45
|
+
state.add_round_key(self.__round_keys[self.__round_count__])
|
|
46
|
+
for round_index in range(self.__round_count__ - 1, 0, -1):
|
|
47
|
+
state.inverse_shift_rows()
|
|
48
|
+
state.inverse_subs_bytes()
|
|
49
|
+
state.add_round_key(self.__round_keys[round_index])
|
|
50
|
+
state.inverse_mix_columns()
|
|
51
|
+
state.inverse_shift_rows()
|
|
52
|
+
state.inverse_subs_bytes()
|
|
53
|
+
state.add_round_key(self.__round_keys[0])
|
|
54
|
+
return state.to_bytes()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from purepython_aes.const import (
|
|
2
|
+
AES_256_KEY_SIZE,
|
|
3
|
+
AES_BLOCK_SIZE,
|
|
4
|
+
AES_WORD_SIZE,
|
|
5
|
+
ROUND_CONSTANTS,
|
|
6
|
+
SBOX,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def expand_key(key: bytes, round_count: int) -> tuple[bytes, ...]:
|
|
11
|
+
"""Expand an AES key into one 16-byte key for every cipher round."""
|
|
12
|
+
|
|
13
|
+
key_word_count: int = len(key) // AES_WORD_SIZE
|
|
14
|
+
expanded_word_count: int = (AES_BLOCK_SIZE // AES_WORD_SIZE) * (round_count + 1)
|
|
15
|
+
expanded_key: bytearray = bytearray(expanded_word_count * AES_WORD_SIZE)
|
|
16
|
+
expanded_key[0 : len(key)] = key
|
|
17
|
+
for word_index in range(key_word_count, expanded_word_count):
|
|
18
|
+
previous_offset: int = (word_index - 1) * AES_WORD_SIZE
|
|
19
|
+
b0: int = expanded_key[previous_offset]
|
|
20
|
+
b1: int = expanded_key[previous_offset + 1]
|
|
21
|
+
b2: int = expanded_key[previous_offset + 2]
|
|
22
|
+
b3: int = expanded_key[previous_offset + 3]
|
|
23
|
+
position_in_key: int = word_index % key_word_count
|
|
24
|
+
if position_in_key == 0:
|
|
25
|
+
round_constant_index: int = word_index // key_word_count
|
|
26
|
+
b0, b1, b2, b3 = (
|
|
27
|
+
SBOX[b1] ^ ROUND_CONSTANTS[round_constant_index],
|
|
28
|
+
SBOX[b2],
|
|
29
|
+
SBOX[b3],
|
|
30
|
+
SBOX[b0],
|
|
31
|
+
)
|
|
32
|
+
else:
|
|
33
|
+
if len(key) == AES_256_KEY_SIZE and position_in_key == 4:
|
|
34
|
+
b0, b1, b2, b3 = SBOX[b0], SBOX[b1], SBOX[b2], SBOX[b3]
|
|
35
|
+
source_offset: int = (word_index - key_word_count) * AES_WORD_SIZE
|
|
36
|
+
destination_offset: int = word_index * AES_WORD_SIZE
|
|
37
|
+
expanded_key[destination_offset] = expanded_key[source_offset] ^ b0
|
|
38
|
+
expanded_key[destination_offset + 1] = expanded_key[source_offset + 1] ^ b1
|
|
39
|
+
expanded_key[destination_offset + 2] = expanded_key[source_offset + 2] ^ b2
|
|
40
|
+
expanded_key[destination_offset + 3] = expanded_key[source_offset + 3] ^ b3
|
|
41
|
+
return tuple(
|
|
42
|
+
bytes(expanded_key[offset : offset + AES_BLOCK_SIZE])
|
|
43
|
+
for offset in range(0, len(expanded_key), AES_BLOCK_SIZE)
|
|
44
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
def galois256_multiply(left: int, right: int) -> int:
|
|
2
|
+
"""Multiply two bytes in the GF(2^8) finite field."""
|
|
3
|
+
|
|
4
|
+
result: int = 0
|
|
5
|
+
for _ in range(8):
|
|
6
|
+
if right & 1:
|
|
7
|
+
result ^= left
|
|
8
|
+
is_high_bit_set: bool = bool(left & 0x80)
|
|
9
|
+
left = (left << 1) & 0xFF
|
|
10
|
+
if is_high_bit_set:
|
|
11
|
+
left ^= 0x1B
|
|
12
|
+
right >>= 1
|
|
13
|
+
return result
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def xor_bytes(*bs: int) -> int:
|
|
17
|
+
"""XOR the supplied bytes."""
|
|
18
|
+
|
|
19
|
+
result: int = 0
|
|
20
|
+
for b in bs:
|
|
21
|
+
result ^= b
|
|
22
|
+
return result
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def mix_column(b0: int, b1: int, b2: int, b3: int) -> tuple[int, int, int, int]:
|
|
26
|
+
"""Apply `MixColumns` to one AES state column."""
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
xor_bytes(galois256_multiply(0x02, b0), galois256_multiply(0x03, b1), b2, b3),
|
|
30
|
+
xor_bytes(b0, galois256_multiply(0x02, b1), galois256_multiply(0x03, b2), b3),
|
|
31
|
+
xor_bytes(b0, b1, galois256_multiply(0x02, b2), galois256_multiply(0x03, b3)),
|
|
32
|
+
xor_bytes(galois256_multiply(0x03, b0), b1, b2, galois256_multiply(0x02, b3)),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def inverse_mix_column(b0: int, b1: int, b2: int, b3: int) -> tuple[int, int, int, int]:
|
|
37
|
+
"""Apply `InvMixColumns` to one AES state column."""
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
xor_bytes(
|
|
41
|
+
galois256_multiply(0x0E, b0),
|
|
42
|
+
galois256_multiply(0x0B, b1),
|
|
43
|
+
galois256_multiply(0x0D, b2),
|
|
44
|
+
galois256_multiply(0x09, b3),
|
|
45
|
+
),
|
|
46
|
+
xor_bytes(
|
|
47
|
+
galois256_multiply(0x09, b0),
|
|
48
|
+
galois256_multiply(0x0E, b1),
|
|
49
|
+
galois256_multiply(0x0B, b2),
|
|
50
|
+
galois256_multiply(0x0D, b3),
|
|
51
|
+
),
|
|
52
|
+
xor_bytes(
|
|
53
|
+
galois256_multiply(0x0D, b0),
|
|
54
|
+
galois256_multiply(0x09, b1),
|
|
55
|
+
galois256_multiply(0x0E, b2),
|
|
56
|
+
galois256_multiply(0x0B, b3),
|
|
57
|
+
),
|
|
58
|
+
xor_bytes(
|
|
59
|
+
galois256_multiply(0x0B, b0),
|
|
60
|
+
galois256_multiply(0x0D, b1),
|
|
61
|
+
galois256_multiply(0x09, b2),
|
|
62
|
+
galois256_multiply(0x0E, b3),
|
|
63
|
+
),
|
|
64
|
+
)
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import ClassVar, final
|
|
5
|
+
|
|
6
|
+
from purepython_aes.aes.core.operations import inverse_mix_column, mix_column
|
|
7
|
+
from purepython_aes.const import AES_BLOCK_SIZE, INVERSE_SBOX, SBOX
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(slots=True)
|
|
11
|
+
@final
|
|
12
|
+
class AesState:
|
|
13
|
+
__constructor_key: ClassVar[object] = object()
|
|
14
|
+
__b: list[int]
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def from_bytes(cls, block: bytes) -> AesState:
|
|
18
|
+
"""Create `AesState` from a 16-byte block."""
|
|
19
|
+
|
|
20
|
+
return cls(cls.__constructor_key, values=list(block))
|
|
21
|
+
|
|
22
|
+
def to_bytes(self) -> bytes:
|
|
23
|
+
"""Serialize the AES state in column-major order."""
|
|
24
|
+
|
|
25
|
+
return bytes(self.__b)
|
|
26
|
+
|
|
27
|
+
def add_round_key(self, key: bytes) -> None:
|
|
28
|
+
"""XOR a round key into this state."""
|
|
29
|
+
|
|
30
|
+
if len(key) != AES_BLOCK_SIZE:
|
|
31
|
+
raise ValueError(f'expected len(key) == {AES_BLOCK_SIZE}, got {len(key)}')
|
|
32
|
+
for index, key_byte in enumerate(key):
|
|
33
|
+
self.__b[index] ^= key_byte
|
|
34
|
+
|
|
35
|
+
def subs_bytes(self) -> None:
|
|
36
|
+
"""Apply the AES S-box to every state byte."""
|
|
37
|
+
|
|
38
|
+
for index in range(AES_BLOCK_SIZE):
|
|
39
|
+
self.__b[index] = SBOX[self.__b[index]]
|
|
40
|
+
|
|
41
|
+
def inverse_subs_bytes(self) -> None:
|
|
42
|
+
"""Apply the inverse AES S-box to every state byte."""
|
|
43
|
+
|
|
44
|
+
for index in range(AES_BLOCK_SIZE):
|
|
45
|
+
self.__b[index] = INVERSE_SBOX[self.__b[index]]
|
|
46
|
+
|
|
47
|
+
def shift_rows(self) -> None:
|
|
48
|
+
"""Cyclically shift each AES state row to the left."""
|
|
49
|
+
|
|
50
|
+
(
|
|
51
|
+
self.__b[1],
|
|
52
|
+
self.__b[5],
|
|
53
|
+
self.__b[9],
|
|
54
|
+
self.__b[13],
|
|
55
|
+
self.__b[2],
|
|
56
|
+
self.__b[6],
|
|
57
|
+
self.__b[10],
|
|
58
|
+
self.__b[14],
|
|
59
|
+
self.__b[3],
|
|
60
|
+
self.__b[7],
|
|
61
|
+
self.__b[11],
|
|
62
|
+
self.__b[15],
|
|
63
|
+
) = (
|
|
64
|
+
self.__b[5],
|
|
65
|
+
self.__b[9],
|
|
66
|
+
self.__b[13],
|
|
67
|
+
self.__b[1],
|
|
68
|
+
self.__b[10],
|
|
69
|
+
self.__b[14],
|
|
70
|
+
self.__b[2],
|
|
71
|
+
self.__b[6],
|
|
72
|
+
self.__b[15],
|
|
73
|
+
self.__b[3],
|
|
74
|
+
self.__b[7],
|
|
75
|
+
self.__b[11],
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def inverse_shift_rows(self) -> None:
|
|
79
|
+
"""Cyclically shift each AES state row to the right."""
|
|
80
|
+
|
|
81
|
+
(
|
|
82
|
+
self.__b[1],
|
|
83
|
+
self.__b[5],
|
|
84
|
+
self.__b[9],
|
|
85
|
+
self.__b[13],
|
|
86
|
+
self.__b[2],
|
|
87
|
+
self.__b[6],
|
|
88
|
+
self.__b[10],
|
|
89
|
+
self.__b[14],
|
|
90
|
+
self.__b[3],
|
|
91
|
+
self.__b[7],
|
|
92
|
+
self.__b[11],
|
|
93
|
+
self.__b[15],
|
|
94
|
+
) = (
|
|
95
|
+
self.__b[13],
|
|
96
|
+
self.__b[1],
|
|
97
|
+
self.__b[5],
|
|
98
|
+
self.__b[9],
|
|
99
|
+
self.__b[10],
|
|
100
|
+
self.__b[14],
|
|
101
|
+
self.__b[2],
|
|
102
|
+
self.__b[6],
|
|
103
|
+
self.__b[7],
|
|
104
|
+
self.__b[11],
|
|
105
|
+
self.__b[15],
|
|
106
|
+
self.__b[3],
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def mix_columns(self) -> None:
|
|
110
|
+
"""Apply the AES `MixColumns` transformation."""
|
|
111
|
+
|
|
112
|
+
(
|
|
113
|
+
self.__b[0],
|
|
114
|
+
self.__b[1],
|
|
115
|
+
self.__b[2],
|
|
116
|
+
self.__b[3],
|
|
117
|
+
) = mix_column(
|
|
118
|
+
self.__b[0],
|
|
119
|
+
self.__b[1],
|
|
120
|
+
self.__b[2],
|
|
121
|
+
self.__b[3],
|
|
122
|
+
)
|
|
123
|
+
(
|
|
124
|
+
self.__b[4],
|
|
125
|
+
self.__b[5],
|
|
126
|
+
self.__b[6],
|
|
127
|
+
self.__b[7],
|
|
128
|
+
) = mix_column(
|
|
129
|
+
self.__b[4],
|
|
130
|
+
self.__b[5],
|
|
131
|
+
self.__b[6],
|
|
132
|
+
self.__b[7],
|
|
133
|
+
)
|
|
134
|
+
(
|
|
135
|
+
self.__b[8],
|
|
136
|
+
self.__b[9],
|
|
137
|
+
self.__b[10],
|
|
138
|
+
self.__b[11],
|
|
139
|
+
) = mix_column(
|
|
140
|
+
self.__b[8],
|
|
141
|
+
self.__b[9],
|
|
142
|
+
self.__b[10],
|
|
143
|
+
self.__b[11],
|
|
144
|
+
)
|
|
145
|
+
(
|
|
146
|
+
self.__b[12],
|
|
147
|
+
self.__b[13],
|
|
148
|
+
self.__b[14],
|
|
149
|
+
self.__b[15],
|
|
150
|
+
) = mix_column(
|
|
151
|
+
self.__b[12],
|
|
152
|
+
self.__b[13],
|
|
153
|
+
self.__b[14],
|
|
154
|
+
self.__b[15],
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def inverse_mix_columns(self) -> None:
|
|
158
|
+
"""Apply the AES `InvMixColumns` transformation."""
|
|
159
|
+
|
|
160
|
+
(
|
|
161
|
+
self.__b[0],
|
|
162
|
+
self.__b[1],
|
|
163
|
+
self.__b[2],
|
|
164
|
+
self.__b[3],
|
|
165
|
+
) = inverse_mix_column(
|
|
166
|
+
self.__b[0],
|
|
167
|
+
self.__b[1],
|
|
168
|
+
self.__b[2],
|
|
169
|
+
self.__b[3],
|
|
170
|
+
)
|
|
171
|
+
(
|
|
172
|
+
self.__b[4],
|
|
173
|
+
self.__b[5],
|
|
174
|
+
self.__b[6],
|
|
175
|
+
self.__b[7],
|
|
176
|
+
) = inverse_mix_column(
|
|
177
|
+
self.__b[4],
|
|
178
|
+
self.__b[5],
|
|
179
|
+
self.__b[6],
|
|
180
|
+
self.__b[7],
|
|
181
|
+
)
|
|
182
|
+
(
|
|
183
|
+
self.__b[8],
|
|
184
|
+
self.__b[9],
|
|
185
|
+
self.__b[10],
|
|
186
|
+
self.__b[11],
|
|
187
|
+
) = inverse_mix_column(
|
|
188
|
+
self.__b[8],
|
|
189
|
+
self.__b[9],
|
|
190
|
+
self.__b[10],
|
|
191
|
+
self.__b[11],
|
|
192
|
+
)
|
|
193
|
+
(
|
|
194
|
+
self.__b[12],
|
|
195
|
+
self.__b[13],
|
|
196
|
+
self.__b[14],
|
|
197
|
+
self.__b[15],
|
|
198
|
+
) = inverse_mix_column(
|
|
199
|
+
self.__b[12],
|
|
200
|
+
self.__b[13],
|
|
201
|
+
self.__b[14],
|
|
202
|
+
self.__b[15],
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def __init__(self, __constructor_sentinel: object, /, values: list[int]) -> None:
|
|
206
|
+
if __constructor_sentinel is not AesState.__constructor_key:
|
|
207
|
+
raise ValueError(f'{AesState.__name__}() must not be called externally')
|
|
208
|
+
if len(values) != AES_BLOCK_SIZE:
|
|
209
|
+
raise ValueError(f'expected {AES_BLOCK_SIZE} values, got {len(values)}')
|
|
210
|
+
self.__b = values
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import ClassVar
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Aes(ABC):
|
|
6
|
+
"""Base class for AES-128, AES-192, and AES-256 algorithms."""
|
|
7
|
+
|
|
8
|
+
__key_size__: ClassVar[int]
|
|
9
|
+
"""AES key length in bytes."""
|
|
10
|
+
|
|
11
|
+
__round_count__: ClassVar[int]
|
|
12
|
+
"""Number of AES rounds."""
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def encrypt_block(self, plaintext: bytes) -> bytes:
|
|
16
|
+
"""Encrypt one 16-byte AES block."""
|
|
17
|
+
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def decrypt_block(self, ciphertext: bytes) -> bytes:
|
|
22
|
+
"""Decrypt one 16-byte AES block."""
|
|
23
|
+
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def __init__(self, key: bytes) -> None:
|
|
28
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from purepython_aes.aes.interface import Aes
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class AesMode(ABC):
|
|
11
|
+
"""Base class for all AES modes of operation."""
|
|
12
|
+
|
|
13
|
+
algorithm: Aes
|
|
14
|
+
"""Either AES-128, AES-192, or AES-256."""
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
from purepython_aes.aes.modes._base import AesMode
|
|
5
|
+
from purepython_aes.aes.padding import BasePadding
|
|
6
|
+
from purepython_aes.const import AES_BLOCK_SIZE
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(slots=True)
|
|
10
|
+
class BlockCipherMode(AesMode, ABC):
|
|
11
|
+
padding: BasePadding
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def encrypt_blocks(self, padded_plaintext: bytes) -> bytes:
|
|
15
|
+
"""Encrypt plaintext containing complete blocks."""
|
|
16
|
+
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def decrypt_blocks(self, ciphertext: bytes) -> bytes:
|
|
21
|
+
"""Decrypt ciphertext containing complete blocks."""
|
|
22
|
+
|
|
23
|
+
raise NotImplementedError
|
|
24
|
+
|
|
25
|
+
def encrypt(self, plaintext: bytes) -> bytes:
|
|
26
|
+
"""Pad and encrypt a byte string."""
|
|
27
|
+
|
|
28
|
+
return self.encrypt_blocks(self.padding.pad(plaintext))
|
|
29
|
+
|
|
30
|
+
def decrypt(self, ciphertext: bytes) -> bytes:
|
|
31
|
+
"""Decrypt and unpad a byte string."""
|
|
32
|
+
|
|
33
|
+
if len(ciphertext) % AES_BLOCK_SIZE != 0:
|
|
34
|
+
raise ValueError(f'len(ciphertext) % {AES_BLOCK_SIZE} must be 0')
|
|
35
|
+
return self.padding.unpad(self.decrypt_blocks(ciphertext))
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import final
|
|
3
|
+
|
|
4
|
+
from purepython_aes.aes.modes.block._base import BlockCipherMode
|
|
5
|
+
from purepython_aes.aes.modes.operations import split_into_blocks
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@final
|
|
9
|
+
@dataclass(slots=True)
|
|
10
|
+
class EcbMode(BlockCipherMode):
|
|
11
|
+
"""Electronic Codebook mode of operation."""
|
|
12
|
+
|
|
13
|
+
def encrypt_blocks(self, padded_plaintext: bytes) -> bytes:
|
|
14
|
+
return bytes(0).join(
|
|
15
|
+
map(self.algorithm.encrypt_block, split_into_blocks(padded_plaintext))
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
def decrypt_blocks(self, ciphertext: bytes) -> bytes:
|
|
19
|
+
return bytes(0).join(
|
|
20
|
+
map(self.algorithm.decrypt_block, split_into_blocks(ciphertext))
|
|
21
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from purepython_aes.const import AES_BLOCK_SIZE
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def xor(left: bytes, right: bytes) -> bytes:
|
|
5
|
+
"""XOR two equally sized byte strings."""
|
|
6
|
+
|
|
7
|
+
return bytes(b0 ^ b1 for b0, b1 in zip(left, right, strict=True))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def split_into_blocks(data: bytes) -> tuple[bytes, ...]:
|
|
11
|
+
"""Split data into 16-byte blocks."""
|
|
12
|
+
|
|
13
|
+
return tuple(
|
|
14
|
+
data[block_start : (block_start + AES_BLOCK_SIZE)]
|
|
15
|
+
for block_start in range(0, len(data), AES_BLOCK_SIZE)
|
|
16
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from purepython_aes.aes.padding._base import BasePadding
|
|
2
|
+
from purepython_aes.aes.padding.ansix923 import AnsiX923Padding
|
|
3
|
+
from purepython_aes.aes.padding.iso7816 import Iso7816Padding
|
|
4
|
+
from purepython_aes.aes.padding.iso10126 import Iso10126Padding
|
|
5
|
+
from purepython_aes.aes.padding.no import NoPadding
|
|
6
|
+
from purepython_aes.aes.padding.pkcs7 import Pkcs7Padding
|
|
7
|
+
from purepython_aes.aes.padding.zero import ZeroPadding
|
|
8
|
+
|
|
9
|
+
__all__: list[str] = [
|
|
10
|
+
'AnsiX923Padding',
|
|
11
|
+
'BasePadding',
|
|
12
|
+
'Iso10126Padding',
|
|
13
|
+
'Iso7816Padding',
|
|
14
|
+
'NoPadding',
|
|
15
|
+
'Pkcs7Padding',
|
|
16
|
+
'ZeroPadding',
|
|
17
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BasePadding(ABC):
|
|
5
|
+
"""Reversible block-padding algorithm."""
|
|
6
|
+
|
|
7
|
+
@abstractmethod
|
|
8
|
+
def pad(self, data: bytes) -> bytes:
|
|
9
|
+
"""Pad data to a multiple of 16 bytes."""
|
|
10
|
+
|
|
11
|
+
raise NotImplementedError
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def unpad(self, data: bytes) -> bytes:
|
|
15
|
+
"""Remove padding from block-aligned data."""
|
|
16
|
+
|
|
17
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from purepython_aes.aes.padding._base import BasePadding
|
|
2
|
+
from purepython_aes.const import AES_BLOCK_SIZE
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AnsiX923Padding(BasePadding):
|
|
6
|
+
"""Implement deterministic ANSI X9.23 block padding."""
|
|
7
|
+
|
|
8
|
+
def pad(self, data: bytes) -> bytes:
|
|
9
|
+
padding_size: int = AES_BLOCK_SIZE - len(data) % AES_BLOCK_SIZE
|
|
10
|
+
zero_padding: bytes = bytes(padding_size - 1)
|
|
11
|
+
length_byte: bytes = bytes([padding_size])
|
|
12
|
+
return data + zero_padding + length_byte
|
|
13
|
+
|
|
14
|
+
def unpad(self, data: bytes) -> bytes:
|
|
15
|
+
if len(data) == 0:
|
|
16
|
+
raise ValueError('ANSI X9.23 padding expects non-empty data')
|
|
17
|
+
padding_size: int = data[-1]
|
|
18
|
+
if padding_size < 1 or padding_size > AES_BLOCK_SIZE:
|
|
19
|
+
raise ValueError('invalid ANSI X9.23 padding size')
|
|
20
|
+
zero_padding: bytes = data[(-padding_size):(-1)]
|
|
21
|
+
if zero_padding != bytes(padding_size - 1):
|
|
22
|
+
raise ValueError('invalid ANSI X9.23 padding bytes')
|
|
23
|
+
return data[:(-padding_size)]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from secrets import token_bytes
|
|
2
|
+
|
|
3
|
+
from purepython_aes.aes.padding._base import BasePadding
|
|
4
|
+
from purepython_aes.const import AES_BLOCK_SIZE
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Iso10126Padding(BasePadding):
|
|
8
|
+
"""Implement legacy ISO 10126 block padding."""
|
|
9
|
+
|
|
10
|
+
def pad(self, data: bytes) -> bytes:
|
|
11
|
+
padding_size: int = AES_BLOCK_SIZE - len(data) % AES_BLOCK_SIZE
|
|
12
|
+
random_padding: bytes = token_bytes(padding_size - 1)
|
|
13
|
+
length_byte: bytes = bytes([padding_size])
|
|
14
|
+
return data + random_padding + length_byte
|
|
15
|
+
|
|
16
|
+
def unpad(self, data: bytes) -> bytes:
|
|
17
|
+
padding_size: int = data[-1]
|
|
18
|
+
if padding_size < 1 or padding_size > AES_BLOCK_SIZE:
|
|
19
|
+
raise ValueError('invalid ISO 10126 padding size')
|
|
20
|
+
return data[:(-padding_size)]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from typing import Final
|
|
2
|
+
|
|
3
|
+
from purepython_aes.aes.padding._base import BasePadding
|
|
4
|
+
from purepython_aes.const import AES_BLOCK_SIZE
|
|
5
|
+
|
|
6
|
+
PADDING_START_BYTE: Final[int] = 0x80
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Iso7816Padding(BasePadding):
|
|
10
|
+
"""Implement ISO/IEC 7816-4 block padding."""
|
|
11
|
+
|
|
12
|
+
def pad(self, data: bytes) -> bytes:
|
|
13
|
+
padding_size: int = AES_BLOCK_SIZE - len(data) % AES_BLOCK_SIZE
|
|
14
|
+
zero_padding: bytes = bytes(padding_size - 1)
|
|
15
|
+
return data + bytes([PADDING_START_BYTE]) + zero_padding
|
|
16
|
+
|
|
17
|
+
def unpad(self, data: bytes) -> bytes:
|
|
18
|
+
padding_start_index: int = len(data) - 1
|
|
19
|
+
while padding_start_index >= 0 and data[padding_start_index] == 0:
|
|
20
|
+
padding_start_index -= 1
|
|
21
|
+
if padding_start_index < 0:
|
|
22
|
+
raise ValueError('ISO/IEC 7816-4 padding marker not found')
|
|
23
|
+
if data[padding_start_index] != PADDING_START_BYTE:
|
|
24
|
+
raise ValueError('invalid ISO/IEC 7816-4 padding marker')
|
|
25
|
+
if (len(data) - padding_start_index) > AES_BLOCK_SIZE:
|
|
26
|
+
raise ValueError('invalid ISO/IEC 7816-4 padding size')
|
|
27
|
+
return data[:padding_start_index]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from purepython_aes.aes.padding._base import BasePadding
|
|
2
|
+
from purepython_aes.const import AES_BLOCK_SIZE
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class NoPadding(BasePadding):
|
|
6
|
+
"""Require callers to provide block-aligned data."""
|
|
7
|
+
|
|
8
|
+
def pad(self, data: bytes) -> bytes:
|
|
9
|
+
if len(data) % AES_BLOCK_SIZE != 0:
|
|
10
|
+
raise ValueError(
|
|
11
|
+
f'expected len(data) % {AES_BLOCK_SIZE} == 0, got {len(data)}'
|
|
12
|
+
)
|
|
13
|
+
return data
|
|
14
|
+
|
|
15
|
+
def unpad(self, data: bytes) -> bytes:
|
|
16
|
+
return data
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from purepython_aes.aes.padding._base import BasePadding
|
|
2
|
+
from purepython_aes.const import AES_BLOCK_SIZE
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Pkcs7Padding(BasePadding):
|
|
6
|
+
"""Implement PKCS#7/CMS block padding."""
|
|
7
|
+
|
|
8
|
+
def pad(self, data: bytes) -> bytes:
|
|
9
|
+
padding_size: int = AES_BLOCK_SIZE - len(data) % AES_BLOCK_SIZE
|
|
10
|
+
padding: bytes = bytes([padding_size]) * padding_size
|
|
11
|
+
return data + padding
|
|
12
|
+
|
|
13
|
+
def unpad(self, data: bytes) -> bytes:
|
|
14
|
+
padding_size: int = data[-1]
|
|
15
|
+
if padding_size < 1 or padding_size > AES_BLOCK_SIZE:
|
|
16
|
+
raise ValueError('invalid PKCS#7 padding size')
|
|
17
|
+
if not data.endswith(bytes([padding_size]) * padding_size):
|
|
18
|
+
raise ValueError('invalid PKCS#7 padding bytes')
|
|
19
|
+
return data[:(-padding_size)]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from purepython_aes.aes.padding._base import BasePadding
|
|
2
|
+
from purepython_aes.const import AES_BLOCK_SIZE
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ZeroPadding(BasePadding):
|
|
6
|
+
"""Implement ambiguous zero-byte padding."""
|
|
7
|
+
|
|
8
|
+
def pad(self, data: bytes) -> bytes:
|
|
9
|
+
remainder_size: int = len(data) % AES_BLOCK_SIZE
|
|
10
|
+
if remainder_size == 0:
|
|
11
|
+
return data
|
|
12
|
+
return data + bytes(AES_BLOCK_SIZE - remainder_size)
|
|
13
|
+
|
|
14
|
+
def unpad(self, data: bytes) -> bytes:
|
|
15
|
+
if len(data) % AES_BLOCK_SIZE != 0:
|
|
16
|
+
raise ValueError(
|
|
17
|
+
f'expected len(data) % {AES_BLOCK_SIZE} == 0, got {len(data)}'
|
|
18
|
+
)
|
|
19
|
+
unpadded_size: int = len(data)
|
|
20
|
+
while unpadded_size > 0 and data[unpadded_size - 1] == 0:
|
|
21
|
+
unpadded_size -= 1
|
|
22
|
+
return data[:unpadded_size]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from purepython_aes.const.aes import (
|
|
2
|
+
AES_128_KEY_SIZE,
|
|
3
|
+
AES_128_ROUND_COUNT,
|
|
4
|
+
AES_192_KEY_SIZE,
|
|
5
|
+
AES_192_ROUND_COUNT,
|
|
6
|
+
AES_256_KEY_SIZE,
|
|
7
|
+
AES_256_ROUND_COUNT,
|
|
8
|
+
AES_BLOCK_SIZE,
|
|
9
|
+
AES_WORD_SIZE,
|
|
10
|
+
ROUND_CONSTANTS,
|
|
11
|
+
)
|
|
12
|
+
from purepython_aes.const.sbox import INVERSE_SBOX, SBOX
|
|
13
|
+
|
|
14
|
+
__all__: list[str] = [
|
|
15
|
+
'AES_128_KEY_SIZE',
|
|
16
|
+
'AES_128_ROUND_COUNT',
|
|
17
|
+
'AES_192_KEY_SIZE',
|
|
18
|
+
'AES_192_ROUND_COUNT',
|
|
19
|
+
'AES_256_KEY_SIZE',
|
|
20
|
+
'AES_256_ROUND_COUNT',
|
|
21
|
+
'AES_BLOCK_SIZE',
|
|
22
|
+
'AES_WORD_SIZE',
|
|
23
|
+
'ROUND_CONSTANTS',
|
|
24
|
+
'INVERSE_SBOX',
|
|
25
|
+
'SBOX',
|
|
26
|
+
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from typing import Final
|
|
2
|
+
|
|
3
|
+
AES_BLOCK_SIZE: Final[int] = 16
|
|
4
|
+
"""AES block size in bytes."""
|
|
5
|
+
|
|
6
|
+
AES_WORD_SIZE: Final[int] = 4
|
|
7
|
+
"""Word size in bytes."""
|
|
8
|
+
|
|
9
|
+
AES_128_KEY_SIZE: Final[int] = 16
|
|
10
|
+
"""Key length in bytes for AES-128."""
|
|
11
|
+
|
|
12
|
+
AES_128_ROUND_COUNT: Final[int] = 10
|
|
13
|
+
"""Number of rounds for AES-128."""
|
|
14
|
+
|
|
15
|
+
AES_192_KEY_SIZE: Final[int] = 24
|
|
16
|
+
"""Key length in bytes for AES-192."""
|
|
17
|
+
|
|
18
|
+
AES_192_ROUND_COUNT: Final[int] = 12
|
|
19
|
+
"""Number of rounds for AES-192."""
|
|
20
|
+
|
|
21
|
+
AES_256_KEY_SIZE: Final[int] = 32
|
|
22
|
+
"""Key length in bytes for AES-256."""
|
|
23
|
+
|
|
24
|
+
AES_256_ROUND_COUNT: Final[int] = 14
|
|
25
|
+
"""Number of rounds for AES-256."""
|
|
26
|
+
|
|
27
|
+
ROUND_CONSTANTS: Final[tuple[int, ...]] = (
|
|
28
|
+
0x00,
|
|
29
|
+
0x01,
|
|
30
|
+
0x02,
|
|
31
|
+
0x04,
|
|
32
|
+
0x08,
|
|
33
|
+
0x10,
|
|
34
|
+
0x20,
|
|
35
|
+
0x40,
|
|
36
|
+
0x80,
|
|
37
|
+
0x1B,
|
|
38
|
+
0x36,
|
|
39
|
+
)
|
|
40
|
+
"""Predefined values used during the AES key expansion."""
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from typing import Final
|
|
2
|
+
|
|
3
|
+
from purepython_aes.types import IntLookupTable256
|
|
4
|
+
|
|
5
|
+
SBOX: Final[IntLookupTable256] = (
|
|
6
|
+
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, # fmt: skip
|
|
7
|
+
0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, # fmt: skip
|
|
8
|
+
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, # fmt: skip
|
|
9
|
+
0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, # fmt: skip
|
|
10
|
+
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, # fmt: skip
|
|
11
|
+
0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, # fmt: skip
|
|
12
|
+
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, # fmt: skip
|
|
13
|
+
0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, # fmt: skip
|
|
14
|
+
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, # fmt: skip
|
|
15
|
+
0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, # fmt: skip
|
|
16
|
+
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, # fmt: skip
|
|
17
|
+
0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, # fmt: skip
|
|
18
|
+
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, # fmt: skip
|
|
19
|
+
0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, # fmt: skip
|
|
20
|
+
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, # fmt: skip
|
|
21
|
+
0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, # fmt: skip
|
|
22
|
+
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, # fmt: skip
|
|
23
|
+
0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, # fmt: skip
|
|
24
|
+
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, # fmt: skip
|
|
25
|
+
0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, # fmt: skip
|
|
26
|
+
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, # fmt: skip
|
|
27
|
+
0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, # fmt: skip
|
|
28
|
+
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, # fmt: skip
|
|
29
|
+
0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, # fmt: skip
|
|
30
|
+
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, # fmt: skip
|
|
31
|
+
0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, # fmt: skip
|
|
32
|
+
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, # fmt: skip
|
|
33
|
+
0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, # fmt: skip
|
|
34
|
+
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, # fmt: skip
|
|
35
|
+
0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, # fmt: skip
|
|
36
|
+
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, # fmt: skip
|
|
37
|
+
0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16, # fmt: skip
|
|
38
|
+
)
|
|
39
|
+
"""
|
|
40
|
+
Forward AES substitution box (S-box).
|
|
41
|
+
|
|
42
|
+
This table implements the nonlinear substitution used by the AES `SubBytes`
|
|
43
|
+
round operation and the `SubWord` operation during key expansion.
|
|
44
|
+
|
|
45
|
+
Internally, the AES S-box is constructed by:
|
|
46
|
+
|
|
47
|
+
1. Computing the multiplicative inverse of the input byte in the finite field
|
|
48
|
+
GF(2^8), treating `0x00` as a special case.
|
|
49
|
+
2. Applying the affine transformation defined by the AES specification.
|
|
50
|
+
|
|
51
|
+
The resulting mapping is a permutation of all 256 possible byte values, meaning
|
|
52
|
+
every byte appears exactly once as an output. Its inverse permutation is stored
|
|
53
|
+
in `INVERSE_SBOX`.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
INVERSE_SBOX: Final[IntLookupTable256] = (
|
|
58
|
+
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, # fmt: skip
|
|
59
|
+
0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, # fmt: skip
|
|
60
|
+
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, # fmt: skip
|
|
61
|
+
0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, # fmt: skip
|
|
62
|
+
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, # fmt: skip
|
|
63
|
+
0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, # fmt: skip
|
|
64
|
+
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, # fmt: skip
|
|
65
|
+
0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, # fmt: skip
|
|
66
|
+
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, # fmt: skip
|
|
67
|
+
0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, # fmt: skip
|
|
68
|
+
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, # fmt: skip
|
|
69
|
+
0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, # fmt: skip
|
|
70
|
+
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, # fmt: skip
|
|
71
|
+
0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, # fmt: skip
|
|
72
|
+
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, # fmt: skip
|
|
73
|
+
0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, # fmt: skip
|
|
74
|
+
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, # fmt: skip
|
|
75
|
+
0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, # fmt: skip
|
|
76
|
+
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, # fmt: skip
|
|
77
|
+
0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, # fmt: skip
|
|
78
|
+
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, # fmt: skip
|
|
79
|
+
0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, # fmt: skip
|
|
80
|
+
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, # fmt: skip
|
|
81
|
+
0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, # fmt: skip
|
|
82
|
+
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, # fmt: skip
|
|
83
|
+
0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, # fmt: skip
|
|
84
|
+
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, # fmt: skip
|
|
85
|
+
0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, # fmt: skip
|
|
86
|
+
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, # fmt: skip
|
|
87
|
+
0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, # fmt: skip
|
|
88
|
+
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, # fmt: skip
|
|
89
|
+
0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D, # fmt: skip
|
|
90
|
+
)
|
|
91
|
+
"""
|
|
92
|
+
Inverse AES substitution box (Inverse S-box).
|
|
93
|
+
|
|
94
|
+
This table implements the inverse substitution transformation used by the
|
|
95
|
+
AES `InvSubBytes` round operation during decryption.
|
|
96
|
+
|
|
97
|
+
`INVERSE_SBOX` is the exact inverse permutation of `SBOX`. For every
|
|
98
|
+
possible byte value `n`:
|
|
99
|
+
|
|
100
|
+
INVERSE_SBOX[SBOX[n]] == n
|
|
101
|
+
SBOX[INVERSE_SBOX[n]] == n
|
|
102
|
+
|
|
103
|
+
Like the forward S-box, this table contains exactly 256 entries and represents
|
|
104
|
+
a permutation of every possible byte value.
|
|
105
|
+
"""
|
purepython_aes/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from typing import TypeAlias
|
|
2
|
+
|
|
3
|
+
IntLookupTable256: TypeAlias = tuple[
|
|
4
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
5
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
6
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
7
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
8
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
9
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
10
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
11
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
12
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
13
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
14
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
15
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
16
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
17
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
18
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
19
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
20
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
21
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
22
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
23
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
24
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
25
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
26
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
27
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
28
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
29
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
30
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
31
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
32
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
33
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
34
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
35
|
+
int, int, int, int, int, int, int, int, # fmt: skip
|
|
36
|
+
]
|
|
37
|
+
"""Type alias describing a fixed-length tuple containing exactly 256 integer values."""
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: purepython-aes
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Pure-Python implementation of AES encryption algorithm.
|
|
5
|
+
Author-email: Emelianov Artem <penguin5726@proton.me>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Pukhosos/purepython-aes
|
|
8
|
+
Project-URL: Repository, https://github.com/Pukhosos/purepython-aes
|
|
9
|
+
Project-URL: Issues, https://github.com/Pukhosos/purepython-aes/issues
|
|
10
|
+
Classifier: Development Status :: 1 - Planning
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Security :: Cryptography
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: isort; extra == "dev"
|
|
25
|
+
Requires-Dist: black; extra == "dev"
|
|
26
|
+
Requires-Dist: flake8; extra == "dev"
|
|
27
|
+
Requires-Dist: flake8-bugbear; extra == "dev"
|
|
28
|
+
Requires-Dist: flake8-builtins; extra == "dev"
|
|
29
|
+
Requires-Dist: flake8-comprehensions; extra == "dev"
|
|
30
|
+
Requires-Dist: flake8-fixme; extra == "dev"
|
|
31
|
+
Requires-Dist: flake8-pyproject; extra == "dev"
|
|
32
|
+
Requires-Dist: flake8-quotes; extra == "dev"
|
|
33
|
+
Requires-Dist: flake8-simplify; extra == "dev"
|
|
34
|
+
Requires-Dist: pep8-naming; extra == "dev"
|
|
35
|
+
Requires-Dist: mypy; extra == "dev"
|
|
36
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
37
|
+
Provides-Extra: test
|
|
38
|
+
Requires-Dist: pytest>=9.0; extra == "test"
|
|
39
|
+
Requires-Dist: pytest-env; extra == "test"
|
|
40
|
+
Requires-Dist: pytest-randomly; extra == "test"
|
|
41
|
+
Requires-Dist: pytest-xdist; extra == "test"
|
|
42
|
+
Requires-Dist: hypothesis; extra == "test"
|
|
43
|
+
Provides-Extra: fuzz
|
|
44
|
+
Requires-Dist: hypofuzz; extra == "fuzz"
|
|
45
|
+
Provides-Extra: build
|
|
46
|
+
Requires-Dist: build; extra == "build"
|
|
47
|
+
Requires-Dist: twine; extra == "build"
|
|
48
|
+
|
|
49
|
+
# purepython-aes
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
`purepython-aes` is a free, open-source software distributed under the [MIT License](LICENSE.txt)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
purepython_aes/__init__.py,sha256=REYvoTsB3HL3wD6AR0T8NJZKfD51VX8aH4bcxUC_xp0,531
|
|
2
|
+
purepython_aes/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
purepython_aes/aes/__init__.py,sha256=lv_RO-dBRo9IPFtRY0csjkycsyUH7DXW41SZMCaHLV0,628
|
|
4
|
+
purepython_aes/aes/interface.py,sha256=OzNhRtS2xu5pOXFF7sAiHkNdSuMHLwaYSRm-0Ldz0Sg,699
|
|
5
|
+
purepython_aes/aes/algorithms/__init__.py,sha256=L21rL4gaCdoRmV2wyFJyuzFpBI4jXry1uQSi0b0eqds,221
|
|
6
|
+
purepython_aes/aes/algorithms/aes128.py,sha256=rmv-LJ2dcHIYmTfSTg2xAn-M94ank89ULYZKgpFuoVs,387
|
|
7
|
+
purepython_aes/aes/algorithms/aes192.py,sha256=Acjoel_K3vHzEuD4gv2idPJdiXWrSMyfHC_6SW_lOG4,387
|
|
8
|
+
purepython_aes/aes/algorithms/aes256.py,sha256=eeTW6Anl_F0b1RhFx__GZ5Uddl6d_5T2endcbBHmlBw,387
|
|
9
|
+
purepython_aes/aes/core/__init__.py,sha256=rCKHARdFJ8Q4e1QTTWSkzejbfIwcuHdo2lpC5QpJpzA,82
|
|
10
|
+
purepython_aes/aes/core/aes.py,sha256=xU1K9cNIek7moVeu-ZDrv3TIQOHTb6SoJsswXX10V3c,2141
|
|
11
|
+
purepython_aes/aes/core/expansion.py,sha256=JmNxuHIV3EzFFDFX2RAnbwKECimovBLrL4O3mfE8UOA,1979
|
|
12
|
+
purepython_aes/aes/core/operations.py,sha256=4jGJZ3xwRb3z0BLMorF-QCbTdPKAWUVPEvxkUe-5JjU,1989
|
|
13
|
+
purepython_aes/aes/core/state.py,sha256=j79LDXRabfGAkF2qx8iVKgrSDfEUfFnfKU1XQ-5OidM,5488
|
|
14
|
+
purepython_aes/aes/modes/__init__.py,sha256=fwEckEnJ9PGVSWNAXIG9M0KVhSSM7onKeFHwFkcvQ18,183
|
|
15
|
+
purepython_aes/aes/modes/_base.py,sha256=L81chTROXrIgNL5GJ1AT9icmixbTT7okfcZ0QyeVaiA,289
|
|
16
|
+
purepython_aes/aes/modes/operations.py,sha256=VYnoYB0Bb9bfwnApE3tL-2koLK6dDRotit1Pc1cAfhA,460
|
|
17
|
+
purepython_aes/aes/modes/block/__init__.py,sha256=TZuLX83Kkf1jhyJztqTIsUsULN8Q56fuUksm6eep_E8,173
|
|
18
|
+
purepython_aes/aes/modes/block/_base.py,sha256=TH6oekojIZluInw8ZLMFpPNuolYQOcxI0QOgsGIWekc,1119
|
|
19
|
+
purepython_aes/aes/modes/block/ecb.py,sha256=lL1gyIjq768Xe_V6QdWeG9xn5w2G-uTQqOX_cs9MYH4,668
|
|
20
|
+
purepython_aes/aes/padding/__init__.py,sha256=X_JqBtvuLm1edPoE7B-DnENBHIgwpemhB7d37ZRDQEY,582
|
|
21
|
+
purepython_aes/aes/padding/_base.py,sha256=dnuNv8OVOBDxAXSUGEeJu5uy9bSFkY2AKkzrK0If8so,408
|
|
22
|
+
purepython_aes/aes/padding/ansix923.py,sha256=xWLzlk85vQn1pPZm5x0wJy3aK46bvtK1YHNCLRC0yfA,991
|
|
23
|
+
purepython_aes/aes/padding/iso10126.py,sha256=OHIywjjWAXFwWcmNASnkGNZNY6xKUm5Y7A5rqETZ0MQ,750
|
|
24
|
+
purepython_aes/aes/padding/iso7816.py,sha256=I_ssvJVOnwriArkPlQlhn9fuRjNwEVR-sdGhJbD9Lyw,1106
|
|
25
|
+
purepython_aes/aes/padding/no.py,sha256=H73EYvOegwxqfpQ_7Xqb02Y4Zo2M3eL_-GlZLE98H4A,487
|
|
26
|
+
purepython_aes/aes/padding/pkcs7.py,sha256=XJk7K48bonCX7E9_UENF_Vva_cgXmhPxyH9BOnmbAjU,762
|
|
27
|
+
purepython_aes/aes/padding/zero.py,sha256=bFStfbwhquROKcZKzlWbK01De6kZb11UEP7Bj_HNZdM,787
|
|
28
|
+
purepython_aes/const/__init__.py,sha256=z5r00PDY5EvyWJGgTd1J4_mPTYwCzVbxWfQzvR3ZWRE,576
|
|
29
|
+
purepython_aes/const/aes.py,sha256=Vhk45-Tf3RXnoQ9qSNz6KLvQ5gkkOHOLakWQdegQMPA,806
|
|
30
|
+
purepython_aes/const/sbox.py,sha256=RaSYzs2mjUKZjv0fbEwCD3YLLkoz7emJhjEbU-RH09o,5399
|
|
31
|
+
purepython_aes/types/__init__.py,sha256=QiuMSx2DaC2lCXoK16RGeEBrfoX4d3jozkx4QV0kFjc,116
|
|
32
|
+
purepython_aes/types/finite/__init__.py,sha256=2cYu5Zxdp5NKAwYbV-MD2qamSaJxfqRYp0jOt657-YY,110
|
|
33
|
+
purepython_aes/types/finite/aliases.py,sha256=gKO8ETbMhV5FX6l3enF8wt246FpMPohreTZ3BPDThBM,1982
|
|
34
|
+
purepython_aes-0.1.0.dist-info/METADATA,sha256=EtqJAtIEueHuNorxAbaDbPVE6OV9B1bW4ho8pqVgMXs,2112
|
|
35
|
+
purepython_aes-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
36
|
+
purepython_aes-0.1.0.dist-info/scm_file_list.json,sha256=kfQDdACHcGFPsm09ncINh7o1yMzqwz4yaugdCuK7RFk,3068
|
|
37
|
+
purepython_aes-0.1.0.dist-info/scm_version.json,sha256=M8rto4eBaxUN90pVC3CaDYQm6j_D1U-WaRS4PcJkfOM,160
|
|
38
|
+
purepython_aes-0.1.0.dist-info/top_level.txt,sha256=IiTUB8OvHhwNXxf6OXKCfUADF7HcQdCRUMfPJC6TS5k,15
|
|
39
|
+
purepython_aes-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
".pre-commit-config.yaml",
|
|
4
|
+
"README.md",
|
|
5
|
+
"pyproject.toml",
|
|
6
|
+
".gitignore",
|
|
7
|
+
"LISENCE.txt",
|
|
8
|
+
"scripts/check-test-marks.sh",
|
|
9
|
+
"src/purepython_aes/py.typed",
|
|
10
|
+
"src/purepython_aes/__init__.py",
|
|
11
|
+
"src/purepython_aes/types/__init__.py",
|
|
12
|
+
"src/purepython_aes/types/finite/aliases.py",
|
|
13
|
+
"src/purepython_aes/types/finite/__init__.py",
|
|
14
|
+
"src/purepython_aes/const/__init__.py",
|
|
15
|
+
"src/purepython_aes/const/aes.py",
|
|
16
|
+
"src/purepython_aes/const/sbox.py",
|
|
17
|
+
"src/purepython_aes/aes/__init__.py",
|
|
18
|
+
"src/purepython_aes/aes/interface.py",
|
|
19
|
+
"src/purepython_aes/aes/core/__init__.py",
|
|
20
|
+
"src/purepython_aes/aes/core/aes.py",
|
|
21
|
+
"src/purepython_aes/aes/core/state.py",
|
|
22
|
+
"src/purepython_aes/aes/core/expansion.py",
|
|
23
|
+
"src/purepython_aes/aes/core/operations.py",
|
|
24
|
+
"src/purepython_aes/aes/padding/_base.py",
|
|
25
|
+
"src/purepython_aes/aes/padding/no.py",
|
|
26
|
+
"src/purepython_aes/aes/padding/pkcs7.py",
|
|
27
|
+
"src/purepython_aes/aes/padding/__init__.py",
|
|
28
|
+
"src/purepython_aes/aes/padding/zero.py",
|
|
29
|
+
"src/purepython_aes/aes/padding/iso10126.py",
|
|
30
|
+
"src/purepython_aes/aes/padding/iso7816.py",
|
|
31
|
+
"src/purepython_aes/aes/padding/ansix923.py",
|
|
32
|
+
"src/purepython_aes/aes/modes/_base.py",
|
|
33
|
+
"src/purepython_aes/aes/modes/__init__.py",
|
|
34
|
+
"src/purepython_aes/aes/modes/operations.py",
|
|
35
|
+
"src/purepython_aes/aes/modes/block/_base.py",
|
|
36
|
+
"src/purepython_aes/aes/modes/block/__init__.py",
|
|
37
|
+
"src/purepython_aes/aes/modes/block/ecb.py",
|
|
38
|
+
"src/purepython_aes/aes/algorithms/aes128.py",
|
|
39
|
+
"src/purepython_aes/aes/algorithms/__init__.py",
|
|
40
|
+
"src/purepython_aes/aes/algorithms/aes256.py",
|
|
41
|
+
"src/purepython_aes/aes/algorithms/aes192.py",
|
|
42
|
+
"tests/__init__.py",
|
|
43
|
+
"tests/test_initial.py",
|
|
44
|
+
"tests/unit/__init__.py",
|
|
45
|
+
"tests/unit/const/__init__.py",
|
|
46
|
+
"tests/unit/const/test_sbox.py",
|
|
47
|
+
"tests/unit/aes/__init__.py",
|
|
48
|
+
"tests/unit/aes/strategies.py",
|
|
49
|
+
"tests/unit/aes/core/test_operations.py",
|
|
50
|
+
"tests/unit/aes/core/__init__.py",
|
|
51
|
+
"tests/unit/aes/core/strategies.py",
|
|
52
|
+
"tests/unit/aes/core/test_state.py",
|
|
53
|
+
"tests/unit/aes/core/test_aes.py",
|
|
54
|
+
"tests/unit/aes/core/test_expansion.py",
|
|
55
|
+
"tests/unit/aes/padding/__init__.py",
|
|
56
|
+
"tests/unit/aes/padding/strategies.py",
|
|
57
|
+
"tests/unit/aes/padding/test_pkcs7.py",
|
|
58
|
+
"tests/unit/aes/padding/test_ansix923.py",
|
|
59
|
+
"tests/unit/aes/padding/test_iso10126.py",
|
|
60
|
+
"tests/unit/aes/padding/test_zero.py",
|
|
61
|
+
"tests/unit/aes/padding/conftest.py",
|
|
62
|
+
"tests/unit/aes/padding/test_iso7816.py",
|
|
63
|
+
"tests/unit/aes/padding/test_no.py",
|
|
64
|
+
"tests/unit/aes/modes/test_operations.py",
|
|
65
|
+
"tests/unit/aes/modes/__init__.py",
|
|
66
|
+
"tests/unit/aes/modes/block/__init__.py",
|
|
67
|
+
"tests/unit/aes/modes/block/test_ecb.py",
|
|
68
|
+
"tests/unit/aes/algorithms/test_aes256.py",
|
|
69
|
+
"tests/unit/aes/algorithms/__init__.py",
|
|
70
|
+
"tests/unit/aes/algorithms/test_aes128.py",
|
|
71
|
+
"tests/unit/aes/algorithms/test_aes192.py",
|
|
72
|
+
"tests/unit/aes/algorithms/conftest.py",
|
|
73
|
+
".github/workflows/release.yml",
|
|
74
|
+
".github/workflows/pre-commit.yml"
|
|
75
|
+
]
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
purepython_aes
|