purepython-aes 0.3.1__tar.gz → 0.4.0__tar.gz

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.
Files changed (116) hide show
  1. {purepython_aes-0.3.1/src/purepython_aes.egg-info → purepython_aes-0.4.0}/PKG-INFO +1 -1
  2. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/__init__.py +6 -0
  3. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/__init__.py +6 -0
  4. purepython_aes-0.4.0/src/purepython_aes/aes/modes/__init__.py +14 -0
  5. purepython_aes-0.4.0/src/purepython_aes/aes/modes/_base.py +31 -0
  6. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/modes/block/_base.py +2 -6
  7. purepython_aes-0.4.0/src/purepython_aes/aes/modes/stream/__init__.py +4 -0
  8. purepython_aes-0.4.0/src/purepython_aes/aes/modes/stream/_base.py +40 -0
  9. purepython_aes-0.4.0/src/purepython_aes/aes/modes/stream/cfb.py +76 -0
  10. purepython_aes-0.4.0/src/purepython_aes/types/__init__.py +9 -0
  11. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/types/finite/__init__.py +2 -1
  12. purepython_aes-0.4.0/src/purepython_aes/types/finite/literals.py +6 -0
  13. {purepython_aes-0.3.1 → purepython_aes-0.4.0/src/purepython_aes.egg-info}/PKG-INFO +1 -1
  14. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes.egg-info/SOURCES.txt +6 -0
  15. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes.egg-info/scm_file_list.json +6 -0
  16. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes.egg-info/scm_version.json +2 -2
  17. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/modes/block/test_pcbc.py +1 -1
  18. purepython_aes-0.4.0/tests/unit/aes/modes/stream/test_cfb.py +48 -0
  19. purepython_aes-0.4.0/tests/unit/const/__init__.py +0 -0
  20. purepython_aes-0.3.1/src/purepython_aes/aes/modes/__init__.py +0 -4
  21. purepython_aes-0.3.1/src/purepython_aes/aes/modes/_base.py +0 -14
  22. purepython_aes-0.3.1/src/purepython_aes/types/__init__.py +0 -5
  23. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/.github/workflows/pre-commit.yml +0 -0
  24. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/.github/workflows/release.yml +0 -0
  25. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/.gitignore +0 -0
  26. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/.pre-commit-config.yaml +0 -0
  27. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/LISENCE.txt +0 -0
  28. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/README.md +0 -0
  29. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/pyproject.toml +0 -0
  30. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/scripts/check-test-marks.sh +0 -0
  31. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/setup.cfg +0 -0
  32. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/__init__.py +0 -0
  33. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/fast/__init__.py +0 -0
  34. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/fast/aes.py +0 -0
  35. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/fast/algorithms/__init__.py +0 -0
  36. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/fast/algorithms/aes128.py +0 -0
  37. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/fast/algorithms/aes192.py +0 -0
  38. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/fast/algorithms/aes256.py +0 -0
  39. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/fast/expansion.py +0 -0
  40. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/fast/keys.py +0 -0
  41. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/fast/ttables.py +0 -0
  42. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/interface.py +0 -0
  43. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/reference/__init__.py +0 -0
  44. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/reference/aes.py +0 -0
  45. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/reference/algorithms/__init__.py +0 -0
  46. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/reference/algorithms/aes128.py +0 -0
  47. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/reference/algorithms/aes192.py +0 -0
  48. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/reference/algorithms/aes256.py +0 -0
  49. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/reference/expansion.py +0 -0
  50. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/reference/operations.py +0 -0
  51. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/core/reference/state.py +0 -0
  52. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/modes/block/__init__.py +0 -0
  53. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/modes/block/cbc.py +0 -0
  54. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/modes/block/ecb.py +0 -0
  55. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/modes/block/pcbc.py +0 -0
  56. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/modes/operations.py +0 -0
  57. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/padding/__init__.py +0 -0
  58. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/padding/_base.py +0 -0
  59. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/padding/ansix923.py +0 -0
  60. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/padding/iso10126.py +0 -0
  61. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/padding/iso7816.py +0 -0
  62. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/padding/no.py +0 -0
  63. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/padding/pkcs7.py +0 -0
  64. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/aes/padding/zero.py +0 -0
  65. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/const/__init__.py +0 -0
  66. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/const/aes.py +0 -0
  67. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/const/sbox.py +0 -0
  68. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/py.typed +0 -0
  69. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes/types/finite/aliases.py +0 -0
  70. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes.egg-info/dependency_links.txt +0 -0
  71. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes.egg-info/requires.txt +0 -0
  72. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/src/purepython_aes.egg-info/top_level.txt +0 -0
  73. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/__init__.py +0 -0
  74. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/test_initial.py +0 -0
  75. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/__init__.py +0 -0
  76. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/__init__.py +0 -0
  77. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/__init__.py +0 -0
  78. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/conftest.py +0 -0
  79. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/fast/__init__.py +0 -0
  80. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/fast/algorithms/__init__.py +0 -0
  81. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/fast/algorithms/strategies.py +0 -0
  82. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/fast/algorithms/test_aes128.py +0 -0
  83. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/fast/algorithms/test_aes192.py +0 -0
  84. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/fast/algorithms/test_aes256.py +0 -0
  85. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/fast/test_aes.py +0 -0
  86. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/fast/test_expansion.py +0 -0
  87. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/fast/test_keys.py +0 -0
  88. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/fast/test_ttables.py +0 -0
  89. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/reference/__init__.py +0 -0
  90. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/reference/algorithms/__init__.py +0 -0
  91. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/reference/algorithms/strategies.py +0 -0
  92. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/reference/algorithms/test_aes128.py +0 -0
  93. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/reference/algorithms/test_aes192.py +0 -0
  94. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/reference/algorithms/test_aes256.py +0 -0
  95. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/reference/test_aes.py +0 -0
  96. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/reference/test_expansion.py +0 -0
  97. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/reference/test_operations.py +0 -0
  98. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/reference/test_state.py +0 -0
  99. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/core/strategies.py +0 -0
  100. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/modes/__init__.py +0 -0
  101. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/modes/block/__init__.py +0 -0
  102. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/modes/block/test_cbc.py +0 -0
  103. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/modes/block/test_ecb.py +0 -0
  104. {purepython_aes-0.3.1/tests/unit/aes/padding → purepython_aes-0.4.0/tests/unit/aes/modes/stream}/__init__.py +0 -0
  105. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/modes/test_operations.py +0 -0
  106. {purepython_aes-0.3.1/tests/unit/const → purepython_aes-0.4.0/tests/unit/aes/padding}/__init__.py +0 -0
  107. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/padding/conftest.py +0 -0
  108. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/padding/strategies.py +0 -0
  109. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/padding/test_ansix923.py +0 -0
  110. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/padding/test_iso10126.py +0 -0
  111. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/padding/test_iso7816.py +0 -0
  112. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/padding/test_no.py +0 -0
  113. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/padding/test_pkcs7.py +0 -0
  114. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/padding/test_zero.py +0 -0
  115. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/aes/strategies.py +0 -0
  116. {purepython_aes-0.3.1 → purepython_aes-0.4.0}/tests/unit/const/test_sbox.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: purepython-aes
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: Pure-Python implementation of AES encryption algorithm.
5
5
  Author-email: Emelianov Artem <penguin5726@proton.me>
