wolfronix-sdk 1.0.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.
- wolfronix_sdk-1.0.0/LICENSE +21 -0
- wolfronix_sdk-1.0.0/PKG-INFO +347 -0
- wolfronix_sdk-1.0.0/README.md +310 -0
- wolfronix_sdk-1.0.0/pyproject.toml +64 -0
- wolfronix_sdk-1.0.0/setup.cfg +4 -0
- wolfronix_sdk-1.0.0/tests/test_client.py +251 -0
- wolfronix_sdk-1.0.0/tests/test_crypto.py +238 -0
- wolfronix_sdk-1.0.0/wolfronix/__init__.py +109 -0
- wolfronix_sdk-1.0.0/wolfronix/admin.py +219 -0
- wolfronix_sdk-1.0.0/wolfronix/client.py +960 -0
- wolfronix_sdk-1.0.0/wolfronix/crypto.py +292 -0
- wolfronix_sdk-1.0.0/wolfronix/errors.py +58 -0
- wolfronix_sdk-1.0.0/wolfronix/types.py +251 -0
- wolfronix_sdk-1.0.0/wolfronix_sdk.egg-info/PKG-INFO +347 -0
- wolfronix_sdk-1.0.0/wolfronix_sdk.egg-info/SOURCES.txt +16 -0
- wolfronix_sdk-1.0.0/wolfronix_sdk.egg-info/dependency_links.txt +1 -0
- wolfronix_sdk-1.0.0/wolfronix_sdk.egg-info/requires.txt +11 -0
- wolfronix_sdk-1.0.0/wolfronix_sdk.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Wolfronix Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wolfronix-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Wolfronix SDK for Python — Zero-knowledge encryption made simple
|
|
5
|
+
Author: Wolfronix Team
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://wolfronix.com
|
|
8
|
+
Project-URL: Documentation, https://wolfronix.com/docs/sdk/python
|
|
9
|
+
Project-URL: Repository, https://github.com/wolfronix/sdk-python
|
|
10
|
+
Project-URL: Issues, https://github.com/wolfronix/sdk-python/issues
|
|
11
|
+
Keywords: wolfronix,encryption,security,zero-knowledge,aes-256,file-encryption,crypto,privacy
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Security :: Cryptography
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: httpx>=0.25.0
|
|
28
|
+
Requires-Dist: cryptography>=41.0.0
|
|
29
|
+
Provides-Extra: websocket
|
|
30
|
+
Requires-Dist: websockets>=12.0; extra == "websocket"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
34
|
+
Requires-Dist: respx>=0.20; extra == "dev"
|
|
35
|
+
Requires-Dist: websockets>=12.0; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# Wolfronix SDK for Python
|
|
39
|
+
|
|
40
|
+
> Official Python SDK for [Wolfronix](https://wolfronix.com) — Zero-knowledge encryption made simple.
|
|
41
|
+
|
|
42
|
+
[](https://www.python.org/downloads/)
|
|
43
|
+
[](LICENSE)
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install wolfronix-sdk
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For WebSocket streaming support:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install wolfronix-sdk[websocket]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Quick Start
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import asyncio
|
|
61
|
+
from wolfronix import Wolfronix, WolfronixConfig
|
|
62
|
+
|
|
63
|
+
async def main():
|
|
64
|
+
# Initialize client
|
|
65
|
+
wfx = Wolfronix(WolfronixConfig(
|
|
66
|
+
base_url="https://your-wolfronix-server:9443",
|
|
67
|
+
client_id="your_client_id",
|
|
68
|
+
wolfronix_key="your_wolfronix_key",
|
|
69
|
+
insecure=True, # For self-signed certs
|
|
70
|
+
))
|
|
71
|
+
|
|
72
|
+
# Health check
|
|
73
|
+
healthy = await wfx.health_check()
|
|
74
|
+
print(f"Server healthy: {healthy}")
|
|
75
|
+
|
|
76
|
+
# Register a new user
|
|
77
|
+
result = await wfx.register("user@example.com", "secure_password")
|
|
78
|
+
print(f"Registered: {result.user_id}")
|
|
79
|
+
|
|
80
|
+
# Or login with existing credentials
|
|
81
|
+
result = await wfx.login("user@example.com", "secure_password")
|
|
82
|
+
print(f"Logged in: {result.user_id}")
|
|
83
|
+
|
|
84
|
+
# Encrypt a file
|
|
85
|
+
with open("document.pdf", "rb") as f:
|
|
86
|
+
enc = await wfx.encrypt(f.read(), filename="document.pdf")
|
|
87
|
+
print(f"Encrypted! File ID: {enc.file_id}")
|
|
88
|
+
|
|
89
|
+
# Decrypt a file
|
|
90
|
+
decrypted_bytes = await wfx.decrypt(enc.file_id)
|
|
91
|
+
with open("decrypted.pdf", "wb") as f:
|
|
92
|
+
f.write(decrypted_bytes)
|
|
93
|
+
|
|
94
|
+
# List files
|
|
95
|
+
files = await wfx.list_files()
|
|
96
|
+
for f in files.files:
|
|
97
|
+
print(f" {f.original_name} (ID: {f.file_id})")
|
|
98
|
+
|
|
99
|
+
asyncio.run(main())
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Features
|
|
103
|
+
|
|
104
|
+
### Authentication (Zero-Knowledge)
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# Register — generates RSA-2048 keys client-side, wraps private key with password
|
|
108
|
+
result = await wfx.register("user@example.com", "password123")
|
|
109
|
+
|
|
110
|
+
# Login — fetches wrapped key, unwraps client-side (server never sees raw private key)
|
|
111
|
+
result = await wfx.login("user@example.com", "password123")
|
|
112
|
+
|
|
113
|
+
# Direct token auth (for server-side apps)
|
|
114
|
+
wfx.set_token("jwt-token", "user-id")
|
|
115
|
+
|
|
116
|
+
# Logout (clears keys from memory)
|
|
117
|
+
wfx.logout()
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### File Encryption (4-Layer Architecture)
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
# Encrypt
|
|
124
|
+
with open("secret.pdf", "rb") as f:
|
|
125
|
+
result = await wfx.encrypt(f.read(), filename="secret.pdf")
|
|
126
|
+
print(f"File ID: {result.file_id}, Time: {result.enc_time_ms}ms")
|
|
127
|
+
|
|
128
|
+
# Decrypt (zero-knowledge flow — private key never leaves client)
|
|
129
|
+
data = await wfx.decrypt(result.file_id)
|
|
130
|
+
with open("decrypted.pdf", "wb") as f:
|
|
131
|
+
f.write(data)
|
|
132
|
+
|
|
133
|
+
# List all files
|
|
134
|
+
files = await wfx.list_files()
|
|
135
|
+
|
|
136
|
+
# Delete a file
|
|
137
|
+
await wfx.delete_file(result.file_id)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### E2E Chat Encryption (RSA + AES Hybrid)
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
# Encrypt a message for a recipient
|
|
144
|
+
packet = await wfx.encrypt_message("Hello, Bob!", "bob@example.com")
|
|
145
|
+
# Send `packet` (JSON string) via your chat transport
|
|
146
|
+
|
|
147
|
+
# Decrypt a received message
|
|
148
|
+
plaintext = await wfx.decrypt_message(packet)
|
|
149
|
+
print(plaintext) # "Hello, Bob!"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Server-Side Message Encryption (Dual-Key Split)
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
# Layer 4 (default) — server splits key, you get key_part_a
|
|
156
|
+
result = await wfx.server_encrypt("Sensitive message")
|
|
157
|
+
|
|
158
|
+
# Decrypt with your key_part_a
|
|
159
|
+
from wolfronix import ServerDecryptParams
|
|
160
|
+
text = await wfx.server_decrypt(ServerDecryptParams(
|
|
161
|
+
encrypted_message=result.encrypted_message,
|
|
162
|
+
nonce=result.nonce,
|
|
163
|
+
key_part_a=result.key_part_a,
|
|
164
|
+
message_tag=result.message_tag,
|
|
165
|
+
))
|
|
166
|
+
|
|
167
|
+
# Batch encryption (up to 100 messages, single round-trip)
|
|
168
|
+
batch = await wfx.server_encrypt_batch([
|
|
169
|
+
{"id": "msg1", "message": "Hello"},
|
|
170
|
+
{"id": "msg2", "message": "World"},
|
|
171
|
+
])
|
|
172
|
+
|
|
173
|
+
# Decrypt batch items
|
|
174
|
+
text1 = await wfx.server_decrypt_batch_item(batch, 0)
|
|
175
|
+
text2 = await wfx.server_decrypt_batch_item(batch, 1)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### WebSocket Streaming Encryption
|
|
179
|
+
|
|
180
|
+
> Requires `pip install wolfronix-sdk[websocket]`
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
# Encrypt stream
|
|
184
|
+
stream = await wfx.create_stream("encrypt")
|
|
185
|
+
enc1 = await stream.send("Chunk 1 data")
|
|
186
|
+
enc2 = await stream.send("Chunk 2 data")
|
|
187
|
+
summary = await stream.end()
|
|
188
|
+
print(f"Processed {summary['chunks_processed']} chunks")
|
|
189
|
+
|
|
190
|
+
# Save stream.key_part_a and stream.stream_tag for decryption
|
|
191
|
+
|
|
192
|
+
# Decrypt stream
|
|
193
|
+
dec_stream = await wfx.create_stream("decrypt", {
|
|
194
|
+
"key_part_a": stream.key_part_a,
|
|
195
|
+
"stream_tag": stream.stream_tag,
|
|
196
|
+
})
|
|
197
|
+
plain1 = await dec_stream.send(enc1)
|
|
198
|
+
plain2 = await dec_stream.send(enc2)
|
|
199
|
+
await dec_stream.end()
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Enterprise Admin Client
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
from wolfronix import WolfronixAdmin, WolfronixAdminConfig
|
|
206
|
+
|
|
207
|
+
admin = WolfronixAdmin(WolfronixAdminConfig(
|
|
208
|
+
base_url="https://wolfronix-server:9443",
|
|
209
|
+
admin_key="your-admin-api-key",
|
|
210
|
+
insecure=True,
|
|
211
|
+
))
|
|
212
|
+
|
|
213
|
+
# Register a client with managed Supabase connector
|
|
214
|
+
result = await admin.register_client({
|
|
215
|
+
"client_id": "acme_corp",
|
|
216
|
+
"client_name": "Acme Corporation",
|
|
217
|
+
"db_type": "supabase",
|
|
218
|
+
"db_config": '{"supabase_url": "https://xxx.supabase.co", "supabase_service_key": "eyJ..."}',
|
|
219
|
+
})
|
|
220
|
+
print(f"Wolfronix key: {result.wolfronix_key}")
|
|
221
|
+
|
|
222
|
+
# Register with custom API
|
|
223
|
+
result = await admin.register_client({
|
|
224
|
+
"client_id": "my_app",
|
|
225
|
+
"client_name": "My Application",
|
|
226
|
+
"db_type": "custom_api",
|
|
227
|
+
"api_endpoint": "https://my-storage-api.com",
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
# List all clients
|
|
231
|
+
clients = await admin.list_clients()
|
|
232
|
+
for c in clients.clients:
|
|
233
|
+
print(f" {c.client_id} ({c.db_type}) — active: {c.is_active}")
|
|
234
|
+
|
|
235
|
+
# Get / update / deactivate
|
|
236
|
+
client = await admin.get_client("acme_corp")
|
|
237
|
+
await admin.update_client("acme_corp", {"db_config": '{"new": "config"}'})
|
|
238
|
+
await admin.deactivate_client("acme_corp")
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Metrics
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
metrics = await wfx.get_metrics()
|
|
245
|
+
print(f"Total encryptions: {metrics.total_encryptions}")
|
|
246
|
+
print(f"Total decryptions: {metrics.total_decryptions}")
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Crypto Utilities
|
|
250
|
+
|
|
251
|
+
You can use the low-level crypto functions directly:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from wolfronix.crypto import (
|
|
255
|
+
generate_key_pair,
|
|
256
|
+
export_key_to_pem,
|
|
257
|
+
import_key_from_pem,
|
|
258
|
+
wrap_private_key,
|
|
259
|
+
unwrap_private_key,
|
|
260
|
+
generate_session_key,
|
|
261
|
+
encrypt_data,
|
|
262
|
+
decrypt_data,
|
|
263
|
+
rsa_encrypt,
|
|
264
|
+
rsa_decrypt,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Generate RSA-2048 key pair
|
|
268
|
+
keys = generate_key_pair()
|
|
269
|
+
pub_pem = export_key_to_pem(keys.public_key, "public")
|
|
270
|
+
priv_pem = export_key_to_pem(keys.private_key, "private")
|
|
271
|
+
|
|
272
|
+
# Wrap private key with password (PBKDF2 + AES-256-GCM)
|
|
273
|
+
wrapped = wrap_private_key(keys.private_key, "my_password")
|
|
274
|
+
# wrapped.encrypted_key (base64), wrapped.salt (hex)
|
|
275
|
+
|
|
276
|
+
# Unwrap private key
|
|
277
|
+
private_key = unwrap_private_key(wrapped.encrypted_key, "my_password", wrapped.salt)
|
|
278
|
+
|
|
279
|
+
# AES-256-GCM encryption
|
|
280
|
+
session_key = generate_session_key()
|
|
281
|
+
encrypted, iv = encrypt_data("Hello, World!", session_key)
|
|
282
|
+
plaintext = decrypt_data(encrypted, iv, session_key)
|
|
283
|
+
|
|
284
|
+
# RSA-OAEP encryption
|
|
285
|
+
ciphertext = rsa_encrypt(b"secret data", keys.public_key)
|
|
286
|
+
plaintext_bytes = rsa_decrypt(ciphertext, keys.private_key)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Security Architecture
|
|
290
|
+
|
|
291
|
+
| Layer | Protection | Implementation |
|
|
292
|
+
|-------|-----------|----------------|
|
|
293
|
+
| Layer 1 | Fake data generation | Synthetic PAN, Aadhaar, SSN for dev/test |
|
|
294
|
+
| Layer 2 | RBAC masking | Role-based partial/full data masking |
|
|
295
|
+
| Layer 3 | AES-256-GCM encryption | Server-side symmetric encryption |
|
|
296
|
+
| Layer 4 | Dual-key RSA split | RSA-2048 OAEP — key split between client & server |
|
|
297
|
+
|
|
298
|
+
**Zero-Knowledge Design:** Your private key is never stored on or transmitted to the server. It's wrapped with your password client-side using PBKDF2 (100,000 iterations) + AES-256-GCM before upload.
|
|
299
|
+
|
|
300
|
+
## Error Handling
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
from wolfronix.errors import (
|
|
304
|
+
WolfronixError,
|
|
305
|
+
AuthenticationError,
|
|
306
|
+
ValidationError,
|
|
307
|
+
NetworkError,
|
|
308
|
+
PermissionDeniedError,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
try:
|
|
312
|
+
await wfx.login("user@example.com", "wrong_password")
|
|
313
|
+
except AuthenticationError as e:
|
|
314
|
+
print(f"Auth failed: {e}")
|
|
315
|
+
except NetworkError as e:
|
|
316
|
+
print(f"Network error: {e}")
|
|
317
|
+
except WolfronixError as e:
|
|
318
|
+
print(f"Error [{e.code}]: {e} (HTTP {e.status_code})")
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## API Parity with JavaScript SDK
|
|
322
|
+
|
|
323
|
+
This Python SDK mirrors the [JavaScript/TypeScript SDK](../javascript/) 1:1:
|
|
324
|
+
|
|
325
|
+
| JavaScript | Python |
|
|
326
|
+
|-----------|--------|
|
|
327
|
+
| `new Wolfronix(config)` | `Wolfronix(WolfronixConfig(...))` |
|
|
328
|
+
| `wfx.register(email, pass)` | `await wfx.register(email, pass)` |
|
|
329
|
+
| `wfx.login(email, pass)` | `await wfx.login(email, pass)` |
|
|
330
|
+
| `wfx.encrypt(file, name)` | `await wfx.encrypt(data, filename=name)` |
|
|
331
|
+
| `wfx.decrypt(fileId)` | `await wfx.decrypt(file_id)` |
|
|
332
|
+
| `wfx.encryptMessage(text, id)` | `await wfx.encrypt_message(text, id)` |
|
|
333
|
+
| `wfx.serverEncrypt(msg)` | `await wfx.server_encrypt(msg)` |
|
|
334
|
+
| `wfx.createStream(dir)` | `await wfx.create_stream(dir)` |
|
|
335
|
+
| `new WolfronixAdmin(config)` | `WolfronixAdmin(WolfronixAdminConfig(...))` |
|
|
336
|
+
| `admin.registerClient(...)` | `await admin.register_client(...)` |
|
|
337
|
+
|
|
338
|
+
## Requirements
|
|
339
|
+
|
|
340
|
+
- Python 3.9+
|
|
341
|
+
- `httpx` >= 0.25.0
|
|
342
|
+
- `cryptography` >= 41.0.0
|
|
343
|
+
- `websockets` >= 12.0 (optional, for streaming)
|
|
344
|
+
|
|
345
|
+
## License
|
|
346
|
+
|
|
347
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Wolfronix SDK for Python
|
|
2
|
+
|
|
3
|
+
> Official Python SDK for [Wolfronix](https://wolfronix.com) — Zero-knowledge encryption made simple.
|
|
4
|
+
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install wolfronix-sdk
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
For WebSocket streaming support:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install wolfronix-sdk[websocket]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import asyncio
|
|
24
|
+
from wolfronix import Wolfronix, WolfronixConfig
|
|
25
|
+
|
|
26
|
+
async def main():
|
|
27
|
+
# Initialize client
|
|
28
|
+
wfx = Wolfronix(WolfronixConfig(
|
|
29
|
+
base_url="https://your-wolfronix-server:9443",
|
|
30
|
+
client_id="your_client_id",
|
|
31
|
+
wolfronix_key="your_wolfronix_key",
|
|
32
|
+
insecure=True, # For self-signed certs
|
|
33
|
+
))
|
|
34
|
+
|
|
35
|
+
# Health check
|
|
36
|
+
healthy = await wfx.health_check()
|
|
37
|
+
print(f"Server healthy: {healthy}")
|
|
38
|
+
|
|
39
|
+
# Register a new user
|
|
40
|
+
result = await wfx.register("user@example.com", "secure_password")
|
|
41
|
+
print(f"Registered: {result.user_id}")
|
|
42
|
+
|
|
43
|
+
# Or login with existing credentials
|
|
44
|
+
result = await wfx.login("user@example.com", "secure_password")
|
|
45
|
+
print(f"Logged in: {result.user_id}")
|
|
46
|
+
|
|
47
|
+
# Encrypt a file
|
|
48
|
+
with open("document.pdf", "rb") as f:
|
|
49
|
+
enc = await wfx.encrypt(f.read(), filename="document.pdf")
|
|
50
|
+
print(f"Encrypted! File ID: {enc.file_id}")
|
|
51
|
+
|
|
52
|
+
# Decrypt a file
|
|
53
|
+
decrypted_bytes = await wfx.decrypt(enc.file_id)
|
|
54
|
+
with open("decrypted.pdf", "wb") as f:
|
|
55
|
+
f.write(decrypted_bytes)
|
|
56
|
+
|
|
57
|
+
# List files
|
|
58
|
+
files = await wfx.list_files()
|
|
59
|
+
for f in files.files:
|
|
60
|
+
print(f" {f.original_name} (ID: {f.file_id})")
|
|
61
|
+
|
|
62
|
+
asyncio.run(main())
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Features
|
|
66
|
+
|
|
67
|
+
### Authentication (Zero-Knowledge)
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
# Register — generates RSA-2048 keys client-side, wraps private key with password
|
|
71
|
+
result = await wfx.register("user@example.com", "password123")
|
|
72
|
+
|
|
73
|
+
# Login — fetches wrapped key, unwraps client-side (server never sees raw private key)
|
|
74
|
+
result = await wfx.login("user@example.com", "password123")
|
|
75
|
+
|
|
76
|
+
# Direct token auth (for server-side apps)
|
|
77
|
+
wfx.set_token("jwt-token", "user-id")
|
|
78
|
+
|
|
79
|
+
# Logout (clears keys from memory)
|
|
80
|
+
wfx.logout()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### File Encryption (4-Layer Architecture)
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
# Encrypt
|
|
87
|
+
with open("secret.pdf", "rb") as f:
|
|
88
|
+
result = await wfx.encrypt(f.read(), filename="secret.pdf")
|
|
89
|
+
print(f"File ID: {result.file_id}, Time: {result.enc_time_ms}ms")
|
|
90
|
+
|
|
91
|
+
# Decrypt (zero-knowledge flow — private key never leaves client)
|
|
92
|
+
data = await wfx.decrypt(result.file_id)
|
|
93
|
+
with open("decrypted.pdf", "wb") as f:
|
|
94
|
+
f.write(data)
|
|
95
|
+
|
|
96
|
+
# List all files
|
|
97
|
+
files = await wfx.list_files()
|
|
98
|
+
|
|
99
|
+
# Delete a file
|
|
100
|
+
await wfx.delete_file(result.file_id)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### E2E Chat Encryption (RSA + AES Hybrid)
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
# Encrypt a message for a recipient
|
|
107
|
+
packet = await wfx.encrypt_message("Hello, Bob!", "bob@example.com")
|
|
108
|
+
# Send `packet` (JSON string) via your chat transport
|
|
109
|
+
|
|
110
|
+
# Decrypt a received message
|
|
111
|
+
plaintext = await wfx.decrypt_message(packet)
|
|
112
|
+
print(plaintext) # "Hello, Bob!"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Server-Side Message Encryption (Dual-Key Split)
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
# Layer 4 (default) — server splits key, you get key_part_a
|
|
119
|
+
result = await wfx.server_encrypt("Sensitive message")
|
|
120
|
+
|
|
121
|
+
# Decrypt with your key_part_a
|
|
122
|
+
from wolfronix import ServerDecryptParams
|
|
123
|
+
text = await wfx.server_decrypt(ServerDecryptParams(
|
|
124
|
+
encrypted_message=result.encrypted_message,
|
|
125
|
+
nonce=result.nonce,
|
|
126
|
+
key_part_a=result.key_part_a,
|
|
127
|
+
message_tag=result.message_tag,
|
|
128
|
+
))
|
|
129
|
+
|
|
130
|
+
# Batch encryption (up to 100 messages, single round-trip)
|
|
131
|
+
batch = await wfx.server_encrypt_batch([
|
|
132
|
+
{"id": "msg1", "message": "Hello"},
|
|
133
|
+
{"id": "msg2", "message": "World"},
|
|
134
|
+
])
|
|
135
|
+
|
|
136
|
+
# Decrypt batch items
|
|
137
|
+
text1 = await wfx.server_decrypt_batch_item(batch, 0)
|
|
138
|
+
text2 = await wfx.server_decrypt_batch_item(batch, 1)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### WebSocket Streaming Encryption
|
|
142
|
+
|
|
143
|
+
> Requires `pip install wolfronix-sdk[websocket]`
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
# Encrypt stream
|
|
147
|
+
stream = await wfx.create_stream("encrypt")
|
|
148
|
+
enc1 = await stream.send("Chunk 1 data")
|
|
149
|
+
enc2 = await stream.send("Chunk 2 data")
|
|
150
|
+
summary = await stream.end()
|
|
151
|
+
print(f"Processed {summary['chunks_processed']} chunks")
|
|
152
|
+
|
|
153
|
+
# Save stream.key_part_a and stream.stream_tag for decryption
|
|
154
|
+
|
|
155
|
+
# Decrypt stream
|
|
156
|
+
dec_stream = await wfx.create_stream("decrypt", {
|
|
157
|
+
"key_part_a": stream.key_part_a,
|
|
158
|
+
"stream_tag": stream.stream_tag,
|
|
159
|
+
})
|
|
160
|
+
plain1 = await dec_stream.send(enc1)
|
|
161
|
+
plain2 = await dec_stream.send(enc2)
|
|
162
|
+
await dec_stream.end()
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Enterprise Admin Client
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from wolfronix import WolfronixAdmin, WolfronixAdminConfig
|
|
169
|
+
|
|
170
|
+
admin = WolfronixAdmin(WolfronixAdminConfig(
|
|
171
|
+
base_url="https://wolfronix-server:9443",
|
|
172
|
+
admin_key="your-admin-api-key",
|
|
173
|
+
insecure=True,
|
|
174
|
+
))
|
|
175
|
+
|
|
176
|
+
# Register a client with managed Supabase connector
|
|
177
|
+
result = await admin.register_client({
|
|
178
|
+
"client_id": "acme_corp",
|
|
179
|
+
"client_name": "Acme Corporation",
|
|
180
|
+
"db_type": "supabase",
|
|
181
|
+
"db_config": '{"supabase_url": "https://xxx.supabase.co", "supabase_service_key": "eyJ..."}',
|
|
182
|
+
})
|
|
183
|
+
print(f"Wolfronix key: {result.wolfronix_key}")
|
|
184
|
+
|
|
185
|
+
# Register with custom API
|
|
186
|
+
result = await admin.register_client({
|
|
187
|
+
"client_id": "my_app",
|
|
188
|
+
"client_name": "My Application",
|
|
189
|
+
"db_type": "custom_api",
|
|
190
|
+
"api_endpoint": "https://my-storage-api.com",
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
# List all clients
|
|
194
|
+
clients = await admin.list_clients()
|
|
195
|
+
for c in clients.clients:
|
|
196
|
+
print(f" {c.client_id} ({c.db_type}) — active: {c.is_active}")
|
|
197
|
+
|
|
198
|
+
# Get / update / deactivate
|
|
199
|
+
client = await admin.get_client("acme_corp")
|
|
200
|
+
await admin.update_client("acme_corp", {"db_config": '{"new": "config"}'})
|
|
201
|
+
await admin.deactivate_client("acme_corp")
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Metrics
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
metrics = await wfx.get_metrics()
|
|
208
|
+
print(f"Total encryptions: {metrics.total_encryptions}")
|
|
209
|
+
print(f"Total decryptions: {metrics.total_decryptions}")
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Crypto Utilities
|
|
213
|
+
|
|
214
|
+
You can use the low-level crypto functions directly:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from wolfronix.crypto import (
|
|
218
|
+
generate_key_pair,
|
|
219
|
+
export_key_to_pem,
|
|
220
|
+
import_key_from_pem,
|
|
221
|
+
wrap_private_key,
|
|
222
|
+
unwrap_private_key,
|
|
223
|
+
generate_session_key,
|
|
224
|
+
encrypt_data,
|
|
225
|
+
decrypt_data,
|
|
226
|
+
rsa_encrypt,
|
|
227
|
+
rsa_decrypt,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Generate RSA-2048 key pair
|
|
231
|
+
keys = generate_key_pair()
|
|
232
|
+
pub_pem = export_key_to_pem(keys.public_key, "public")
|
|
233
|
+
priv_pem = export_key_to_pem(keys.private_key, "private")
|
|
234
|
+
|
|
235
|
+
# Wrap private key with password (PBKDF2 + AES-256-GCM)
|
|
236
|
+
wrapped = wrap_private_key(keys.private_key, "my_password")
|
|
237
|
+
# wrapped.encrypted_key (base64), wrapped.salt (hex)
|
|
238
|
+
|
|
239
|
+
# Unwrap private key
|
|
240
|
+
private_key = unwrap_private_key(wrapped.encrypted_key, "my_password", wrapped.salt)
|
|
241
|
+
|
|
242
|
+
# AES-256-GCM encryption
|
|
243
|
+
session_key = generate_session_key()
|
|
244
|
+
encrypted, iv = encrypt_data("Hello, World!", session_key)
|
|
245
|
+
plaintext = decrypt_data(encrypted, iv, session_key)
|
|
246
|
+
|
|
247
|
+
# RSA-OAEP encryption
|
|
248
|
+
ciphertext = rsa_encrypt(b"secret data", keys.public_key)
|
|
249
|
+
plaintext_bytes = rsa_decrypt(ciphertext, keys.private_key)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Security Architecture
|
|
253
|
+
|
|
254
|
+
| Layer | Protection | Implementation |
|
|
255
|
+
|-------|-----------|----------------|
|
|
256
|
+
| Layer 1 | Fake data generation | Synthetic PAN, Aadhaar, SSN for dev/test |
|
|
257
|
+
| Layer 2 | RBAC masking | Role-based partial/full data masking |
|
|
258
|
+
| Layer 3 | AES-256-GCM encryption | Server-side symmetric encryption |
|
|
259
|
+
| Layer 4 | Dual-key RSA split | RSA-2048 OAEP — key split between client & server |
|
|
260
|
+
|
|
261
|
+
**Zero-Knowledge Design:** Your private key is never stored on or transmitted to the server. It's wrapped with your password client-side using PBKDF2 (100,000 iterations) + AES-256-GCM before upload.
|
|
262
|
+
|
|
263
|
+
## Error Handling
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
from wolfronix.errors import (
|
|
267
|
+
WolfronixError,
|
|
268
|
+
AuthenticationError,
|
|
269
|
+
ValidationError,
|
|
270
|
+
NetworkError,
|
|
271
|
+
PermissionDeniedError,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
try:
|
|
275
|
+
await wfx.login("user@example.com", "wrong_password")
|
|
276
|
+
except AuthenticationError as e:
|
|
277
|
+
print(f"Auth failed: {e}")
|
|
278
|
+
except NetworkError as e:
|
|
279
|
+
print(f"Network error: {e}")
|
|
280
|
+
except WolfronixError as e:
|
|
281
|
+
print(f"Error [{e.code}]: {e} (HTTP {e.status_code})")
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## API Parity with JavaScript SDK
|
|
285
|
+
|
|
286
|
+
This Python SDK mirrors the [JavaScript/TypeScript SDK](../javascript/) 1:1:
|
|
287
|
+
|
|
288
|
+
| JavaScript | Python |
|
|
289
|
+
|-----------|--------|
|
|
290
|
+
| `new Wolfronix(config)` | `Wolfronix(WolfronixConfig(...))` |
|
|
291
|
+
| `wfx.register(email, pass)` | `await wfx.register(email, pass)` |
|
|
292
|
+
| `wfx.login(email, pass)` | `await wfx.login(email, pass)` |
|
|
293
|
+
| `wfx.encrypt(file, name)` | `await wfx.encrypt(data, filename=name)` |
|
|
294
|
+
| `wfx.decrypt(fileId)` | `await wfx.decrypt(file_id)` |
|
|
295
|
+
| `wfx.encryptMessage(text, id)` | `await wfx.encrypt_message(text, id)` |
|
|
296
|
+
| `wfx.serverEncrypt(msg)` | `await wfx.server_encrypt(msg)` |
|
|
297
|
+
| `wfx.createStream(dir)` | `await wfx.create_stream(dir)` |
|
|
298
|
+
| `new WolfronixAdmin(config)` | `WolfronixAdmin(WolfronixAdminConfig(...))` |
|
|
299
|
+
| `admin.registerClient(...)` | `await admin.register_client(...)` |
|
|
300
|
+
|
|
301
|
+
## Requirements
|
|
302
|
+
|
|
303
|
+
- Python 3.9+
|
|
304
|
+
- `httpx` >= 0.25.0
|
|
305
|
+
- `cryptography` >= 41.0.0
|
|
306
|
+
- `websockets` >= 12.0 (optional, for streaming)
|
|
307
|
+
|
|
308
|
+
## License
|
|
309
|
+
|
|
310
|
+
MIT — see [LICENSE](LICENSE).
|