purepython-aes 0.4.0__tar.gz → 0.6.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 (118) hide show
  1. {purepython_aes-0.4.0/src/purepython_aes.egg-info → purepython_aes-0.6.0}/PKG-INFO +1 -1
  2. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/__init__.py +4 -0
  3. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/__init__.py +4 -0
  4. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/modes/__init__.py +3 -1
  5. purepython_aes-0.6.0/src/purepython_aes/aes/modes/stream/__init__.py +6 -0
  6. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/modes/stream/_base.py +4 -4
  7. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/modes/stream/cfb.py +2 -2
  8. purepython_aes-0.6.0/src/purepython_aes/aes/modes/stream/ctr.py +50 -0
  9. purepython_aes-0.6.0/src/purepython_aes/aes/modes/stream/ofb.py +45 -0
  10. {purepython_aes-0.4.0 → purepython_aes-0.6.0/src/purepython_aes.egg-info}/PKG-INFO +1 -1
  11. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes.egg-info/SOURCES.txt +4 -0
  12. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes.egg-info/scm_file_list.json +4 -0
  13. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes.egg-info/scm_version.json +2 -2
  14. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/fast/test_keys.py +2 -2
  15. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/reference/test_expansion.py +5 -5
  16. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/reference/test_operations.py +6 -6
  17. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/reference/test_state.py +1 -1
  18. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/modes/block/test_cbc.py +8 -8
  19. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/modes/block/test_ecb.py +2 -2
  20. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/modes/block/test_pcbc.py +8 -8
  21. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/modes/stream/test_cfb.py +2 -2
  22. purepython_aes-0.6.0/tests/unit/aes/modes/stream/test_ctr.py +28 -0
  23. purepython_aes-0.6.0/tests/unit/aes/modes/stream/test_ofb.py +28 -0
  24. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/modes/test_operations.py +2 -2
  25. purepython_aes-0.4.0/src/purepython_aes/aes/modes/stream/__init__.py +0 -4
  26. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/.github/workflows/pre-commit.yml +0 -0
  27. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/.github/workflows/release.yml +0 -0
  28. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/.gitignore +0 -0
  29. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/.pre-commit-config.yaml +0 -0
  30. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/LISENCE.txt +0 -0
  31. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/README.md +0 -0
  32. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/pyproject.toml +0 -0
  33. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/scripts/check-test-marks.sh +0 -0
  34. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/setup.cfg +0 -0
  35. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/__init__.py +0 -0
  36. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/fast/__init__.py +0 -0
  37. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/fast/aes.py +0 -0
  38. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/fast/algorithms/__init__.py +0 -0
  39. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/fast/algorithms/aes128.py +0 -0
  40. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/fast/algorithms/aes192.py +0 -0
  41. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/fast/algorithms/aes256.py +0 -0
  42. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/fast/expansion.py +0 -0
  43. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/fast/keys.py +0 -0
  44. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/fast/ttables.py +0 -0
  45. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/interface.py +0 -0
  46. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/reference/__init__.py +0 -0
  47. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/reference/aes.py +0 -0
  48. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/reference/algorithms/__init__.py +0 -0
  49. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/reference/algorithms/aes128.py +0 -0
  50. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/reference/algorithms/aes192.py +0 -0
  51. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/reference/algorithms/aes256.py +0 -0
  52. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/reference/expansion.py +0 -0
  53. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/reference/operations.py +0 -0
  54. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/core/reference/state.py +0 -0
  55. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/modes/_base.py +0 -0
  56. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/modes/block/__init__.py +0 -0
  57. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/modes/block/_base.py +0 -0
  58. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/modes/block/cbc.py +0 -0
  59. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/modes/block/ecb.py +0 -0
  60. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/modes/block/pcbc.py +0 -0
  61. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/modes/operations.py +0 -0
  62. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/padding/__init__.py +0 -0
  63. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/padding/_base.py +0 -0
  64. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/padding/ansix923.py +0 -0
  65. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/padding/iso10126.py +0 -0
  66. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/padding/iso7816.py +0 -0
  67. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/padding/no.py +0 -0
  68. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/padding/pkcs7.py +0 -0
  69. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/aes/padding/zero.py +0 -0
  70. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/const/__init__.py +0 -0
  71. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/const/aes.py +0 -0
  72. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/const/sbox.py +0 -0
  73. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/py.typed +0 -0
  74. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/types/__init__.py +0 -0
  75. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/types/finite/__init__.py +0 -0
  76. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/types/finite/aliases.py +0 -0
  77. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes/types/finite/literals.py +0 -0
  78. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes.egg-info/dependency_links.txt +0 -0
  79. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes.egg-info/requires.txt +0 -0
  80. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/src/purepython_aes.egg-info/top_level.txt +0 -0
  81. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/__init__.py +0 -0
  82. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/test_initial.py +0 -0
  83. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/__init__.py +0 -0
  84. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/__init__.py +0 -0
  85. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/__init__.py +0 -0
  86. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/conftest.py +0 -0
  87. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/fast/__init__.py +0 -0
  88. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/fast/algorithms/__init__.py +0 -0
  89. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/fast/algorithms/strategies.py +0 -0
  90. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/fast/algorithms/test_aes128.py +0 -0
  91. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/fast/algorithms/test_aes192.py +0 -0
  92. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/fast/algorithms/test_aes256.py +0 -0
  93. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/fast/test_aes.py +0 -0
  94. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/fast/test_expansion.py +0 -0
  95. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/fast/test_ttables.py +0 -0
  96. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/reference/__init__.py +0 -0
  97. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/reference/algorithms/__init__.py +0 -0
  98. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/reference/algorithms/strategies.py +0 -0
  99. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/reference/algorithms/test_aes128.py +0 -0
  100. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/reference/algorithms/test_aes192.py +0 -0
  101. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/reference/algorithms/test_aes256.py +0 -0
  102. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/reference/test_aes.py +0 -0
  103. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/core/strategies.py +0 -0
  104. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/modes/__init__.py +0 -0
  105. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/modes/block/__init__.py +0 -0
  106. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/modes/stream/__init__.py +0 -0
  107. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/padding/__init__.py +0 -0
  108. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/padding/conftest.py +0 -0
  109. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/padding/strategies.py +0 -0
  110. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/padding/test_ansix923.py +0 -0
  111. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/padding/test_iso10126.py +0 -0
  112. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/padding/test_iso7816.py +0 -0
  113. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/padding/test_no.py +0 -0
  114. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/padding/test_pkcs7.py +0 -0
  115. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/padding/test_zero.py +0 -0
  116. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/aes/strategies.py +0 -0
  117. {purepython_aes-0.4.0 → purepython_aes-0.6.0}/tests/unit/const/__init__.py +0 -0
  118. {purepython_aes-0.4.0 → purepython_aes-0.6.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.4.0
3
+ Version: 0.6.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
@@ -10,10 +10,12 @@ from purepython_aes.aes import (
10
10
  CbcMode,
11
11
  CfbMode,
12
12
  CipherMode,
13
+ CtrMode,
13
14
  EcbMode,
14
15
  Iso7816Padding,
15
16
  Iso10126Padding,
16
17
  NoPadding,
18
+ OfbMode,
17
19
  PcbcMode,
18
20
  Pkcs7Padding,
19
21
  ReferenceAes128,
@@ -35,10 +37,12 @@ __all__: list[str] = [
35
37
  'CbcMode',
36
38
  'CfbMode',
37
39
  'CipherMode',
40
+ 'CtrMode',
38
41
  'EcbMode',
39
42
  'Iso7816Padding',
40
43
  'Iso10126Padding',
41
44
  'NoPadding',
45
+ 'OfbMode',
42
46
  'PcbcMode',
43
47
  'Pkcs7Padding',
44
48
  'ReferenceAes128',
@@ -13,7 +13,9 @@ from purepython_aes.aes.modes import (
13
13
  CbcMode,
14
14
  CfbMode,
15
15
  CipherMode,
16
+ CtrMode,
16
17
  EcbMode,
18
+ OfbMode,
17
19
  PcbcMode,
18
20
  StreamCipherMode,
19
21
  )
@@ -40,7 +42,9 @@ __all__: list[str] = [
40
42
  'CbcMode',
41
43
  'CfbMode',
42
44
  'CipherMode',
45
+ 'CtrMode',
43
46
  'EcbMode',
47
+ 'OfbMode',
44
48
  'PcbcMode',
45
49
  'StreamCipherMode',
46
50
  'AnsiX923Padding',
@@ -1,6 +1,6 @@
1
1
  from purepython_aes.aes.modes._base import AesMode, CipherMode
2
2
  from purepython_aes.aes.modes.block import BlockCipherMode, CbcMode, EcbMode, PcbcMode
3
- from purepython_aes.aes.modes.stream import CfbMode, StreamCipherMode
3
+ from purepython_aes.aes.modes.stream import CfbMode, CtrMode, OfbMode, StreamCipherMode
4
4
 
5
5
  __all__: list[str] = [
6
6
  'AesMode',
@@ -10,5 +10,7 @@ __all__: list[str] = [
10
10
  'EcbMode',
11
11
  'PcbcMode',
12
12
  'CfbMode',
13
+ 'CtrMode',
14
+ 'OfbMode',
13
15
  'StreamCipherMode',
14
16
  ]
@@ -0,0 +1,6 @@
1
+ from purepython_aes.aes.modes.stream._base import StreamCipherMode
2
+ from purepython_aes.aes.modes.stream.cfb import CfbMode
3
+ from purepython_aes.aes.modes.stream.ctr import CtrMode
4
+ from purepython_aes.aes.modes.stream.ofb import OfbMode
5
+
6
+ __all__: list[str] = ['StreamCipherMode', 'CfbMode', 'CtrMode', 'OfbMode']
@@ -10,17 +10,17 @@ from purepython_aes.const import AES_BLOCK_SIZE
10
10
  class StreamCipherMode(CipherMode, ABC):
11
11
  def encrypt(self, plaintext: bytes) -> bytes:
12
12
  iv: bytes = token_bytes(AES_BLOCK_SIZE)
13
- ciphertext: bytes = self.transform_for_encryption(iv, plaintext)
13
+ ciphertext: bytes = self.__encrypt_stream__(iv, plaintext)
14
14
  return iv + ciphertext
15
15
 
16
16
  def decrypt(self, ciphertext: bytes) -> bytes:
17
- return self.transform_for_decryption(
17
+ return self.__decrypt_stream__(
18
18
  initialization_value=ciphertext[:AES_BLOCK_SIZE],
19
19
  ciphertext=ciphertext[AES_BLOCK_SIZE:],
20
20
  )
21
21
 
22
22
  @abstractmethod
23
- def transform_for_encryption(
23
+ def __encrypt_stream__(
24
24
  self,
25
25
  initialization_value: bytes,
26
26
  plaintext: bytes,
@@ -30,7 +30,7 @@ class StreamCipherMode(CipherMode, ABC):
30
30
  raise NotImplementedError
31
31
 
32
32
  @abstractmethod
33
- def transform_for_decryption(
33
+ def __decrypt_stream__(
34
34
  self,
35
35
  initialization_value: bytes,
36
36
  ciphertext: bytes,
@@ -25,7 +25,7 @@ class CfbMode(StreamCipherMode):
25
25
  )
26
26
  )
27
27
 
28
- def transform_for_encryption(
28
+ def __encrypt_stream__(
29
29
  self,
30
30
  initialization_value: bytes,
31
31
  plaintext: bytes,
@@ -50,7 +50,7 @@ class CfbMode(StreamCipherMode):
50
50
  )
51
51
  return bytes(ciphertext)
52
52
 
53
- def transform_for_decryption(
53
+ def __decrypt_stream__(
54
54
  self,
55
55
  initialization_value: bytes,
56
56
  ciphertext: bytes,
@@ -0,0 +1,50 @@
1
+ # src/purepython_aes/aes/modes/stream/ctr.py
2
+ from dataclasses import dataclass
3
+ from typing import final
4
+
5
+ from purepython_aes.aes.modes.operations import xor
6
+ from purepython_aes.aes.modes.stream._base import StreamCipherMode
7
+ from purepython_aes.const import AES_BLOCK_SIZE
8
+
9
+
10
+ @final
11
+ @dataclass(slots=True)
12
+ class CtrMode(StreamCipherMode):
13
+ """Counter mode of operation."""
14
+
15
+ def __encrypt_stream__(
16
+ self,
17
+ initialization_value: bytes,
18
+ plaintext: bytes,
19
+ ) -> bytes:
20
+ return self.transform(initialization_value, plaintext)
21
+
22
+ def __decrypt_stream__(
23
+ self,
24
+ initialization_value: bytes,
25
+ ciphertext: bytes,
26
+ ) -> bytes:
27
+ return self.transform(initialization_value, ciphertext)
28
+
29
+ def transform(
30
+ self,
31
+ initialization_value: bytes,
32
+ data: bytes,
33
+ ) -> bytes:
34
+ """XOR data with the counter-generated keystream."""
35
+
36
+ block_count: int = (len(data) + AES_BLOCK_SIZE - 1) // AES_BLOCK_SIZE
37
+ initial_counter: int = int.from_bytes(initialization_value, byteorder='big')
38
+ transformed_data: bytearray = bytearray()
39
+ for block_index in range(block_count):
40
+ block_start: int = block_index * AES_BLOCK_SIZE
41
+ data_block: bytes = data[block_start : (block_start + AES_BLOCK_SIZE)]
42
+ counter: int = initial_counter + block_index
43
+ counter_block: bytes = counter.to_bytes(AES_BLOCK_SIZE, byteorder='big')
44
+ keystream_block: bytes = self.algorithm.encrypt_block(counter_block)
45
+ transformed_block: bytes = xor(
46
+ data_block,
47
+ keystream_block[: len(data_block)],
48
+ )
49
+ transformed_data.extend(transformed_block)
50
+ return bytes(transformed_data)
@@ -0,0 +1,45 @@
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
+
7
+
8
+ @final
9
+ @dataclass(slots=True)
10
+ class OfbMode(StreamCipherMode):
11
+ """Output FeedBack mode of operation."""
12
+
13
+ def __encrypt_stream__(
14
+ self,
15
+ initialization_value: bytes,
16
+ plaintext: bytes,
17
+ ) -> bytes:
18
+ return self.__transform_stream__(initialization_value, plaintext)
19
+
20
+ def __decrypt_stream__(
21
+ self,
22
+ initialization_value: bytes,
23
+ ciphertext: bytes,
24
+ ) -> bytes:
25
+ return self.__transform_stream__(initialization_value, ciphertext)
26
+
27
+ def __transform_stream__(
28
+ self,
29
+ initialization_value: bytes,
30
+ data: bytes,
31
+ ) -> bytes:
32
+ """XOR data with the OFB keystream."""
33
+
34
+ feedback_register: bytes = initialization_value
35
+ data_size: int = len(data)
36
+ transformed_data: bytearray = bytearray(data_size)
37
+ for block_start in range(0, data_size, AES_BLOCK_SIZE):
38
+ block_end: int = min(block_start + AES_BLOCK_SIZE, data_size)
39
+ block_length: int = block_end - block_start
40
+ feedback_register = self.algorithm.encrypt_block(feedback_register)
41
+ for block_index in range(block_length):
42
+ transformed_data[block_start + block_index] = (
43
+ data[block_start + block_index] ^ feedback_register[block_index]
44
+ )
45
+ return bytes(transformed_data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: purepython-aes
3
- Version: 0.4.0
3
+ Version: 0.6.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
@@ -47,6 +47,8 @@ src/purepython_aes/aes/modes/block/pcbc.py
47
47
  src/purepython_aes/aes/modes/stream/__init__.py
48
48
  src/purepython_aes/aes/modes/stream/_base.py
49
49
  src/purepython_aes/aes/modes/stream/cfb.py
50
+ src/purepython_aes/aes/modes/stream/ctr.py
51
+ src/purepython_aes/aes/modes/stream/ofb.py
50
52
  src/purepython_aes/aes/padding/__init__.py
51
53
  src/purepython_aes/aes/padding/_base.py
52
54
  src/purepython_aes/aes/padding/ansix923.py
@@ -98,6 +100,8 @@ tests/unit/aes/modes/block/test_ecb.py
98
100
  tests/unit/aes/modes/block/test_pcbc.py
99
101
  tests/unit/aes/modes/stream/__init__.py
100
102
  tests/unit/aes/modes/stream/test_cfb.py
103
+ tests/unit/aes/modes/stream/test_ctr.py
104
+ tests/unit/aes/modes/stream/test_ofb.py
101
105
  tests/unit/aes/padding/__init__.py
102
106
  tests/unit/aes/padding/conftest.py
103
107
  tests/unit/aes/padding/strategies.py
@@ -48,8 +48,10 @@
48
48
  "src/purepython_aes/aes/modes/__init__.py",
49
49
  "src/purepython_aes/aes/modes/operations.py",
50
50
  "src/purepython_aes/aes/modes/stream/_base.py",
51
+ "src/purepython_aes/aes/modes/stream/ctr.py",
51
52
  "src/purepython_aes/aes/modes/stream/cfb.py",
52
53
  "src/purepython_aes/aes/modes/stream/__init__.py",
54
+ "src/purepython_aes/aes/modes/stream/ofb.py",
53
55
  "src/purepython_aes/aes/modes/block/_base.py",
54
56
  "src/purepython_aes/aes/modes/block/__init__.py",
55
57
  "src/purepython_aes/aes/modes/block/cbc.py",
@@ -98,6 +100,8 @@
98
100
  "tests/unit/aes/modes/__init__.py",
99
101
  "tests/unit/aes/modes/stream/test_cfb.py",
100
102
  "tests/unit/aes/modes/stream/__init__.py",
103
+ "tests/unit/aes/modes/stream/test_ofb.py",
104
+ "tests/unit/aes/modes/stream/test_ctr.py",
101
105
  "tests/unit/aes/modes/block/__init__.py",
102
106
  "tests/unit/aes/modes/block/test_pcbc.py",
103
107
  "tests/unit/aes/modes/block/test_ecb.py",
@@ -1,7 +1,7 @@
1
1
  {
2
- "tag": "0.4.0",
2
+ "tag": "0.6.0",
3
3
  "distance": 0,
4
- "node": "ge127baa0ad018fcbb3e51110b1a486ae5ffe9daa",
4
+ "node": "g0b685541a28a77cde6c306ae0d3077cfeb784035",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-07-14"
@@ -19,11 +19,11 @@ from tests.unit.aes.strategies import aes128key, aes192key, aes256key
19
19
  @mark.quick
20
20
  @mark.parametrize(
21
21
  'round_count',
22
- (
22
+ [
23
23
  AES_128_ROUND_COUNT,
24
24
  AES_192_ROUND_COUNT,
25
25
  AES_256_ROUND_COUNT,
26
- ),
26
+ ],
27
27
  )
28
28
  def test_preserves_round_key_count(round_count: int) -> None:
29
29
  encryption_round_keys: RoundKeys = [
@@ -13,15 +13,15 @@ from purepython_aes.const import (
13
13
  AES_BLOCK_SIZE,
14
14
  )
15
15
 
16
- AES_CONFIGURATIONS: Final[tuple[tuple[int, int], tuple[int, int], tuple[int, int]]] = (
16
+ AES_CONFIGURATIONS: Final[list[tuple[int, int]]] = [
17
17
  (AES_128_KEY_SIZE, AES_128_ROUND_COUNT),
18
18
  (AES_192_KEY_SIZE, AES_192_ROUND_COUNT),
19
19
  (AES_256_KEY_SIZE, AES_256_ROUND_COUNT),
20
- )
20
+ ]
21
21
 
22
22
 
23
23
  @mark.quick
24
- @mark.parametrize(('key_size', 'round_count'), AES_CONFIGURATIONS)
24
+ @mark.parametrize(['key_size', 'round_count'], AES_CONFIGURATIONS)
25
25
  def test_expand_key_returns_corrent_number_of_keys(
26
26
  key_size: int,
27
27
  round_count: int,
@@ -31,14 +31,14 @@ def test_expand_key_returns_corrent_number_of_keys(
31
31
 
32
32
 
33
33
  @mark.quick
34
- @mark.parametrize(('key_size', 'round_count'), AES_CONFIGURATIONS)
34
+ @mark.parametrize(['key_size', 'round_count'], AES_CONFIGURATIONS)
35
35
  def test_expand_key_returns_16_byte_round_keys(key_size: int, round_count: int) -> None:
36
36
  round_keys: tuple[bytes, ...] = expand_key(bytes(key_size), round_count)
37
37
  assert all(len(round_key) == AES_BLOCK_SIZE for round_key in round_keys)
38
38
 
39
39
 
40
40
  @mark.quick
41
- @mark.parametrize(('key_size', 'round_count'), AES_CONFIGURATIONS)
41
+ @mark.parametrize(['key_size', 'round_count'], AES_CONFIGURATIONS)
42
42
  def test_expanded_schedule_starts_with_original_key(
43
43
  key_size: int,
44
44
  round_count: int,
@@ -29,8 +29,8 @@ def xor_columns(
29
29
 
30
30
  @mark.quick
31
31
  @mark.parametrize(
32
- ('left', 'right', 'expected'),
33
- (
32
+ ['left', 'right', 'expected'],
33
+ [
34
34
  (0x00, 0x00, 0x00),
35
35
  (0x00, 0x01, 0x00),
36
36
  (0x01, 0x00, 0x00),
@@ -45,7 +45,7 @@ def xor_columns(
45
45
  (0xFF, 0xFF, 0x13),
46
46
  (0x80, 0x02, 0x1B),
47
47
  (0x80, 0x80, 0x9A),
48
- ),
48
+ ],
49
49
  )
50
50
  def test_galois256_multiply_known_values(left: int, right: int, expected: int) -> None:
51
51
  assert galois256_multiply(left, right) == expected
@@ -107,8 +107,8 @@ def test_galois256_multiply_right_distributes_over_xor() -> None:
107
107
 
108
108
  @mark.quick
109
109
  @mark.parametrize(
110
- ('values', 'expected'),
111
- (
110
+ ['values', 'expected'],
111
+ [
112
112
  ((), 0x00),
113
113
  ((0x00,), 0x00),
114
114
  ((0x57,), 0x57),
@@ -117,7 +117,7 @@ def test_galois256_multiply_right_distributes_over_xor() -> None:
117
117
  ((0xFF, 0x0F, 0xF0), 0x00),
118
118
  ((0x01, 0x02, 0x04, 0x08), 0x0F),
119
119
  ((0xDE, 0xAD, 0xBE, 0xEF), 0x22),
120
- ),
120
+ ],
121
121
  )
122
122
  def test_xor_bytes_known_values(values: tuple[int, ...], expected: int) -> None:
123
123
  assert xor_bytes(*values) == expected
@@ -17,7 +17,7 @@ class TestAesState:
17
17
  assert state.to_bytes() == block
18
18
 
19
19
  @staticmethod
20
- @mark.parametrize('block', (bytes(), bytes(1), bytes(15), bytes(17), bytes(32)))
20
+ @mark.parametrize('block', [bytes(), bytes(1), bytes(15), bytes(17), bytes(32)])
21
21
  def test_from_bytes_rejects_invalid_block_lengths(block: bytes) -> None:
22
22
  with raises(
23
23
  expected_exception=ValueError,
@@ -50,11 +50,11 @@ class TestCbcMode:
50
50
  key: bytes,
51
51
  data: bytes,
52
52
  ) -> None:
53
- ecb: CbcMode = CbcMode(aes(key[:aes.__key_size__]), padding()) # fmt: skip
54
- assert ecb.decrypt(ecb.encrypt(data)) == data
53
+ cbc: CbcMode = CbcMode(aes(key[:aes.__key_size__]), padding()) # fmt: skip
54
+ assert cbc.decrypt(cbc.encrypt(data)) == data
55
55
 
56
56
  @staticmethod
57
- @mark.parametrize(['aes'], [[Aes128], [Aes192], [Aes256]])
57
+ @mark.parametrize('aes', [Aes128, Aes192, Aes256])
58
58
  @given(key=aes256key, data=binary())
59
59
  def test_decrypt_inverts_encrypt_zero_padding(
60
60
  aes: type[Aes],
@@ -62,16 +62,16 @@ class TestCbcMode:
62
62
  data: bytes,
63
63
  ) -> None:
64
64
  assume(not data.endswith(bytes(1)))
65
- ecb: CbcMode = CbcMode(aes(key[:aes.__key_size__]), ZeroPadding()) # fmt: skip
66
- assert ecb.decrypt(ecb.encrypt(data)) == data
65
+ cbc: CbcMode = CbcMode(aes(key[:aes.__key_size__]), ZeroPadding()) # fmt: skip
66
+ assert cbc.decrypt(cbc.encrypt(data)) == data
67
67
 
68
68
  @staticmethod
69
- @mark.parametrize(['aes'], [[Aes128], [Aes192], [Aes256]])
69
+ @mark.parametrize('aes', [Aes128, Aes192, Aes256])
70
70
  @given(key=aes256key, data=aes_blocks)
71
71
  def test_decrypt_inverts_encrypt_no_padding(
72
72
  aes: type[Aes],
73
73
  key: bytes,
74
74
  data: bytes,
75
75
  ) -> None:
76
- ecb: CbcMode = CbcMode(aes(key[:aes.__key_size__]), NoPadding()) # fmt: skip
77
- assert ecb.decrypt(ecb.encrypt(data)) == data
76
+ cbc: CbcMode = CbcMode(aes(key[:aes.__key_size__]), NoPadding()) # fmt: skip
77
+ assert cbc.decrypt(cbc.encrypt(data)) == data
@@ -54,7 +54,7 @@ class TestEcbMode:
54
54
  assert ecb.decrypt(ecb.encrypt(data)) == data
55
55
 
56
56
  @staticmethod
57
- @mark.parametrize(['aes'], [[Aes128], [Aes192], [Aes256]])
57
+ @mark.parametrize('aes', [Aes128, Aes192, Aes256])
58
58
  @given(key=aes256key, data=binary())
59
59
  def test_decrypt_inverts_encrypt_zero_padding(
60
60
  aes: type[Aes],
@@ -66,7 +66,7 @@ class TestEcbMode:
66
66
  assert ecb.decrypt(ecb.encrypt(data)) == data
67
67
 
68
68
  @staticmethod
69
- @mark.parametrize(['aes'], [[Aes128], [Aes192], [Aes256]])
69
+ @mark.parametrize('aes', [Aes128, Aes192, Aes256])
70
70
  @given(key=aes256key, data=aes_blocks)
71
71
  def test_decrypt_inverts_encrypt_no_padding(
72
72
  aes: type[Aes],
@@ -50,11 +50,11 @@ class TestPcbcMode:
50
50
  key: bytes,
51
51
  data: bytes,
52
52
  ) -> None:
53
- ecb: PcbcMode = PcbcMode(aes(key[:aes.__key_size__]), padding()) # fmt: skip
54
- assert ecb.decrypt(ecb.encrypt(data)) == data
53
+ pcbc: PcbcMode = PcbcMode(aes(key[:aes.__key_size__]), padding()) # fmt: skip
54
+ assert pcbc.decrypt(pcbc.encrypt(data)) == data
55
55
 
56
56
  @staticmethod
57
- @mark.parametrize(['aes'], [[Aes128], [Aes192], [Aes256]])
57
+ @mark.parametrize('aes', [Aes128, Aes192, Aes256])
58
58
  @given(key=aes256key, data=binary())
59
59
  def test_decrypt_inverts_encrypt_zero_padding(
60
60
  aes: type[Aes],
@@ -62,19 +62,19 @@ class TestPcbcMode:
62
62
  data: bytes,
63
63
  ) -> None:
64
64
  assume(not data.endswith(bytes(1)))
65
- ecb: PcbcMode = PcbcMode(
65
+ pcbc: PcbcMode = PcbcMode(
66
66
  algorithm=aes(key[:aes.__key_size__]), # fmt: skip
67
67
  padding=ZeroPadding(),
68
68
  )
69
- assert ecb.decrypt(ecb.encrypt(data)) == data
69
+ assert pcbc.decrypt(pcbc.encrypt(data)) == data
70
70
 
71
71
  @staticmethod
72
- @mark.parametrize(['aes'], [[Aes128], [Aes192], [Aes256]])
72
+ @mark.parametrize('aes', [Aes128, Aes192, Aes256])
73
73
  @given(key=aes256key, data=aes_blocks)
74
74
  def test_decrypt_inverts_encrypt_no_padding(
75
75
  aes: type[Aes],
76
76
  key: bytes,
77
77
  data: bytes,
78
78
  ) -> None:
79
- ecb: PcbcMode = PcbcMode(aes(key[:aes.__key_size__]), NoPadding()) # fmt: skip
80
- assert ecb.decrypt(ecb.encrypt(data)) == data
79
+ pcbc: PcbcMode = PcbcMode(aes(key[:aes.__key_size__]), NoPadding()) # fmt: skip
80
+ assert pcbc.decrypt(pcbc.encrypt(data)) == data
@@ -44,5 +44,5 @@ class TestCfbMode:
44
44
  key: bytes,
45
45
  data: bytes,
46
46
  ) -> None:
47
- ecb: CfbMode = CfbMode(aes(key[:aes.__key_size__]), segment_size) # fmt: skip
48
- assert ecb.decrypt(ecb.encrypt(data)) == data
47
+ cfb: CfbMode = CfbMode(aes(key[:aes.__key_size__]), segment_size) # fmt: skip
48
+ assert cfb.decrypt(cfb.encrypt(data)) == data
@@ -0,0 +1,28 @@
1
+ from hypothesis import given
2
+ from hypothesis.strategies import binary
3
+ from pytest import mark
4
+
5
+ from purepython_aes import (
6
+ Aes,
7
+ Aes128,
8
+ Aes192,
9
+ Aes256,
10
+ CtrMode,
11
+ ReferenceAes128,
12
+ ReferenceAes192,
13
+ ReferenceAes256,
14
+ )
15
+ from tests.unit.aes.strategies import aes256key
16
+
17
+
18
+ @mark.quick
19
+ class TestCtrMode:
20
+ @staticmethod
21
+ @mark.parametrize(
22
+ 'aes',
23
+ [Aes128, Aes192, Aes256, ReferenceAes128, ReferenceAes192, ReferenceAes256],
24
+ )
25
+ @given(key=aes256key, data=binary())
26
+ def test_decrypt_inverts_encrypt(aes: type[Aes], key: bytes, data: bytes) -> None:
27
+ ctr: CtrMode = CtrMode(aes(key[:aes.__key_size__])) # fmt: skip
28
+ assert ctr.decrypt(ctr.encrypt(data)) == data
@@ -0,0 +1,28 @@
1
+ from hypothesis import given
2
+ from hypothesis.strategies import binary
3
+ from pytest import mark
4
+
5
+ from purepython_aes import (
6
+ Aes,
7
+ Aes128,
8
+ Aes192,
9
+ Aes256,
10
+ OfbMode,
11
+ ReferenceAes128,
12
+ ReferenceAes192,
13
+ ReferenceAes256,
14
+ )
15
+ from tests.unit.aes.strategies import aes256key
16
+
17
+
18
+ @mark.quick
19
+ class TestOfbMode:
20
+ @staticmethod
21
+ @mark.parametrize(
22
+ 'aes',
23
+ [Aes128, Aes192, Aes256, ReferenceAes128, ReferenceAes192, ReferenceAes256],
24
+ )
25
+ @given(key=aes256key, data=binary())
26
+ def test_decrypt_inverts_encrypt(aes: type[Aes], key: bytes, data: bytes) -> None:
27
+ ofb: OfbMode = OfbMode(aes(key[:aes.__key_size__])) # fmt: skip
28
+ assert ofb.decrypt(ofb.encrypt(data)) == data
@@ -13,7 +13,7 @@ def test_xor_empty_inputs() -> None:
13
13
 
14
14
  @mark.quick
15
15
  @mark.parametrize(
16
- ('left', 'right', 'expected'),
16
+ ['left', 'right', 'expected'],
17
17
  [
18
18
  (b'\x00', b'\x00', b'\x00'),
19
19
  (b'\x00', b'\xff', b'\xff'),
@@ -45,7 +45,7 @@ def test_xor_known_values(left: bytes, right: bytes, expected: bytes) -> None:
45
45
 
46
46
  @mark.quick
47
47
  @mark.parametrize(
48
- ('left', 'right'),
48
+ ['left', 'right'],
49
49
  [
50
50
  (b'\x00', bytes(0)),
51
51
  (bytes(0), b'\x00'),
@@ -1,4 +0,0 @@
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']
File without changes
File without changes