6
6
  License-Expression: MIT
@@ -8,6 +8,8 @@ from purepython_aes.aes import (
8
8
  BasePadding,
9
9
  BlockCipherMode,
10
10
  CbcMode,
11
+ CfbMode,
12
+ CipherMode,
11
13
  EcbMode,
12
14
  Iso7816Padding,
13
15
  Iso10126Padding,
@@ -17,6 +19,7 @@ from purepython_aes.aes import (
17
19
  ReferenceAes128,
18
20
  ReferenceAes192,
19
21
  ReferenceAes256,
22
+ StreamCipherMode,
20
23
  ZeroPadding,
21
24
  )
22
25
 
@@ -30,6 +33,8 @@ __all__: list[str] = [
30
33
  'BasePadding',
31
34
  'BlockCipherMode',
32
35
  'CbcMode',
36
+ 'CfbMode',
37
+ 'CipherMode',
33
38
  'EcbMode',
34
39
  'Iso7816Padding',
35
40
  'Iso10126Padding',
@@ -39,5 +44,6 @@ __all__: list[str] = [
39
44
  'ReferenceAes128',
40
45
  'ReferenceAes192',
41
46
  'ReferenceAes256',
47
+ 'StreamCipherMode',
42
48
  'ZeroPadding',
43
49
  ]
@@ -11,8 +11,11 @@ from purepython_aes.aes.modes import (
11
11
  AesMode,
12
12
  BlockCipherMode,
13
13
  CbcMode,
14
+ CfbMode,
15
+ CipherMode,
14
16
  EcbMode,
15
17
  PcbcMode,
18
+ StreamCipherMode,
16
19
  )
17
20
  from purepython_aes.aes.padding import (
18
21
  AnsiX923Padding,
@@ -35,8 +38,11 @@ __all__: list[str] = [
35
38
  'AesMode',
36
39
  'BlockCipherMode',
37
40
  'CbcMode',
41
+ 'CfbMode',
42
+ 'CipherMode',
38
43
  'EcbMode',
39
44
  'PcbcMode',
45
+ 'StreamCipherMode',
40
46
  'AnsiX923Padding',
41
47
  'BasePadding',
42
48
  'Iso7816Padding',
@@ -0,0 +1,14 @@
1
+ from purepython_aes.aes.modes._base import AesMode, CipherMode
2
+ from purepython_aes.aes.modes.block import BlockCipherMode, CbcMode, EcbMode, PcbcMode
3
+ from purepython_aes.aes.modes.stream import CfbMode, StreamCipherMode
4
+
5
+ __all__: list[str] = [
6
+ 'AesMode',
7
+ 'CipherMode',
8
+ 'BlockCipherMode',
9
+ 'CbcMode',
10
+ 'EcbMode',
11
+ 'PcbcMode',
12
+ 'CfbMode',
13
+ 'StreamCipherMode',
14
+ ]
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from dataclasses import dataclass
5
+
6
+ from purepython_aes.aes.core.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."""
15
+
16
+
17
+ @dataclass(slots=True)
18
+ class CipherMode(AesMode):
19
+ """Base class for all AES Cipher modes of operation."""
20
+
21
+ @abstractmethod
22
+ def encrypt(self, plaintext: bytes) -> bytes:
23
+ """Encrypt a byte string."""
24
+
25
+ raise NotImplementedError
26
+
27
+ @abstractmethod
28
+ def decrypt(self, ciphertext: bytes) -> bytes:
29
+ """Decrypt a byte string."""
30
+
31
+ raise NotImplementedError
@@ -1,13 +1,13 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from dataclasses import dataclass
3
3
 
4
- from purepython_aes.aes.modes._base import AesMode
4
+ from purepython_aes.aes.modes._base import CipherMode
5
5
  from purepython_aes.aes.padding import BasePadding
6
6
  from purepython_aes.const import AES_BLOCK_SIZE
7
7
 
8
8
 
9
9
  @dataclass(slots=True)
10
- class BlockCipherMode(AesMode, ABC):
10
+ class BlockCipherMode(CipherMode, ABC):
11
11
  padding: BasePadding
12
12
 
13
13
  @abstractmethod
@@ -23,13 +23,9 @@ class BlockCipherMode(AesMode, ABC):
23
23
  raise NotImplementedError
24
24
 
25
25
  def encrypt(self, plaintext: bytes) -> bytes:
26
- """Pad and encrypt a byte string."""
27
-
28
26
  return self.__encrypt_blocks__(self.padding.pad(plaintext))
29
27
 
30
28
  def decrypt(self, ciphertext: bytes) -> bytes:
31
- """Decrypt and unpad a byte string."""
32
-
33
29
  if len(ciphertext) % AES_BLOCK_SIZE != 0:
34
30
  raise ValueError(f'len(ciphertext) % {AES_BLOCK_SIZE} must be 0')
35
31
  return self.padding.unpad(self.__decrypt_blocks__(ciphertext))
@@ -0,0 +1,4 @@
1
+ from purepython_aes.aes.modes.stream._base import StreamCipherMode
2
+ from purepython_aes.aes.modes.stream.cfb import CfbMode
3
+
4
+ __all__: list[str] = ['StreamCipherMode', 'CfbMode']
@@ -0,0 +1,40 @@
1
+ from abc import ABC, abstractmethod
2
+ from dataclasses import dataclass
3
+ from secrets import token_bytes
4
+
5
+ from purepython_aes.aes.modes._base import CipherMode
6
+ from purepython_aes.const import AES_BLOCK_SIZE
7
+
8
+
9
+ @dataclass(slots=True)
10
+ class StreamCipherMode(CipherMode, ABC):
11
+ def encrypt(self, plaintext: bytes) -> bytes:
12
+ iv: bytes = token_bytes(AES_BLOCK_SIZE)
13
+ ciphertext: bytes = self.transform_for_encryption(iv, plaintext)
14
+ return iv + ciphertext
15
+
16
+ def decrypt(self, ciphertext: bytes) -> bytes:
17
+ return self.transform_for_decryption(
18
+ initialization_value=ciphertext[:AES_BLOCK_SIZE],
19
+ ciphertext=ciphertext[AES_BLOCK_SIZE:],
20
+ )
21
+
22
+ @abstractmethod
23
+ def transform_for_encryption(
24
+ self,
25
+ initialization_value: bytes,
26
+ plaintext: bytes,
27
+ ) -> bytes:
28
+ """Transform plaintext into ciphertext."""
29
+
30
+ raise NotImplementedError
31
+
32
+ @abstractmethod
33
+ def transform_for_decryption(
34
+ self,
35
+ initialization_value: bytes,
36
+ ciphertext: bytes,
37
+ ) -> bytes:
38
+ """Transform ciphertext into plaintext."""
39
+
40
+ raise NotImplementedError
@@ -0,0 +1,76 @@
1
+ from dataclasses import dataclass
2
+ from typing import final
3
+
4
+ from purepython_aes.aes.modes.stream._base import StreamCipherMode
5
+ from purepython_aes.const import AES_BLOCK_SIZE
6
+ from purepython_aes.types import CfbSegmentSize
7
+
8
+
9
+ @final
10
+ @dataclass(slots=True)
11
+ class CfbMode(StreamCipherMode):
12
+ """Cipher FeedBack mode of operation."""
13
+
14
+ segment_size: CfbSegmentSize = AES_BLOCK_SIZE # type: ignore[assignment]
15
+ """Feedback segment size in bytes."""
16
+
17
+ def __post_init__(self) -> None:
18
+ if self.segment_size < 1 or self.segment_size > AES_BLOCK_SIZE:
19
+ raise ValueError(
20
+ ', '.join(
21
+ (
22
+ f'expected 1 <= segment_size <= {AES_BLOCK_SIZE}',
23
+ f'got {self.segment_size}',
24
+ )
25
+ )
26
+ )
27
+
28
+ def transform_for_encryption(
29
+ self,
30
+ initialization_value: bytes,
31
+ plaintext: bytes,
32
+ ) -> bytes:
33
+ feedback_register: bytes = initialization_value
34
+ plaintext_size: int = len(plaintext)
35
+ ciphertext: bytearray = bytearray(plaintext_size)
36
+ for segment_start in range(0, plaintext_size, self.segment_size):
37
+ segment_end: int = min(segment_start + self.segment_size, plaintext_size)
38
+ segment_length: int = segment_end - segment_start
39
+ encrypted_register: bytes = self.algorithm.encrypt_block(feedback_register)
40
+ for segment_index in range(segment_length):
41
+ ciphertext[segment_start + segment_index] = (
42
+ plaintext[segment_start + segment_index]
43
+ ^ encrypted_register[segment_index]
44
+ )
45
+ ciphertext_segment: bytes = bytes(ciphertext[segment_start:segment_end])
46
+ feedback_register = (
47
+ feedback_register[segment_length:] + bytes(ciphertext_segment)
48
+ if segment_length != AES_BLOCK_SIZE
49
+ else ciphertext_segment
50
+ )
51
+ return bytes(ciphertext)
52
+
53
+ def transform_for_decryption(
54
+ self,
55
+ initialization_value: bytes,
56
+ ciphertext: bytes,
57
+ ) -> bytes:
58
+ feedback_register: bytes = initialization_value
59
+ ciphertext_size: int = len(ciphertext)
60
+ plaintext: bytearray = bytearray(ciphertext_size)
61
+ for segment_start in range(0, ciphertext_size, self.segment_size):
62
+ segment_end: int = min(segment_start + self.segment_size, ciphertext_size)
63
+ segment_length: int = segment_end - segment_start
64
+ encrypted_register: bytes = self.algorithm.encrypt_block(feedback_register)
65
+ for segment_index in range(segment_length):
66
+ plaintext[segment_start + segment_index] = (
67
+ ciphertext[segment_start + segment_index]
68
+ ^ encrypted_register[segment_index]
69
+ )
70
+ ciphertext_segment: bytes = ciphertext[segment_start:segment_end]
71
+ feedback_register = (
72
+ feedback_register[segment_length:] + ciphertext_segment
73
+ if segment_length != AES_BLOCK_SIZE
74
+ else ciphertext_segment
75
+ )
76
+ return bytes(plaintext)
@@ -0,0 +1,9 @@
1
+ # noqa: A005
2
+ from purepython_aes.types.finite import (
3
+ CfbSegmentSize,
4
+ IntLookupTable256,
5
+ RoundKey,
6
+ RoundKeys,
7
+ )
8
+
9
+ __all__: list[str] = ['CfbSegmentSize', 'IntLookupTable256', 'RoundKey', 'RoundKeys']
@@ -1,3 +1,4 @@
1
1
  from purepython_aes.types.finite.aliases import IntLookupTable256, RoundKey, RoundKeys
2
+ from purepython_aes.types.finite.literals import CfbSegmentSize
2
3
 
3
- __all__: list[str] = ['IntLookupTable256', 'RoundKey', 'RoundKeys']
4
+ __all__: list[str] = ['IntLookupTable256', 'RoundKey', 'RoundKeys', 'CfbSegmentSize']
@@ -0,0 +1,6 @@
1
+ from typing import Literal, TypeAlias
2
+
3
+ CfbSegmentSize: TypeAlias = Literal[
4
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
5
+ ]
6
+ """Allowed value for `segment_size` field of CfbMode."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: purepython-aes
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: Pure-Python implementation of AES encryption algorithm.
5
5
  Author-email: Emelianov Artem <penguin5726@proton.me>
6
6
  License-Expression: MIT
@@ -44,6 +44,9 @@ src/purepython_aes/aes/modes/block/_base.py
44
44
  src/purepython_aes/aes/modes/block/cbc.py
45
45
  src/purepython_aes/aes/modes/block/ecb.py
46
46
  src/purepython_aes/aes/modes/block/pcbc.py
47
+ src/purepython_aes/aes/modes/stream/__init__.py
48
+ src/purepython_aes/aes/modes/stream/_base.py
49
+ src/purepython_aes/aes/modes/stream/cfb.py
47
50
  src/purepython_aes/aes/padding/__init__.py
48
51
  src/purepython_aes/aes/padding/_base.py
49
52
  src/purepython_aes/aes/padding/ansix923.py
@@ -58,6 +61,7 @@ src/purepython_aes/const/sbox.py
58
61
  src/purepython_aes/types/__init__.py
59
62
  src/purepython_aes/types/finite/__init__.py
60
63
  src/purepython_aes/types/finite/aliases.py
64
+ src/purepython_aes/types/finite/literals.py
61
65
  tests/__init__.py
62
66
  tests/test_initial.py
63
67
  tests/unit/__init__.py
@@ -92,6 +96,8 @@ tests/unit/aes/modes/block/__init__.py
92
96
  tests/unit/aes/modes/block/test_cbc.py
93
97
  tests/unit/aes/modes/block/test_ecb.py
94
98
  tests/unit/aes/modes/block/test_pcbc.py
99
+ tests/unit/aes/modes/stream/__init__.py
100
+ tests/unit/aes/modes/stream/test_cfb.py
95
101
  tests/unit/aes/padding/__init__.py
96
102
  tests/unit/aes/padding/conftest.py
97
103
  tests/unit/aes/padding/strategies.py
@@ -11,6 +11,7 @@
11
11
  "src/purepython_aes/types/__init__.py",
12
12
  "src/purepython_aes/types/finite/aliases.py",
13
13
  "src/purepython_aes/types/finite/__init__.py",
14
+ "src/purepython_aes/types/finite/literals.py",
14
15
  "src/purepython_aes/const/__init__.py",
15
16
  "src/purepython_aes/const/aes.py",
16
17
  "src/purepython_aes/const/sbox.py",
@@ -46,6 +47,9 @@
46
47
  "src/purepython_aes/aes/modes/_base.py",
47
48
  "src/purepython_aes/aes/modes/__init__.py",
48
49
  "src/purepython_aes/aes/modes/operations.py",
50
+ "src/purepython_aes/aes/modes/stream/_base.py",
51
+ "src/purepython_aes/aes/modes/stream/cfb.py",
52
+ "src/purepython_aes/aes/modes/stream/__init__.py",
49
53
  "src/purepython_aes/aes/modes/block/_base.py",
50
54
  "src/purepython_aes/aes/modes/block/__init__.py",
51
55
  "src/purepython_aes/aes/modes/block/cbc.py",
@@ -92,6 +96,8 @@
92
96
  "tests/unit/aes/padding/test_no.py",
93
97
  "tests/unit/aes/modes/test_operations.py",
94
98
  "tests/unit/aes/modes/__init__.py",
99
+ "tests/unit/aes/modes/stream/test_cfb.py",
100
+ "tests/unit/aes/modes/stream/__init__.py",
95
101
  "tests/unit/aes/modes/block/__init__.py",
96
102
  "tests/unit/aes/modes/block/test_pcbc.py",
97
103
  "tests/unit/aes/modes/block/test_ecb.py",
@@ -1,7 +1,7 @@
1
1
  {
2
- "tag": "0.3.1",
2
+ "tag": "0.4.0",
3
3
  "distance": 0,
4
- "node": "g35d4036fe4f9868f92a439cff80b412c016aa475",
4
+ "node": "ge127baa0ad018fcbb3e51110b1a486ae5ffe9daa",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-07-14"
@@ -25,7 +25,7 @@ from tests.unit.aes.strategies import aes256key, aes_blocks
25
25
 
26
26
 
27
27
  @mark.quick
28
- class TestCbcMode:
28
+ class TestPcbcMode:
29
29
  @staticmethod
30
30
  @mark.parametrize(
31
31
  ['aes', 'padding'],
@@ -0,0 +1,48 @@
1
+ from itertools import product
2
+
3
+ from hypothesis import given
4
+ from hypothesis.strategies import binary
5
+ from pytest import mark
6
+
7
+ from purepython_aes import (
8
+ Aes,
9
+ Aes128,
10
+ Aes192,
11
+ Aes256,
12
+ CfbMode,
13
+ ReferenceAes128,
14
+ ReferenceAes192,
15
+ ReferenceAes256,
16
+ )
17
+ from purepython_aes.types import CfbSegmentSize
18
+ from tests.unit.aes.strategies import aes256key
19
+
20
+
21
+ @mark.quick
22
+ class TestCfbMode:
23
+ @staticmethod
24
+ @mark.parametrize(
25
+ ['aes', 'segment_size'],
26
+ tuple(
27
+ product(
28
+ [
29
+ Aes128,
30
+ Aes192,
31
+ Aes256,
32
+ ReferenceAes128,
33
+ ReferenceAes192,
34
+ ReferenceAes256,
35
+ ],
36
+ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
37
+ ),
38
+ ),
39
+ )
40
+ @given(key=aes256key, data=binary())
41
+ def test_decrypt_inverts_encrypt(
42
+ aes: type[Aes],
43
+ segment_size: CfbSegmentSize,
44
+ key: bytes,
45
+ data: bytes,
46
+ ) -> None:
47
+ ecb: CfbMode = CfbMode(aes(key[:aes.__key_size__]), segment_size) # fmt: skip
48
+ assert ecb.decrypt(ecb.encrypt(data)) == data
File without changes
@@ -1,4 +0,0 @@
1
- from purepython_aes.aes.modes._base import AesMode
2
- from purepython_aes.aes.modes.block import BlockCipherMode, CbcMode, EcbMode, PcbcMode
3
-
4
- __all__: list[str] = ['AesMode', 'BlockCipherMode', 'CbcMode', 'EcbMode', 'PcbcMode']
@@ -1,14 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from abc import ABC
4
- from dataclasses import dataclass
5
-
6
- from purepython_aes.aes.core.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."""
@@ -1,5 +0,0 @@
1
- # noqa: A005
2
-
3
- from purepython_aes.types.finite import IntLookupTable256, RoundKey, RoundKeys
4
-
5
- __all__: list[str] = ['IntLookupTable256', 'RoundKey', 'RoundKeys']
File without changes
File without changes