swarmauri_crypto_pgp 0.3.0.dev5__tar.gz → 0.11.0.dev2__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.
- {swarmauri_crypto_pgp-0.3.0.dev5 → swarmauri_crypto_pgp-0.11.0.dev2}/PKG-INFO +20 -18
- {swarmauri_crypto_pgp-0.3.0.dev5 → swarmauri_crypto_pgp-0.11.0.dev2}/README.md +13 -13
- {swarmauri_crypto_pgp-0.3.0.dev5 → swarmauri_crypto_pgp-0.11.0.dev2}/pyproject.toml +7 -6
- {swarmauri_crypto_pgp-0.3.0.dev5 → swarmauri_crypto_pgp-0.11.0.dev2}/swarmauri_crypto_pgp/PGPCrypto.py +60 -19
- {swarmauri_crypto_pgp-0.3.0.dev5 → swarmauri_crypto_pgp-0.11.0.dev2}/LICENSE +0 -0
- {swarmauri_crypto_pgp-0.3.0.dev5 → swarmauri_crypto_pgp-0.11.0.dev2}/swarmauri_crypto_pgp/__init__.py +0 -0
|
@@ -1,47 +1,47 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarmauri_crypto_pgp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0.dev2
|
|
4
4
|
Summary: OpenPGP (GnuPG) + AES-GCM crypto provider for Swarmauri
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Keywords: swarmauri,sdk,standards,crypto,pgp,cryptography
|
|
8
8
|
Author: Jacob Stewart
|
|
9
9
|
Author-email: jacob@swarmauri.com
|
|
10
|
-
Requires-Python: >=3.10,<3.
|
|
10
|
+
Requires-Python: >=3.10,<3.15
|
|
11
11
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
12
|
Classifier: Natural Language :: English
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
17
13
|
Classifier: Development Status :: 3 - Alpha
|
|
18
14
|
Classifier: Topic :: Security :: Cryptography
|
|
19
15
|
Classifier: Intended Audience :: Developers
|
|
20
16
|
Classifier: Programming Language :: Python
|
|
21
17
|
Classifier: Programming Language :: Python :: 3
|
|
22
18
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
24
|
Requires-Dist: cryptography (>=41)
|
|
24
25
|
Requires-Dist: python-gnupg (>=0.5.0)
|
|
25
26
|
Requires-Dist: swarmauri_base
|
|
26
27
|
Requires-Dist: swarmauri_core
|
|
27
28
|
Description-Content-Type: text/markdown
|
|
28
29
|
|
|
29
|
-

