securepayload 1.0.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.
@@ -0,0 +1,124 @@
1
+ """
2
+ SecurePayload — Python AES API payload encryption.
3
+
4
+ Primary usage::
5
+
6
+ import securepayload
7
+
8
+ securepayload.bootstrap()
9
+ ciphertext = securepayload.encrypt({"order_no": "m123"})
10
+ payload = securepayload.decrypt(ciphertext)
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import os
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ from .aes import Aes
20
+ from .encryption_service import EncryptionService
21
+ from .env import find_env_file, load_env
22
+ from .exceptions import (
23
+ DecryptionError,
24
+ EncryptionError,
25
+ InvalidKeyError,
26
+ SecurityEncryptionError,
27
+ )
28
+
29
+ __version__ = "1.0.0"
30
+
31
+ _service: EncryptionService | None = None
32
+
33
+
34
+ def configure(
35
+ key: str | None = None,
36
+ *,
37
+ key_size: int = 128,
38
+ iteration_count: int = 8,
39
+ iv: str = "",
40
+ env_file: Path | str | None = None,
41
+ ) -> None:
42
+ """
43
+ Set the AES key used by ``encrypt`` and ``decrypt``.
44
+
45
+ If ``key`` is omitted, loads ``SECURITY_AES_KEY`` from the environment
46
+ (searching for ``.env`` when needed).
47
+ """
48
+ global _service
49
+
50
+ resolved = key
51
+ if not resolved:
52
+ load_env(env_file)
53
+ resolved = os.environ.get("SECURITY_AES_KEY")
54
+
55
+ if not resolved:
56
+ raise InvalidKeyError(
57
+ "AES key is not configured. Set SECURITY_AES_KEY in .env or call configure(key=...)."
58
+ )
59
+
60
+ _service = EncryptionService(
61
+ resolved,
62
+ key_size=key_size,
63
+ iteration_count=iteration_count,
64
+ iv=iv,
65
+ )
66
+
67
+
68
+ def bootstrap(
69
+ env_file: Path | str | None = None,
70
+ *,
71
+ key: str | None = None,
72
+ key_size: int = 128,
73
+ iteration_count: int = 8,
74
+ iv: str = "",
75
+ ) -> str:
76
+ """
77
+ Load ``.env`` and configure the module in one step.
78
+
79
+ If ``env_file`` is omitted, searches upward from the current working directory
80
+ for a ``.env`` file. Returns the configured key.
81
+ """
82
+ if key is None:
83
+ load_env(env_file)
84
+ key = os.environ.get("SECURITY_AES_KEY")
85
+ if not key:
86
+ raise InvalidKeyError(
87
+ "AES key is not configured. Set SECURITY_AES_KEY in .env or call bootstrap(key=...)."
88
+ )
89
+ configure(key=key, key_size=key_size, iteration_count=iteration_count, iv=iv)
90
+ return key
91
+
92
+
93
+ def _get_service() -> EncryptionService:
94
+ if _service is None:
95
+ configure()
96
+ assert _service is not None
97
+ return _service
98
+
99
+
100
+ def encrypt(data: dict | list | str) -> str:
101
+ """Encrypt a payload to a Base64 AES ciphertext string."""
102
+ return _get_service().encrypt(data)
103
+
104
+
105
+ def decrypt(encrypted_data: Any) -> Any:
106
+ """Decrypt a Base64 ciphertext string or pass through dict/list payloads."""
107
+ return _get_service().decrypt(encrypted_data)
108
+
109
+
110
+ __all__ = [
111
+ "Aes",
112
+ "EncryptionService",
113
+ "SecurityEncryptionError",
114
+ "EncryptionError",
115
+ "DecryptionError",
116
+ "InvalidKeyError",
117
+ "configure",
118
+ "bootstrap",
119
+ "encrypt",
120
+ "decrypt",
121
+ "load_env",
122
+ "find_env_file",
123
+ "__version__",
124
+ ]
securepayload/aes.py ADDED
@@ -0,0 +1,170 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ import binascii
5
+ import copy
6
+ import json
7
+ from typing import Any, Callable, Iterable, Mapping, Sequence
8
+
9
+ from Crypto.Cipher import AES
10
+
11
+ from .exceptions import DecryptionError, EncryptionError, InvalidKeyError, SecurityEncryptionError
12
+
13
+ _JSON_SEPARATORS = (",", ":")
14
+
15
+
16
+ def prepare_key(key: str, key_size: int = 128) -> bytes:
17
+ if not key:
18
+ raise InvalidKeyError("AES key must be a non-empty string.")
19
+
20
+ byte_len = key_size // 8
21
+ raw = key.encode("utf-8")
22
+
23
+ if len(raw) < byte_len:
24
+ return raw.ljust(byte_len, b"\0")
25
+ if len(raw) > byte_len:
26
+ return raw[:byte_len]
27
+ return raw
28
+
29
+
30
+ def _pkcs7_pad(data: bytes, block_size: int = 16) -> bytes:
31
+ pad_len = block_size - (len(data) % block_size)
32
+ return data + bytes([pad_len] * pad_len)
33
+
34
+
35
+ def _pkcs7_unpad(data: bytes, block_size: int = 16) -> bytes:
36
+ if not data:
37
+ raise DecryptionError("Cannot unpad empty ciphertext.")
38
+ pad_len = data[-1]
39
+ if pad_len < 1 or pad_len > block_size:
40
+ raise DecryptionError("Invalid PKCS#7 padding.")
41
+ if data[-pad_len:] != bytes([pad_len] * pad_len):
42
+ raise DecryptionError("Invalid PKCS#7 padding bytes.")
43
+ return data[:-pad_len]
44
+
45
+
46
+ def _to_json(data: Any) -> str:
47
+ return json.dumps(data, separators=_JSON_SEPARATORS, ensure_ascii=False)
48
+
49
+
50
+ class Aes:
51
+ def __init__(
52
+ self,
53
+ key: str,
54
+ *,
55
+ key_size: int = 128,
56
+ iteration_count: int = 8,
57
+ iv: str = "",
58
+ ) -> None:
59
+ # iteration_count and iv are accepted for API compatibility; ECB ignores IV.
60
+ self.key_size = key_size
61
+ self.iteration_count = iteration_count
62
+ self.iv = iv
63
+ self.key = key
64
+ self._key_bytes = prepare_key(key, key_size)
65
+
66
+ def set_new_key(self, iv: str, key: str) -> tuple[str, str]:
67
+ """Update IV and key. Empty IV is allowed (ECB mode does not use IV)."""
68
+ if not key:
69
+ raise InvalidKeyError("Invalid key")
70
+ self.iv = iv
71
+ self.key = key
72
+ self._key_bytes = prepare_key(key, self.key_size)
73
+ return self.iv, self.key
74
+
75
+ def obj_copy(self, obj: Any) -> Any:
76
+ """Deep copy helper used by ``obj_pipe``."""
77
+ return copy.deepcopy(obj)
78
+
79
+ def obj_pipe(
80
+ self,
81
+ obj: Mapping[str, Any],
82
+ mode: int,
83
+ props: Iterable[str],
84
+ ) -> dict[str, Any]:
85
+ """Encrypt (mode > 0) or decrypt (mode <= 0) selected object properties."""
86
+ result = dict(self.obj_copy(obj))
87
+ for prop in props:
88
+ if prop not in obj:
89
+ continue
90
+ result[prop] = self.encrypt(obj[prop]) if mode > 0 else self.decrypt(obj[prop])
91
+ return result
92
+
93
+ def _transform(
94
+ self,
95
+ data: Any,
96
+ *,
97
+ props: Sequence[str] | None,
98
+ pipe_mode: int,
99
+ on_scalar: Callable[[str], str],
100
+ error_cls: type[SecurityEncryptionError],
101
+ action: str,
102
+ ) -> Any:
103
+ if isinstance(data, Mapping):
104
+ if props:
105
+ return self.obj_pipe(data, pipe_mode, props)
106
+ return on_scalar(_to_json(data))
107
+ if isinstance(data, (list, tuple)):
108
+ if props:
109
+ return [self.obj_pipe(item, pipe_mode, props) for item in data]
110
+ return on_scalar(_to_json(data))
111
+ if isinstance(data, str):
112
+ return on_scalar(data)
113
+ if isinstance(data, int):
114
+ return on_scalar(str(data))
115
+ raise error_cls(f"Unsupported {action} input type: {type(data).__name__}")
116
+
117
+ def encrypt(self, data: Any, props: Sequence[str] | None = None) -> Any:
118
+ """Encrypt strings, numbers, dicts, or lists."""
119
+ return self._transform(
120
+ data,
121
+ props=props,
122
+ pipe_mode=1,
123
+ on_scalar=self.do_encrypt,
124
+ error_cls=EncryptionError,
125
+ action="encrypt",
126
+ )
127
+
128
+ def decrypt(self, data: Any, props: Sequence[str] | None = None) -> Any:
129
+ """Decrypt strings, numbers, dicts, or lists."""
130
+ return self._transform(
131
+ data,
132
+ props=props,
133
+ pipe_mode=0,
134
+ on_scalar=self.do_decrypt,
135
+ error_cls=DecryptionError,
136
+ action="decrypt",
137
+ )
138
+
139
+ def do_encrypt(self, plain_text: str) -> str:
140
+ """Encrypt plaintext and return Base64 ciphertext."""
141
+ try:
142
+ cipher = AES.new(self._key_bytes, AES.MODE_ECB)
143
+ padded = _pkcs7_pad(plain_text.encode("utf-8"))
144
+ encrypted = cipher.encrypt(padded)
145
+ return base64.b64encode(encrypted).decode("ascii")
146
+ except SecurityEncryptionError:
147
+ raise
148
+ except (TypeError, ValueError, UnicodeError) as exc:
149
+ raise EncryptionError("Encryption failed.") from exc
150
+
151
+ def do_decrypt(self, cipher_text: str, key: str | None = None, iv: str | None = None) -> str:
152
+ """
153
+ Decrypt Base64 ciphertext.
154
+
155
+ ``key`` and ``iv`` are optional overrides; only ``key`` is applied when
156
+ provided. ECB mode ignores IV.
157
+ """
158
+ _ = iv # ECB mode does not use IV.
159
+ try:
160
+ key_bytes = self._key_bytes if key is None else prepare_key(key, self.key_size)
161
+ raw = base64.b64decode(cipher_text, validate=True)
162
+ if len(raw) % 16 != 0:
163
+ raise DecryptionError("Ciphertext length must be a multiple of the AES block size.")
164
+ cipher = AES.new(key_bytes, AES.MODE_ECB)
165
+ decrypted = cipher.decrypt(raw)
166
+ return _pkcs7_unpad(decrypted).decode("utf-8")
167
+ except SecurityEncryptionError:
168
+ raise
169
+ except (binascii.Error, TypeError, ValueError, UnicodeError) as exc:
170
+ raise DecryptionError("Decryption failed.") from exc
@@ -0,0 +1,59 @@
1
+ """High-level encryption service for API payloads."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any
7
+
8
+ from .aes import Aes
9
+
10
+
11
+ class EncryptionService:
12
+ """
13
+ Encrypt and decrypt API payloads using AES-128-ECB.
14
+
15
+ Provide your own AES key (16 characters for AES-128) to encrypt/decrypt
16
+ payloads exchanged with APIs.
17
+ """
18
+
19
+ def __init__(
20
+ self,
21
+ key: str,
22
+ *,
23
+ key_size: int = 128,
24
+ iteration_count: int = 8,
25
+ iv: str = "",
26
+ ) -> None:
27
+ self._aes = Aes(
28
+ key,
29
+ key_size=key_size,
30
+ iteration_count=iteration_count,
31
+ iv=iv,
32
+ )
33
+
34
+ def decrypt(self, encrypted_data: Any) -> Any:
35
+ """
36
+ Decrypt AES payload.
37
+
38
+ - dict/list: returned unchanged (already decoded)
39
+ - str: decrypted and JSON-decoded when possible; raw string otherwise
40
+ - other: ``None``
41
+ """
42
+ if isinstance(encrypted_data, (dict, list)):
43
+ return encrypted_data
44
+
45
+ if isinstance(encrypted_data, str):
46
+ plaintext = self._aes.decrypt(encrypted_data)
47
+ try:
48
+ return json.loads(plaintext)
49
+ except json.JSONDecodeError:
50
+ return plaintext
51
+
52
+ return None
53
+
54
+ def encrypt(self, data: dict | list | str) -> str:
55
+ """Encrypt data to a Base64 AES string."""
56
+ return self._aes.encrypt(data)
57
+
58
+ Decrypt = decrypt
59
+ Encrypt = encrypt
securepayload/env.py ADDED
@@ -0,0 +1,56 @@
1
+ """Load environment variables from a .env file."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+
8
+
9
+ def find_env_file(start: Path | None = None) -> Path | None:
10
+ """Walk upward from ``start`` (default: cwd) looking for a ``.env`` file."""
11
+ current = (start or Path.cwd()).resolve()
12
+ for directory in (current, *current.parents):
13
+ candidate = directory / ".env"
14
+ if candidate.is_file():
15
+ return candidate
16
+ return None
17
+
18
+
19
+ def load_env(env_file: Path | str | None = None) -> bool:
20
+ """
21
+ Load variables from a ``.env`` file into ``os.environ``.
22
+
23
+ Uses python-dotenv when installed; otherwise parses the file directly.
24
+ When ``env_file`` is omitted, searches upward from the current working directory.
25
+ Returns ``True`` if a file was found and loaded.
26
+ """
27
+ if env_file is not None:
28
+ path = Path(env_file)
29
+ else:
30
+ found = find_env_file()
31
+ if found is None:
32
+ return False
33
+ path = found
34
+
35
+ if not path.is_file():
36
+ return False
37
+
38
+ try:
39
+ from dotenv import load_dotenv
40
+
41
+ load_dotenv(path)
42
+ return True
43
+ except ImportError:
44
+ pass
45
+
46
+ for line in path.read_text(encoding="utf-8").splitlines():
47
+ stripped = line.strip()
48
+ if not stripped or stripped.startswith("#") or "=" not in stripped:
49
+ continue
50
+ key, _, value = stripped.partition("=")
51
+ key = key.strip()
52
+ value = value.strip().strip('"').strip("'")
53
+ if key:
54
+ os.environ.setdefault(key, value)
55
+
56
+ return True
@@ -0,0 +1,14 @@
1
+ class SecurityEncryptionError(Exception):
2
+ """Base exception for encryption package errors."""
3
+
4
+
5
+ class InvalidKeyError(SecurityEncryptionError):
6
+ """Raised when the AES key is missing or invalid."""
7
+
8
+
9
+ class EncryptionError(SecurityEncryptionError):
10
+ """Raised when encryption fails."""
11
+
12
+
13
+ class DecryptionError(SecurityEncryptionError):
14
+ """Raised when decryption fails."""
@@ -0,0 +1,9 @@
1
+ """Shared known ciphertext vectors for tests and examples."""
2
+
3
+ KNOWN_STRING_PLAIN = {"test": "hello"}
4
+ KNOWN_STRING_CIPHER = "4f58KzglCzu10lH/7VxEy+tBHZ/TaMAkHQSH/SnDBEI="
5
+
6
+ KNOWN_ORDER_PLAIN = {"order_no": "m123"}
7
+ KNOWN_ORDER_CIPHER = "3zBcPh7pTI8VHlNt6MdfQxHTv+BOkN5Gg5gmBxzQ07g="
8
+
9
+ SAMPLE_KEY = "abcdefghijklmnop"
@@ -0,0 +1,309 @@
1
+ Metadata-Version: 2.4
2
+ Name: securepayload
3
+ Version: 1.0.0
4
+ Summary: SecurePayload — Python AES encryption library for API payloads. Encrypt and decrypt with securepayload.encrypt() and securepayload.decrypt().
5
+ Author-email: zfhassaan <zfhassaan@gmail.com>
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://github.com/zfhassaan/securepayload
8
+ Project-URL: Documentation, https://github.com/zfhassaan/securepayload#readme
9
+ Project-URL: Source, https://github.com/zfhassaan/securepayload
10
+ Project-URL: Issues, https://github.com/zfhassaan/securepayload/issues
11
+ Keywords: aes,aes-encryption,api-payload,cryptography,encryption,payload-encryption,python-security,secure-payload,webhook-encryption
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Security :: Cryptography
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: pycryptodome>=3.20.0
24
+ Requires-Dist: python-dotenv>=1.0.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
27
+
28
+ <p align="center">
29
+ <img src="assets/banner.png" alt="SecurePayload — Python AES API Payload Encryption Library" width="100%" />
30
+ </p>
31
+
32
+ <p align="center">
33
+ <img src="assets/logo.png" alt="SecurePayload logo" width="50" />
34
+ </p>
35
+
36
+ <h1 align="center">SecurePayload</h1>
37
+
38
+ <p align="center">
39
+ <strong>Python AES encryption library for secure API payload handling</strong>
40
+ </p>
41
+
42
+ <p align="center">
43
+ Encrypt and decrypt JSON API payloads, webhook bodies, and request data with a simple two-call API —
44
+ <code>securepayload.encrypt()</code> and <code>securepayload.decrypt()</code>
45
+ </p>
46
+
47
+ <p align="center">
48
+ <a href="#installation">Installation</a> •
49
+ <a href="#quick-start">Quick Start</a> •
50
+ <a href="#usage">Usage</a> •
51
+ <a href="#api-reference">API Reference</a> •
52
+ <a href="#testing">Testing</a>
53
+ </p>
54
+
55
+ <p align="center">
56
+ <img src="https://img.shields.io/badge/python-3.9+-3776AB?style=flat-square&logo=python&logoColor=white" alt="Python 3.9+" />
57
+ <img src="https://img.shields.io/badge/AES--128--ECB-14b8a6?style=flat-square" alt="AES-128-ECB" />
58
+ <img src="https://img.shields.io/badge/license-Proprietary-64748b?style=flat-square" alt="License" />
59
+ </p>
60
+
61
+ ---
62
+
63
+ ## Overview
64
+
65
+ **SecurePayload** is a lightweight Python cryptography library built for developers who need to **encrypt API request bodies** and **decrypt encrypted webhook or HTTP payloads**. It uses AES-128-ECB with PKCS#7 padding and Base64 output — compatible with existing encrypted API integrations.
66
+
67
+ Ideal for:
68
+
69
+ - Python microservices sending encrypted API requests
70
+ - Webhook receivers decrypting incoming payloads
71
+ - Background workers and ETL pipelines handling secure JSON data
72
+ - Integration scripts bridging encrypted API endpoints
73
+
74
+ ```python
75
+ import securepayload
76
+
77
+ securepayload.bootstrap()
78
+
79
+ encrypted = securepayload.encrypt({"order_no": "m123", "channel": "CARD"})
80
+ decrypted = securepayload.decrypt(encrypted)
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Features
86
+
87
+ | Feature | Description |
88
+ |---------|-------------|
89
+ | **Simple API** | `securepayload.encrypt()` and `securepayload.decrypt()` — minimal boilerplate |
90
+ | **API payload ready** | JSON dicts and lists encrypted automatically |
91
+ | **Environment-based keys** | Configure via `securepayload.bootstrap()` or `configure(key=...)` |
92
+ | **Selective field encryption** | `Aes.obj_pipe()` for encrypting individual record fields |
93
+ | **Typed exceptions** | `InvalidKeyError`, `EncryptionError`, `DecryptionError` |
94
+
95
+ ---
96
+
97
+ ## Requirements
98
+
99
+ - Python **3.9+**
100
+ - [PyCryptodome](https://pycryptodome.readthedocs.io/)
101
+ - [python-dotenv](https://github.com/theskumar/python-dotenv)
102
+
103
+ ---
104
+
105
+ ## Installation
106
+
107
+ ### From PyPI
108
+
109
+ ```bash
110
+ pip install securepayload
111
+ ```
112
+
113
+ ### From source (development)
114
+
115
+ ```bash
116
+ git clone https://github.com/zfhassaan/securepayload.git
117
+ cd securepayload
118
+ python -m venv env
119
+
120
+ # Windows
121
+ env\Scripts\activate
122
+
123
+ # macOS / Linux
124
+ source env/bin/activate
125
+
126
+ pip install -e ".[dev]"
127
+ ```
128
+
129
+ ### Configure your AES key
130
+
131
+ Create a `.env` file (or set the variable in your environment):
132
+
133
+ ```env
134
+ SECURITY_AES_KEY=your-16-char-key
135
+ ```
136
+
137
+ > **Security:** Never commit production keys. Keep `.env` out of version control.
138
+
139
+ ---
140
+
141
+ ## Quick start
142
+
143
+ ```python
144
+ import securepayload
145
+
146
+ securepayload.bootstrap() # loads .env and configures the key
147
+
148
+ payload = {"order_no": "26168785012837", "channel": "CARD"}
149
+
150
+ encrypted = securepayload.encrypt(payload)
151
+ decrypted = securepayload.decrypt(encrypted)
152
+
153
+ print(encrypted) # Base64 ciphertext
154
+ print(decrypted) # Original dict
155
+ ```
156
+
157
+ ```bash
158
+ python examples/basic_usage.py
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Usage
164
+
165
+ ### Encrypt and decrypt API payloads
166
+
167
+ ```python
168
+ import securepayload
169
+
170
+ # Load key from .env (searches upward from cwd)
171
+ securepayload.bootstrap()
172
+
173
+ # Or pass key explicitly
174
+ securepayload.configure(key="your-16-char-key")
175
+
176
+ # Encrypt JSON payload → Base64 string
177
+ ciphertext = securepayload.encrypt({"order_no": "m123", "channel": "CARD"})
178
+
179
+ # Decrypt → dict (auto JSON-parsed)
180
+ data = securepayload.decrypt(ciphertext)
181
+ ```
182
+
183
+ | Input to `decrypt()` | Result |
184
+ |----------------------|--------|
185
+ | Base64 `str` | Decrypted; JSON-parsed when valid JSON |
186
+ | `dict` / `list` | Returned unchanged |
187
+ | Other | `None` |
188
+
189
+ ### HTTP integration
190
+
191
+ ```python
192
+ import os
193
+ import requests
194
+ import securepayload
195
+
196
+ securepayload.configure(key=os.environ["SECURITY_AES_KEY"])
197
+
198
+ body = securepayload.encrypt({"event": "order.updated", "order_no": "m123"})
199
+ requests.post("https://api.example.com/webhook", data=body)
200
+ ```
201
+
202
+ ### Error handling
203
+
204
+ ```python
205
+ import securepayload
206
+ from securepayload.exceptions import DecryptionError, InvalidKeyError
207
+
208
+ try:
209
+ securepayload.configure(key="")
210
+ except InvalidKeyError:
211
+ ...
212
+
213
+ try:
214
+ securepayload.decrypt("invalid-ciphertext")
215
+ except DecryptionError:
216
+ ...
217
+ ```
218
+
219
+ ### Advanced: selective field encryption
220
+
221
+ ```python
222
+ from securepayload import Aes
223
+
224
+ aes = Aes(key="your-16-char-key")
225
+ record = {"name": "public", "token": "secret-value"}
226
+ sealed = aes.obj_pipe(record, mode=1, props=["token"])
227
+ ```
228
+
229
+ ---
230
+
231
+ ## Examples
232
+
233
+ | Script | Description |
234
+ |--------|-------------|
235
+ | `examples/basic_usage.py` | Encrypt/decrypt demo |
236
+ | `examples/vector_test.py` | Known ciphertext vector validation |
237
+ | `examples/run_tests.py` | Runs the pytest suite |
238
+
239
+ ```bash
240
+ python examples/basic_usage.py
241
+ python examples/vector_test.py
242
+ ```
243
+
244
+ ---
245
+
246
+ ## Testing
247
+
248
+ ```bash
249
+ pip install -e ".[dev]"
250
+ pytest -v
251
+ ```
252
+
253
+ ---
254
+
255
+ ## Cryptographic specification
256
+
257
+ | Setting | Value |
258
+ |---------|-------|
259
+ | Algorithm | AES-128-ECB |
260
+ | Padding | PKCS#7 |
261
+ | Output | Base64 |
262
+ | Key | 16-byte UTF-8 string (padded/truncated) |
263
+
264
+ ### Known test vectors
265
+
266
+ | Plaintext | Ciphertext |
267
+ |-----------|------------|
268
+ | `{"test":"hello"}` | `4f58KzglCzu10lH/7VxEy+tBHZ/TaMAkHQSH/SnDBEI=` |
269
+ | `{"order_no":"m123"}` | `3zBcPh7pTI8VHlNt6MdfQxHTv+BOkN5Gg5gmBxzQ07g=` |
270
+
271
+ ---
272
+
273
+ ## Project structure
274
+
275
+ ```
276
+ ├── assets/
277
+ │ ├── banner.png # README banner
278
+ │ └── logo.png # Project logo
279
+ ├── securepayload/ # Main package
280
+ │ ├── __init__.py # securepayload.encrypt / decrypt
281
+ │ ├── aes.py
282
+ │ ├── encryption_service.py
283
+ │ └── exceptions.py
284
+ ├── examples/
285
+ ├── tests/
286
+ └── docs/
287
+ └── API.md
288
+ ```
289
+
290
+ ---
291
+
292
+ ## Security considerations
293
+
294
+ - **ECB mode** is retained for compatibility with existing encrypted API systems.
295
+ - Use `.env` locally and a secrets manager in production.
296
+ - Rotate keys through your deployment pipeline.
297
+
298
+ ---
299
+
300
+ ## Documentation
301
+
302
+ - [API reference](docs/API.md)
303
+ - Repository: [github.com/zfhassaan/securepayload](https://github.com/zfhassaan/securepayload)
304
+
305
+ ---
306
+
307
+ ## License
308
+
309
+ Proprietary — internal tooling. Use according to your organization's policies.
@@ -0,0 +1,10 @@
1
+ securepayload/__init__.py,sha256=OeB0UOLhmy9KR7GK48oufMs8b7yXL_pe9YXaSr7VZfo,2918
2
+ securepayload/aes.py,sha256=hJha9JxEfauiFUTjDvp5CxM23z6WiRzcb9OyGM5eHPc,5788
3
+ securepayload/encryption_service.py,sha256=9lJG39UXZocGwMqwnWfYKKbhJfR0coPk5OAl3FIjlqo,1473
4
+ securepayload/env.py,sha256=Qwt9TWjmov696f7I4VpDd5Eb8Y8ngI3NNoQlYPosb8U,1584
5
+ securepayload/exceptions.py,sha256=ASrQzgzEClFwDerzJfx8hSoGau3giY2ZG4te_8XEWkY,385
6
+ securepayload/vectors.py,sha256=Zg_w50lcWHjCgB5XgzBOBL2aFS52TMBVZT5fV1QTyP8,314
7
+ securepayload-1.0.0.dist-info/METADATA,sha256=wl6QgBPZZqiTrq_n9WR4UMt8iAibg5Cnr4kKuTC0XOE,7996
8
+ securepayload-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
9
+ securepayload-1.0.0.dist-info/top_level.txt,sha256=6q4BgFB9aqOpCkzymWB7qY-mdEPEr51VF2xuX2suCE8,14
10
+ securepayload-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ securepayload