|
|
30
31
|
|
|
31
32
|
<p align="center">
|
|
32
|
-
<a href="https://
|
|
33
|
-
<img src="https://
|
|
33
|
+
<a href="https://pepy.tech/project/swarmauri_crypto_pgp/">
|
|
34
|
+
<img src="https://static.pepy.tech/badge/swarmauri_crypto_pgp/month" alt="PyPI - Downloads"/></a>
|
|
34
35
|
<a href="https://hits.sh/github.com/swarmauri/swarmauri-sdk/tree/master/pkgs/standards/swarmauri_crypto_pgp/">
|
|
35
36
|
<img alt="Hits" src="https://hits.sh/github.com/swarmauri/swarmauri-sdk/tree/master/pkgs/standards/swarmauri_crypto_pgp.svg"/></a>
|
|
36
37
|
<a href="https://pypi.org/project/swarmauri_crypto_pgp/">
|
|
37
|
-
<img src="https://img.shields.io/
|
|
38
|
+
<img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue" alt="PyPI - Python Version"/></a>
|
|
38
39
|
<a href="https://pypi.org/project/swarmauri_crypto_pgp/">
|
|
39
40
|
<img src="https://img.shields.io/pypi/l/swarmauri_crypto_pgp" alt="PyPI - License"/></a>
|
|
40
41
|
<a href="https://pypi.org/project/swarmauri_crypto_pgp/">
|
|
41
42
|
<img src="https://img.shields.io/pypi/v/swarmauri_crypto_pgp?label=swarmauri_crypto_pgp&color=green" alt="PyPI - swarmauri_crypto_pgp"/></a>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
---
|
|
43
|
+
<a href="https://discord.gg/N4UpBuQv8T">
|
|
44
|
+
<img src="https://img.shields.io/badge/Discord-Join%20Chat-5865F2?logo=discord&logoColor=white" alt="Discord"/></a></p>
|
|
45
45
|
|
|
46
46
|
## Swarmauri Crypto PGP
|
|
47
47
|
|
|
@@ -52,18 +52,18 @@ symmetrical encryption, public-key key wrapping, and hybrid envelopes.
|
|
|
52
52
|
|
|
53
53
|
### Features at a glance
|
|
54
54
|
|
|
55
|
-
- **Symmetric AEAD**
|
|
56
|
-
- **Key wrapping**
|
|
55
|
+
- **Symmetric AEAD** ? AES-256-GCM powers `encrypt` and `decrypt`.
|
|
56
|
+
- **Key wrapping** ? `wrap` and `unwrap` delegate to GnuPG to protect random or
|
|
57
57
|
supplied key material with a recipient's public/private key pair.
|
|
58
|
-
- **Hybrid envelopes**
|
|
58
|
+
- **Hybrid envelopes** ? `encrypt_for_many` supports both traditional
|
|
59
59
|
KEM+AEAD (shared ciphertext + wrapped session key) and OpenPGP sealed mode for
|
|
60
60
|
per-recipient ciphertexts.
|
|
61
|
-
- **Sealing convenience**
|
|
61
|
+
- **Sealing convenience** ? `seal` and `unseal` provide single-recipient
|
|
62
62
|
OpenPGP public-key encryption without managing the envelope structure.
|
|
63
63
|
|
|
64
64
|
### System requirements
|
|
65
65
|
|
|
66
|
-
- Python 3.10
|
|
66
|
+
- Python 3.10 ? 3.13.
|
|
67
67
|
- [GnuPG](https://gnupg.org/) available on the `PATH` (required by
|
|
68
68
|
`python-gnupg`).
|
|
69
69
|
|
|
@@ -144,3 +144,5 @@ The provider is registered under the `swarmauri.cryptos` entry-point as
|
|
|
144
144
|
If you want to contribute to swarmauri-sdk, read up on our
|
|
145
145
|
[guidelines for contributing](https://github.com/swarmauri/swarmauri-sdk/blob/master/CONTRIBUTING.md)
|
|
146
146
|
that will help you get started.
|
|
147
|
+
|
|
148
|
+
|
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-

|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<a href="https://
|
|
5
|
-
<img src="https://
|
|
4
|
+
<a href="https://pepy.tech/project/swarmauri_crypto_pgp/">
|
|
5
|
+
<img src="https://static.pepy.tech/badge/swarmauri_crypto_pgp/month" alt="PyPI - Downloads"/></a>
|
|
6
6
|
<a href="https://hits.sh/github.com/swarmauri/swarmauri-sdk/tree/master/pkgs/standards/swarmauri_crypto_pgp/">
|
|
7
7
|
<img alt="Hits" src="https://hits.sh/github.com/swarmauri/swarmauri-sdk/tree/master/pkgs/standards/swarmauri_crypto_pgp.svg"/></a>
|
|
8
8
|
<a href="https://pypi.org/project/swarmauri_crypto_pgp/">
|
|
9
|
-
<img src="https://img.shields.io/
|
|
9
|
+
<img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue" alt="PyPI - Python Version"/></a>
|
|
10
10
|
<a href="https://pypi.org/project/swarmauri_crypto_pgp/">
|
|
11
11
|
<img src="https://img.shields.io/pypi/l/swarmauri_crypto_pgp" alt="PyPI - License"/></a>
|
|
12
12
|
<a href="https://pypi.org/project/swarmauri_crypto_pgp/">
|
|
13
13
|
<img src="https://img.shields.io/pypi/v/swarmauri_crypto_pgp?label=swarmauri_crypto_pgp&color=green" alt="PyPI - swarmauri_crypto_pgp"/></a>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
---
|
|
14
|
+
<a href="https://discord.gg/N4UpBuQv8T">
|
|
15
|
+
<img src="https://img.shields.io/badge/Discord-Join%20Chat-5865F2?logo=discord&logoColor=white" alt="Discord"/></a></p>
|
|
17
16
|
|
|
18
17
|
## Swarmauri Crypto PGP
|
|
19
18
|
|
|
@@ -24,18 +23,18 @@ symmetrical encryption, public-key key wrapping, and hybrid envelopes.
|
|
|
24
23
|
|
|
25
24
|
### Features at a glance
|
|
26
25
|
|
|
27
|
-
- **Symmetric AEAD**
|
|
28
|
-
- **Key wrapping**
|
|
26
|
+
- **Symmetric AEAD** ? AES-256-GCM powers `encrypt` and `decrypt`.
|
|
27
|
+
- **Key wrapping** ? `wrap` and `unwrap` delegate to GnuPG to protect random or
|
|
29
28
|
supplied key material with a recipient's public/private key pair.
|
|
30
|
-
- **Hybrid envelopes**
|
|
29
|
+
- **Hybrid envelopes** ? `encrypt_for_many` supports both traditional
|
|
31
30
|
KEM+AEAD (shared ciphertext + wrapped session key) and OpenPGP sealed mode for
|
|
32
31
|
per-recipient ciphertexts.
|
|
33
|
-
- **Sealing convenience**
|
|
32
|
+
- **Sealing convenience** ? `seal` and `unseal` provide single-recipient
|
|
34
33
|
OpenPGP public-key encryption without managing the envelope structure.
|
|
35
34
|
|
|
36
35
|
### System requirements
|
|
37
36
|
|
|
38
|
-
- Python 3.10
|
|
37
|
+
- Python 3.10 ? 3.13.
|
|
39
38
|
- [GnuPG](https://gnupg.org/) available on the `PATH` (required by
|
|
40
39
|
`python-gnupg`).
|
|
41
40
|
|
|
@@ -115,4 +114,5 @@ The provider is registered under the `swarmauri.cryptos` entry-point as
|
|
|
115
114
|
|
|
116
115
|
If you want to contribute to swarmauri-sdk, read up on our
|
|
117
116
|
[guidelines for contributing](https://github.com/swarmauri/swarmauri-sdk/blob/master/CONTRIBUTING.md)
|
|
118
|
-
that will help you get started.
|
|
117
|
+
that will help you get started.
|
|
118
|
+
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "swarmauri_crypto_pgp"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.11.0.dev2"
|
|
4
4
|
description = "OpenPGP (GnuPG) + AES-GCM crypto provider for Swarmauri"
|
|
5
5
|
license = "Apache-2.0"
|
|
6
6
|
readme = "README.md"
|
|
7
|
-
requires-python = ">=3.10,<3.
|
|
7
|
+
requires-python = ">=3.10,<3.15"
|
|
8
8
|
authors = [{ name = "Jacob Stewart", email = "jacob@swarmauri.com" }]
|
|
9
9
|
classifiers = [
|
|
10
10
|
"License :: OSI Approved :: Apache Software License",
|
|
11
11
|
"Natural Language :: English",
|
|
12
|
-
"Programming Language :: Python :: 3.10",
|
|
13
|
-
"Programming Language :: Python :: 3.11",
|
|
14
|
-
"Programming Language :: Python :: 3.12",
|
|
15
|
-
"Programming Language :: Python :: 3.13",
|
|
16
12
|
"Development Status :: 3 - Alpha",
|
|
17
13
|
"Topic :: Security :: Cryptography",
|
|
18
14
|
"Intended Audience :: Developers",
|
|
19
15
|
"Programming Language :: Python",
|
|
20
16
|
"Programming Language :: Python :: 3",
|
|
21
17
|
"Programming Language :: Python :: 3 :: Only",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
22
23
|
]
|
|
23
24
|
dependencies = [
|
|
24
25
|
"swarmauri_core",
|
|
@@ -4,11 +4,13 @@ Implements the ICrypto contract using:
|
|
|
4
4
|
- AES-256-GCM for symmetric encrypt/decrypt (AEAD)
|
|
5
5
|
- OpenPGP public-key encryption for:
|
|
6
6
|
• wrapping the session key to many recipients (KEM+AEAD mode)
|
|
7
|
-
• sealing/unsealing (direct public-key encryption of plaintext) for one or
|
|
7
|
+
• sealing/unsealing (direct public-key encryption of plaintext) for one or
|
|
8
|
+
many
|
|
8
9
|
|
|
9
10
|
Notes
|
|
10
11
|
-----
|
|
11
|
-
- For wrap/encrypt_for_many/seal, ``KeyRef.public`` must contain an
|
|
12
|
+
- For wrap/encrypt_for_many/seal, ``KeyRef.public`` must contain an
|
|
13
|
+
ASCII-armored
|
|
12
14
|
OpenPGP public key (as produced by ``gpg --armor --export``)
|
|
13
15
|
- For unwrap/unseal, ``KeyRef.material`` must contain an ASCII-armored OpenPGP
|
|
14
16
|
private key (as produced by ``gpg --armor --export-secret-keys``)
|
|
@@ -47,7 +49,8 @@ _SEAL_ALG = "OpenPGP-SEAL"
|
|
|
47
49
|
|
|
48
50
|
@ComponentBase.register_type(CryptoBase, "PGPCrypto")
|
|
49
51
|
class PGPCrypto(CryptoBase):
|
|
50
|
-
"""Concrete implementation of the ICrypto contract using AES-GCM and
|
|
52
|
+
"""Concrete implementation of the ICrypto contract using AES-GCM and
|
|
53
|
+
OpenPGP."""
|
|
51
54
|
|
|
52
55
|
type: Literal["PGPCrypto"] = "PGPCrypto"
|
|
53
56
|
|
|
@@ -92,7 +95,9 @@ class PGPCrypto(CryptoBase):
|
|
|
92
95
|
"KeyRef.material must contain symmetric key bytes for AEAD"
|
|
93
96
|
)
|
|
94
97
|
if len(key.material) not in (16, 24, 32):
|
|
95
|
-
raise ValueError(
|
|
98
|
+
raise ValueError(
|
|
99
|
+
"KeyRef.material must be 16/24/32 bytes for AES-GCM"
|
|
100
|
+
)
|
|
96
101
|
|
|
97
102
|
nonce = nonce or secrets.token_bytes(12)
|
|
98
103
|
aead = AESGCM(key.material)
|
|
@@ -128,7 +133,8 @@ class PGPCrypto(CryptoBase):
|
|
|
128
133
|
return aead.decrypt(ct.nonce, blob, aad or ct.aad)
|
|
129
134
|
|
|
130
135
|
# ─────────────────────────── sealing ───────────────────────────
|
|
131
|
-
# (OpenPGP public-key encryption of plaintext; gpg handles hybrid
|
|
136
|
+
# (OpenPGP public-key encryption of plaintext; gpg handles hybrid
|
|
137
|
+
# internally)
|
|
132
138
|
|
|
133
139
|
async def seal(
|
|
134
140
|
self,
|
|
@@ -152,7 +158,9 @@ class PGPCrypto(CryptoBase):
|
|
|
152
158
|
else recipient.public
|
|
153
159
|
)
|
|
154
160
|
if not import_result.fingerprints:
|
|
155
|
-
raise RuntimeError(
|
|
161
|
+
raise RuntimeError(
|
|
162
|
+
"Failed to import recipient OpenPGP public key"
|
|
163
|
+
)
|
|
156
164
|
fp = import_result.fingerprints[0]
|
|
157
165
|
|
|
158
166
|
enc = gpg.encrypt(pt, fp, armor=False, always_trust=True)
|
|
@@ -171,7 +179,11 @@ class PGPCrypto(CryptoBase):
|
|
|
171
179
|
raise UnsupportedAlgorithm(f"Unsupported seal alg: {alg}")
|
|
172
180
|
if recipient_priv.material is None:
|
|
173
181
|
raise ValueError(
|
|
174
|
-
|
|
182
|
+
(
|
|
183
|
+
"KeyRef.material must contain ASCII-armored OpenPGP "
|
|
184
|
+
"private "
|
|
185
|
+
"key"
|
|
186
|
+
)
|
|
175
187
|
)
|
|
176
188
|
|
|
177
189
|
passphrase = None
|
|
@@ -190,7 +202,9 @@ class PGPCrypto(CryptoBase):
|
|
|
190
202
|
|
|
191
203
|
dec = gpg.decrypt(sealed, passphrase=passphrase or "")
|
|
192
204
|
if not dec.ok:
|
|
193
|
-
raise RuntimeError(
|
|
205
|
+
raise RuntimeError(
|
|
206
|
+
f"GPG decrypt (unseal) failed: {dec.status}"
|
|
207
|
+
)
|
|
194
208
|
return bytes(dec.data)
|
|
195
209
|
|
|
196
210
|
# ─────────── hybrid encrypt-for-many via OpenPGP (KEM+AEAD) ───────────
|
|
@@ -213,10 +227,16 @@ class PGPCrypto(CryptoBase):
|
|
|
213
227
|
for r in recipients:
|
|
214
228
|
if r.public is None:
|
|
215
229
|
raise ValueError(
|
|
216
|
-
|
|
230
|
+
(
|
|
231
|
+
"Recipient KeyRef.public must contain "
|
|
232
|
+
"ASCII-armored OpenPGP "
|
|
233
|
+
"public key"
|
|
234
|
+
)
|
|
217
235
|
)
|
|
218
236
|
import_result = gpg.import_keys(
|
|
219
|
-
r.public.decode()
|
|
237
|
+
r.public.decode()
|
|
238
|
+
if isinstance(r.public, bytes)
|
|
239
|
+
else r.public
|
|
220
240
|
)
|
|
221
241
|
if not import_result.fingerprints:
|
|
222
242
|
raise RuntimeError(
|
|
@@ -250,7 +270,8 @@ class PGPCrypto(CryptoBase):
|
|
|
250
270
|
aad=None, # AAD is not bound in OpenPGP-seal mode
|
|
251
271
|
)
|
|
252
272
|
|
|
253
|
-
# 2) Default KEM+AEAD path (shared AES-GCM ct + PGP-wrapped session
|
|
273
|
+
# 2) Default KEM+AEAD path (shared AES-GCM ct + PGP-wrapped session
|
|
274
|
+
# key)
|
|
254
275
|
enc_alg = self._normalize_aead_alg(enc_alg)
|
|
255
276
|
if enc_alg != _AEAD_DEFAULT:
|
|
256
277
|
raise UnsupportedAlgorithm(f"Unsupported enc_alg: {enc_alg}")
|
|
@@ -270,13 +291,21 @@ class PGPCrypto(CryptoBase):
|
|
|
270
291
|
for r in recipients:
|
|
271
292
|
if r.public is None:
|
|
272
293
|
raise ValueError(
|
|
273
|
-
|
|
294
|
+
(
|
|
295
|
+
"Recipient KeyRef.public must contain "
|
|
296
|
+
"ASCII-armored OpenPGP "
|
|
297
|
+
"public key"
|
|
298
|
+
)
|
|
274
299
|
)
|
|
275
300
|
import_result = gpg.import_keys(
|
|
276
|
-
r.public.decode()
|
|
301
|
+
r.public.decode()
|
|
302
|
+
if isinstance(r.public, bytes)
|
|
303
|
+
else r.public
|
|
277
304
|
)
|
|
278
305
|
if not import_result.fingerprints:
|
|
279
|
-
raise RuntimeError(
|
|
306
|
+
raise RuntimeError(
|
|
307
|
+
"Failed to import recipient OpenPGP public key"
|
|
308
|
+
)
|
|
280
309
|
fp = import_result.fingerprints[0]
|
|
281
310
|
|
|
282
311
|
enc = gpg.encrypt(k, fp, armor=False, always_trust=True)
|
|
@@ -323,14 +352,20 @@ class PGPCrypto(CryptoBase):
|
|
|
323
352
|
with tempfile.TemporaryDirectory() as gpg_home:
|
|
324
353
|
gpg = gnupg.GPG(gnupghome=gpg_home)
|
|
325
354
|
import_result = gpg.import_keys(
|
|
326
|
-
kek.public.decode()
|
|
355
|
+
kek.public.decode()
|
|
356
|
+
if isinstance(kek.public, bytes)
|
|
357
|
+
else kek.public
|
|
327
358
|
)
|
|
328
359
|
if not import_result.fingerprints:
|
|
329
|
-
raise RuntimeError(
|
|
360
|
+
raise RuntimeError(
|
|
361
|
+
"Failed to import recipient OpenPGP public key"
|
|
362
|
+
)
|
|
330
363
|
recipient_fp = import_result.fingerprints[0]
|
|
331
364
|
|
|
332
365
|
dek = dek or secrets.token_bytes(32)
|
|
333
|
-
enc = gpg.encrypt(
|
|
366
|
+
enc = gpg.encrypt(
|
|
367
|
+
dek, recipient_fp, armor=False, always_trust=True
|
|
368
|
+
)
|
|
334
369
|
if not enc.ok:
|
|
335
370
|
raise RuntimeError(f"GPG encrypt failed: {enc.status}")
|
|
336
371
|
|
|
@@ -344,10 +379,16 @@ class PGPCrypto(CryptoBase):
|
|
|
344
379
|
|
|
345
380
|
async def unwrap(self, kek: KeyRef, wrapped: WrappedKey) -> bytes:
|
|
346
381
|
if wrapped.wrap_alg != _WRAP_ALG:
|
|
347
|
-
raise UnsupportedAlgorithm(
|
|
382
|
+
raise UnsupportedAlgorithm(
|
|
383
|
+
f"Unsupported wrap_alg: {wrapped.wrap_alg}"
|
|
384
|
+
)
|
|
348
385
|
if kek.material is None:
|
|
349
386
|
raise ValueError(
|
|
350
|
-
|
|
387
|
+
(
|
|
388
|
+
"KeyRef.material must contain ASCII-armored OpenPGP "
|
|
389
|
+
"private "
|
|
390
|
+
"key"
|
|
391
|
+
)
|
|
351
392
|
)
|
|
352
393
|
|
|
353
394
|
passphrase = None
|
|
File without changes
|
|
File without changes
|