gun101 1.2.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.
@@ -0,0 +1,35 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build-and-publish:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ id-token: write # IMPORTANT: this permission is required for trusted publishing
12
+ contents: read
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Set up Python
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: '3.9'
21
+
22
+ - name: Install build tools
23
+ run: >-
24
+ python -m pip install build --user
25
+
26
+ - name: Clean old artifacts
27
+ run: rm -rf dist/
28
+
29
+ - name: Build a binary wheel and a source tarball
30
+ run: >-
31
+ python -m build --sdist --wheel --outdir dist/
32
+
33
+ - name: Publish package to PyPI
34
+ uses: pypa/gh-action-pypi-publish@release/v1
35
+ # Note: No password/token is provided here! It uses Trusted Publishing instead.
@@ -0,0 +1,25 @@
1
+ name: Run Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [ "main" ]
6
+ pull_request:
7
+ branches: [ "main" ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - name: Set up Python
16
+ uses: actions/setup-python@v5
17
+ with:
18
+ python-version: '3.9'
19
+ - name: Install dependencies
20
+ run: |
21
+ python -m pip install --upgrade pip
22
+ pip install .[dev]
23
+ - name: Run tests
24
+ run: |
25
+ pytest
@@ -0,0 +1,25 @@
1
+ # Build artifacts
2
+ dist/
3
+ build/
4
+ *.egg-info/
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.py[cod]
9
+ *.so
10
+ .venv/
11
+ venv/
12
+
13
+ # IDE
14
+ .vscode/
15
+ .idea/
16
+
17
+ # OS
18
+ .DS_Store
19
+ Thumbs.db
20
+
21
+ # Security layer state
22
+ .security/
23
+
24
+ # GKP identity (never commit private keys)
25
+ .gun-112/
gun101-1.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Security 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.
gun101-1.2.0/PKG-INFO ADDED
@@ -0,0 +1,542 @@
1
+ Metadata-Version: 2.4
2
+ Name: gun101
3
+ Version: 1.2.0
4
+ Summary: GUN101 PDF Encryption Protocol
5
+ Author: Security Team
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.9
9
+ Requires-Dist: argon2-cffi==23.1.0
10
+ Requires-Dist: cryptography==42.0.2
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest-cov==4.1.0; extra == 'dev'
13
+ Requires-Dist: pytest==7.4.3; extra == 'dev'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # GUN101 PDF Encryption Protocol
17
+
18
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/downloads/)
19
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
20
+ [![Version](https://img.shields.io/badge/version-1.2.0-orange.svg)](pyproject.toml)
21
+
22
+ **GUN101** is a production-ready Python library for encrypting PDF files with military-grade AES-256-GCM encryption, augmented by a nanosecond-precision time-based cryptographic challenge system. It protects your documents even when users choose weak passwords.
23
+
24
+ ---
25
+
26
+ ## Table of Contents
27
+
28
+ - [What is GUN101?](#what-is-gun101)
29
+ - [Security Architecture](#security-architecture)
30
+ - [Installation](#installation)
31
+ - [Quick Start](#quick-start)
32
+ - [Password-Based Encryption (GUN101)](#password-based-encryption)
33
+ - [GUN101-GKP (Ghost Key Protocol)](#gun101-gkp-ghost-key-protocol)
34
+ - [CLI Usage](#cli-usage)
35
+ - [Python API Reference](#python-api-reference)
36
+ - [PDFEncryptionHandler](#pdfencryptionhandler)
37
+ - [IdentityManager](#identitymanager)
38
+ - [CryptoEngine](#cryptoengine)
39
+ - [KeyManager](#keymanager)
40
+ - [SecurityLayer](#securitylayer)
41
+ - [Configuration](#configuration)
42
+ - [Examples](#examples)
43
+ - [Running Tests](#running-tests)
44
+ - [Security Considerations](#security-considerations)
45
+ - [Applying the GKP Rename — Files to Update](#applying-the-gkp-rename--files-to-update)
46
+ - [License](#license)
47
+
48
+ ---
49
+
50
+ ## What is GUN101?
51
+
52
+ GUN101 is a layered PDF encryption protocol built around a core insight: even if a password is stolen, the encrypted file should remain uncrackable. It does this by weaving nanosecond-precision timestamps into the encryption key itself.
53
+
54
+ **Two encryption modes are supported:**
55
+
56
+ | Mode | How it works | Best for |
57
+ |---|---|---|
58
+ | **GUN101** (password-based) | User supplies a password; multi-layer key derivation hardens it | Personal files, shared secrets |
59
+ | **GUN101-GKP** (Ghost Key Protocol) | A random AES key is sealed with the recipient's RSA-4096 public key | Targeted delivery; no password needed |
60
+
61
+ ---
62
+
63
+ ## Security Architecture
64
+
65
+ GUN101 stacks five independent security layers. Breaking the encryption requires defeating all of them simultaneously.
66
+
67
+ ### Layer 1 — Argon2 Password Hashing
68
+ The user's password is hashed with Argon2id — a memory-hard, GPU-resistant algorithm. This prevents rainbow table attacks and makes brute-force attempts extremely expensive.
69
+
70
+ ### Layer 2 — Dual-Pass PBKDF2 Key Derivation
71
+ The raw password is stretched through two sequential PBKDF2 passes:
72
+ - **Pass 1**: PBKDF2-HMAC-SHA256 at 100,000 iterations
73
+ - **Pass 2**: PBKDF2-HMAC-SHA512 at 10,000 iterations
74
+
75
+ This ensures the encryption key is deterministic (same password + salt = same key, every time) while being computationally costly to brute-force.
76
+
77
+ ### Layer 3 — GUN101 Time-Based Challenge
78
+ At the moment of encryption, a challenge is created from the current time captured at nanosecond precision. This challenge is salted and hashed (SHA-256), then stored alongside the ciphertext. During decryption, the challenge is re-verified — any tampering with the file causes this check to fail.
79
+
80
+ The timestamp component is also mixed into the final AES key, meaning the key varies with each encryption even if the password is identical. Reverse-engineering this requires knowing the exact nanosecond of encryption, which is impossible after the fact.
81
+
82
+ ### Layer 4 — AES-256-GCM Authenticated Encryption
83
+ Actual file content is encrypted with AES-256 in GCM mode. GCM provides both confidentiality (the content is hidden) and authenticity (any modification to the ciphertext is detected and rejected).
84
+
85
+ ### Layer 5 — Rate Limiting
86
+ Decryption attempts are tracked per file (by SHA-256 fingerprint). After 5 failed attempts, the file is locked for 15 minutes. Lockout state persists to disk in `.security/lockout_log.json`.
87
+
88
+ ---
89
+
90
+ ## Installation
91
+
92
+ **From PyPI (recommended):**
93
+ ```bash
94
+ pip install gun101
95
+ ```
96
+
97
+ **From source:**
98
+ ```bash
99
+ git clone https://github.com/25f3002130/GUN101.git
100
+ cd GUN101
101
+ pip install -e .
102
+ ```
103
+
104
+ **Requirements:** Python 3.9+, `cryptography>=42.0.2`, `argon2-cffi>=23.1.0`
105
+
106
+ > **Note:** The PyPI package name is `gun101` (with hyphen), but the Python import name is `gun101` (no hyphen). This is standard Python packaging practice — hyphens are not valid in Python identifiers.
107
+
108
+ ---
109
+
110
+ ## Quick Start
111
+
112
+ ### Password-Based Encryption
113
+
114
+ ```python
115
+ from gun101 import PDFEncryptionHandler
116
+
117
+ handler = PDFEncryptionHandler()
118
+
119
+ # Read your PDF
120
+ with open("document.pdf", "rb") as f:
121
+ pdf_data = f.read()
122
+
123
+ # Encrypt
124
+ encrypted = handler.encrypt_pdf(pdf_data, password="MySecurePassword!")
125
+
126
+ # Save encrypted file
127
+ with open("document.pdf.encrypted", "wb") as f:
128
+ f.write(encrypted)
129
+
130
+ # Decrypt later
131
+ with open("document.pdf.encrypted", "rb") as f:
132
+ encrypted_data = f.read()
133
+
134
+ decrypted_pdf, metadata = handler.decrypt_pdf(encrypted_data, password="MySecurePassword!")
135
+
136
+ with open("document_restored.pdf", "wb") as f:
137
+ f.write(decrypted_pdf)
138
+ ```
139
+
140
+ ### GUN101-GKP (Ghost Key Protocol)
141
+
142
+ GUN101-GKP requires no shared password. The sender uses the recipient's public **GKP Identity Token**; only the recipient's physical device (which holds the matching private key) can decrypt the file.
143
+
144
+ **Step 1 — Recipient generates their GKP identity (one-time setup):**
145
+ ```python
146
+ from gun101.identity import IdentityManager
147
+
148
+ manager = IdentityManager()
149
+ token = manager.generate_identity() # optionally pass a passphrase
150
+ print("Share this token with senders:\n", token)
151
+ ```
152
+
153
+ **Step 2 — Sender encrypts for the recipient:**
154
+ ```python
155
+ from gun101 import PDFEncryptionHandler
156
+
157
+ handler = PDFEncryptionHandler()
158
+
159
+ with open("document.pdf", "rb") as f:
160
+ pdf_data = f.read()
161
+
162
+ recipient_token = "..." # GKP Identity Token provided by the recipient
163
+
164
+ encrypted = handler.encrypt_pdf_for_recipient(pdf_data, recipient_token)
165
+
166
+ with open("document.pdf.encrypted", "wb") as f:
167
+ f.write(encrypted)
168
+ ```
169
+
170
+ **Step 3 — Recipient decrypts on their device:**
171
+ ```python
172
+ from gun101 import PDFEncryptionHandler
173
+
174
+ handler = PDFEncryptionHandler()
175
+
176
+ with open("document.pdf.encrypted", "rb") as f:
177
+ encrypted_data = f.read()
178
+
179
+ decrypted_pdf, metadata = handler.decrypt_pdf_identity(encrypted_data)
180
+
181
+ with open("document_restored.pdf", "wb") as f:
182
+ f.write(decrypted_pdf)
183
+ ```
184
+
185
+ ---
186
+
187
+ ## CLI Usage
188
+
189
+ GUN101 ships with a `gun101` command-line tool installed automatically with the package.
190
+
191
+ ```
192
+ gun101 help Show all commands
193
+ gun101 encrypt <file> Encrypt a PDF with a password (GUN101)
194
+ gun101 encrypt <file> --recipient <TOKEN> Encrypt for a specific recipient (GUN101-GKP)
195
+ gun101 decrypt <file> Decrypt (auto-detects GUN101 vs GKP)
196
+ gun101 generate-identity Generate your GKP Identity Token
197
+ gun101 show-identity Display your GKP Identity Token
198
+ gun101 reset-identity Delete your GKP identity (irreversible)
199
+ ```
200
+
201
+ **Options:**
202
+
203
+ | Flag | Description |
204
+ |---|---|
205
+ | `-o`, `--output PATH` | Custom output file path |
206
+ | `--recipient TOKEN` | Recipient's GKP Identity Token (enables GUN101-GKP mode) |
207
+
208
+ **Examples:**
209
+
210
+ ```bash
211
+ # Password-based (GUN101) — prompted to enter and confirm the password
212
+ gun101 encrypt report.pdf
213
+ gun101 decrypt report.pdf.encrypted
214
+
215
+ # GUN101-GKP — no password needed
216
+ gun101 generate-identity
217
+ gun101 show-identity # copy the token and send it to the sender
218
+ gun101 encrypt report.pdf --recipient <GKP_TOKEN>
219
+ gun101 decrypt report.pdf.encrypted # auto-detects GKP mode
220
+ ```
221
+
222
+ ---
223
+
224
+ ## Python API Reference
225
+
226
+ ### PDFEncryptionHandler
227
+
228
+ The main entry point for all encryption and decryption operations.
229
+
230
+ ```python
231
+ from gun101 import PDFEncryptionHandler
232
+ handler = PDFEncryptionHandler()
233
+ ```
234
+
235
+ #### `encrypt_pdf(pdf_data, password, metadata=None) → bytes`
236
+
237
+ Encrypts a PDF using password-based GUN101. Raises `ValueError` on wrong password, challenge failure, tampering, or lockout.
238
+
239
+ | Parameter | Type | Description |
240
+ |---|---|---|
241
+ | `pdf_data` | `bytes` | Raw PDF file content |
242
+ | `password` | `str` | User password |
243
+ | `metadata` | `dict`, optional | Arbitrary key-value metadata to encrypt alongside the PDF |
244
+
245
+ Returns the encrypted container as `bytes`.
246
+
247
+ #### `decrypt_pdf(encrypted_package, password) → tuple[bytes, dict | None]`
248
+
249
+ Decrypts a password-locked GUN101 PDF.
250
+
251
+ | Parameter | Type | Description |
252
+ |---|---|---|
253
+ | `encrypted_package` | `bytes` | Output of `encrypt_pdf` |
254
+ | `password` | `str` | User password |
255
+
256
+ Returns `(pdf_bytes, metadata_dict_or_None)`.
257
+
258
+ #### `encrypt_pdf_for_recipient(pdf_data, recipient_token, metadata=None) → bytes`
259
+
260
+ Encrypts a PDF using GUN101-GKP for a specific recipient. No password required. A random AES-256 key is generated and sealed using the recipient's RSA-4096 GKP Identity Token.
261
+
262
+ | Parameter | Type | Description |
263
+ |---|---|---|
264
+ | `pdf_data` | `bytes` | Raw PDF file content |
265
+ | `recipient_token` | `str` | Recipient's GKP Identity Token |
266
+ | `metadata` | `dict`, optional | Arbitrary key-value metadata to encrypt alongside the PDF |
267
+
268
+ #### `decrypt_pdf_identity(encrypted_package, passphrase=None) → tuple[bytes, dict | None]`
269
+
270
+ Decrypts a GKP-locked PDF using this device's GKP private key.
271
+
272
+ | Parameter | Type | Description |
273
+ |---|---|---|
274
+ | `encrypted_package` | `bytes` | Output of `encrypt_pdf_for_recipient` |
275
+ | `passphrase` | `str`, optional | Passphrase if the GKP private key was protected at generation time |
276
+
277
+ ---
278
+
279
+ ### IdentityManager
280
+
281
+ Manages RSA-4096 keypairs and GKP Identity Tokens.
282
+
283
+ ```python
284
+ from gun101.identity import IdentityManager
285
+ manager = IdentityManager()
286
+ ```
287
+
288
+ | Method | Description |
289
+ |---|---|
290
+ | `generate_identity(passphrase=None) → str` | Generate a new GKP keypair; returns the public Identity Token |
291
+ | `get_identity_token() → str` | Retrieve the stored GKP Identity Token |
292
+ | `get_identity_fingerprint(token=None) → str` | Get a human-readable colon-separated fingerprint |
293
+ | `has_identity() → bool` | Check if a GKP identity exists on this device |
294
+ | `reset_identity()` | Delete the keypair from disk (irreversible) |
295
+ | `encrypt_symmetric_key(key, token) → bytes` | RSA-OAEP encrypt an AES key with a recipient's GKP token |
296
+ | `decrypt_symmetric_key(encrypted_key, passphrase=None) → bytes` | RSA-OAEP decrypt using local GKP private key |
297
+
298
+ GKP private keys are stored at `~/.gun101/private_key.pem` with `0o600` permissions (owner read/write only).
299
+
300
+ ---
301
+
302
+ ### CryptoEngine
303
+
304
+ Low-level AES-256-GCM encryption primitives.
305
+
306
+ ```python
307
+ from gun101 import CryptoEngine
308
+ engine = CryptoEngine()
309
+ ```
310
+
311
+ | Method | Description |
312
+ |---|---|
313
+ | `encrypt(data, key, aad=None) → (nonce, ciphertext, tag)` | Encrypt; returns separate components |
314
+ | `decrypt(nonce, ciphertext, tag, key, aad=None) → bytes` | Decrypt and verify |
315
+ | `encrypt_to_bytes(data, key, aad=None) → bytes` | Encrypt to single byte string (`nonce + ciphertext + tag`) |
316
+ | `decrypt_from_bytes(encrypted, key, aad=None) → bytes` | Decrypt from single byte string |
317
+
318
+ ---
319
+
320
+ ### KeyManager
321
+
322
+ Password hashing and key derivation.
323
+
324
+ ```python
325
+ from gun101 import KeyManager
326
+ km = KeyManager()
327
+ ```
328
+
329
+ | Method | Description |
330
+ |---|---|
331
+ | `derive_key_from_password(password, salt=None) → (key, salt)` | PBKDF2 dual-pass key derivation |
332
+ | `hash_password(password) → str` | Argon2 hash for storage |
333
+ | `verify_password(password, argon2_hash) → bool` | Verify a password against stored Argon2 hash |
334
+
335
+ ---
336
+
337
+ ### SecurityLayer
338
+
339
+ Rate-limiting and brute-force protection.
340
+
341
+ ```python
342
+ from gun101 import SecurityLayer
343
+ layer = SecurityLayer(max_attempts=5)
344
+ ```
345
+
346
+ | Method | Description |
347
+ |---|---|
348
+ | `record_attempt(identifier, success=False) → (allowed, message)` | Record a decryption attempt; returns whether allowed to proceed |
349
+ | `is_locked(identifier) → bool` | Check if an identifier is currently locked out |
350
+ | `get_status(identifier) → dict` | Get attempt count, lock status, and remaining attempts |
351
+ | `reset_attempts(identifier)` | Manually reset attempt counter |
352
+
353
+ ---
354
+
355
+ ## Configuration
356
+
357
+ Security parameters are centralized in `SecurityConfig` and available via the `security_config` singleton:
358
+
359
+ ```python
360
+ from gun101 import security_config
361
+
362
+ print(security_config.ENCRYPTION_PROTOCOL) # GUN101
363
+ print(security_config.GKP_PROTOCOL) # GUN101-GKP
364
+ print(security_config.ENCRYPTION_ALGORITHM) # AES-256-GCM
365
+ print(security_config.MAX_DECRYPTION_ATTEMPTS) # 5
366
+ print(security_config.LOCKOUT_DURATION) # 15 minutes
367
+ ```
368
+
369
+ | Parameter | Default | Description |
370
+ |---|---|---|
371
+ | `ENCRYPTION_PROTOCOL` | `"GUN101"` | Outer protocol identifier (all containers) |
372
+ | `GKP_PROTOCOL` | `"GUN101-GKP"` | GKP sub-protocol identifier |
373
+ | `GKP_LOCK_MODE` | `"GKP"` | Value stored in container's `lock_mode` field |
374
+ | `KEY_SIZE` | 32 bytes | AES key length (256 bits) |
375
+ | `ARGON2_TIME_COST` | 4 | Argon2 iteration count |
376
+ | `ARGON2_MEMORY_COST` | 128 MB | Argon2 memory requirement |
377
+ | `ARGON2_PARALLELISM` | 4 | Argon2 thread count |
378
+ | `KEY_STRETCH_ITERATIONS` | 100,000 | PBKDF2 iterations (first pass) |
379
+ | `MAX_DECRYPTION_ATTEMPTS` | 5 | Failed attempts before lockout |
380
+ | `LOCKOUT_DURATION` | 15 minutes | Lockout duration |
381
+ | `TIMESTAMP_TOLERANCE` | 300 seconds | Tolerance window for timestamp validation |
382
+
383
+ ---
384
+
385
+ ## Examples
386
+
387
+ The `examples/` directory contains ready-to-run scripts:
388
+
389
+ ```bash
390
+ # Basic password-based encryption and decryption
391
+ python examples/basic_usage.py
392
+
393
+ # Advanced configuration and GUN101-GKP (Ghost Key Protocol)
394
+ python examples/advanced_config.py
395
+ ```
396
+
397
+ **Encrypt a real file in three lines:**
398
+ ```python
399
+ from gun101 import PDFEncryptionHandler
400
+ handler = PDFEncryptionHandler()
401
+ open("out.encrypted","wb").write(handler.encrypt_pdf(open("doc.pdf","rb").read(), "password"))
402
+ ```
403
+
404
+ **Attach metadata to an encrypted file:**
405
+ ```python
406
+ metadata = {"author": "Alice", "classification": "CONFIDENTIAL"}
407
+ encrypted = handler.encrypt_pdf(pdf_data, "password", metadata=metadata)
408
+
409
+ pdf, meta = handler.decrypt_pdf(encrypted, "password")
410
+ print(meta) # {'author': 'Alice', 'classification': 'CONFIDENTIAL'}
411
+ ```
412
+
413
+ ---
414
+
415
+ ## Running Tests
416
+
417
+ ```bash
418
+ pip install -e ".[dev]"
419
+ pytest
420
+ pytest --cov=gun101 --cov-report=term-missing # with coverage
421
+ ```
422
+
423
+ Tests cover encryption/decryption correctness, wrong-password rejection, rate limiting, GKP identity keypair operations, and GUN101 challenge verification.
424
+
425
+ ---
426
+
427
+ ## Security Considerations
428
+
429
+ - **Lost password**: Password-locked files cannot be recovered without the original password. There is no back door.
430
+ - **Lost GKP identity**: If you delete your GKP identity (or lose the device), any GKP-encrypted files targeting that identity are permanently unrecoverable. Back up `~/.gun101/private_key.pem` before resetting.
431
+ - **GKP passphrase protection**: When generating a GKP identity, supply a passphrase to encrypt the private key at rest on disk.
432
+ - **Rate limiting persistence**: Lockout state is written to `.security/lockout_log.json` in the working directory. In server deployments, ensure this path is writable and persistent.
433
+ - **Timestamp validation**: GUN101 verifies that the challenge timestamp in the encrypted metadata matches the one in the container. Any tampering with either field causes decryption to fail.
434
+
435
+ ---
436
+
437
+ ## Applying the GKP Rename — Files to Update
438
+
439
+ The GUN101-GKP (Ghost Key Protocol) naming was introduced after the initial release. If you are applying this rename to an existing copy of the codebase, here are every file that needs to change and exactly what to update in each.
440
+
441
+ > **Note on backward compatibility**: The outer `"protocol"` field in all encrypted containers stays `"GUN101"`. Only the `lock_mode` value changes from `"identity"` → `"GKP"`. This means **any files already encrypted with the old `lock_mode: "identity"` will fail to auto-detect correctly** until `pdf_handler.py` and `cli.py` are updated. Re-encrypt important files after the update if backward compatibility matters.
442
+
443
+ ---
444
+
445
+ ### `src/gun101/config.py`
446
+
447
+ Add two new constants to the `SecurityConfig` class:
448
+
449
+ ```python
450
+ # GUN101-GKP: Ghost Key Protocol (identity/asymmetric sub-protocol)
451
+ GKP_PROTOCOL = "GUN101-GKP"
452
+ GKP_LOCK_MODE = "GKP" # value stored in the container's lock_mode field
453
+ ```
454
+
455
+ This is the single source of truth for both strings. Every other file reads from `security_config` rather than hardcoding them.
456
+
457
+ ---
458
+
459
+ ### `src/gun101/pdf_handler.py`
460
+
461
+ Three changes:
462
+
463
+ **1. Module docstring** — update the description of the identity-based mode to say "GUN101-GKP (Ghost Key Protocol)".
464
+
465
+ **2. `encrypt_pdf_for_recipient`** — the container dict currently writes `"lock_mode": "identity"`. Replace with:
466
+
467
+ ```python
468
+ "sub_protocol": security_config.GKP_PROTOCOL, # "GUN101-GKP"
469
+ "lock_mode": security_config.GKP_LOCK_MODE, # "GKP"
470
+ ```
471
+
472
+ Same change applies inside `encryption_metadata` (the dict that gets encrypted alongside the PDF).
473
+
474
+ **3. `decrypt_pdf` and `decrypt_pdf_identity`** — both methods check `lock_mode` to route or reject files. Update the string comparisons:
475
+
476
+ ```python
477
+ # decrypt_pdf — reject GKP files
478
+ if lock_mode == security_config.GKP_LOCK_MODE: # was: == "identity"
479
+ raise ValueError("This file is GKP-locked (Ghost Key Protocol)...")
480
+
481
+ # decrypt_pdf_identity — require GKP files
482
+ if lock_mode != security_config.GKP_LOCK_MODE: # was: != "identity"
483
+ raise ValueError("This file is password-locked (GUN101), not GKP-locked...")
484
+ ```
485
+
486
+ ---
487
+
488
+ ### `src/gun101/identity.py`
489
+
490
+ No functional changes — this file only needs its **docstrings and comments** updated. Rename every occurrence of "Identity Manager" / "identity-based" / "identity-locked" to use "GKP" / "Ghost Key Protocol" language. The class name `IdentityManager` and all method names stay the same.
491
+
492
+ ---
493
+
494
+ ### `src/gun101/cli.py`
495
+
496
+ Two changes:
497
+
498
+ **1. Auto-detect block in `decrypt_command`** — currently compares `lock_mode == "identity"`. Update to:
499
+
500
+ ```python
501
+ if lock_mode == security_config.GKP_LOCK_MODE:
502
+ ```
503
+
504
+ **2. User-facing text** — update the banner, help strings, and info/success messages to say "GUN101-GKP" and "Ghost Key Protocol" where they currently say "identity-based" or "Identity Token". No command names change (`generate-identity`, `show-identity`, `reset-identity` stay as-is for CLI backward compatibility).
505
+
506
+ ---
507
+
508
+ ### `src/gun101/__init__.py`
509
+
510
+ Add one new public constant:
511
+
512
+ ```python
513
+ __gkp_protocol__ = "GUN101-GKP"
514
+ ```
515
+
516
+ Update the module docstring to mention both modes by name.
517
+
518
+ ---
519
+
520
+ ### `examples/basic_usage.py` and `examples/advanced_config.py`
521
+
522
+ Update comments and `print()` strings that refer to "identity-based encryption" to say "GUN101-GKP (Ghost Key Protocol)". No functional code changes needed in the examples.
523
+
524
+ ---
525
+
526
+ ### Files that do **not** need changes
527
+
528
+ | File | Reason |
529
+ |---|---|
530
+ | `crypto_engine.py` | Pure AES-256-GCM logic; protocol-agnostic |
531
+ | `key_manager.py` | Pure key derivation; no protocol references |
532
+ | `security_layer.py` | Pure rate-limiting; no protocol references |
533
+ | `utils.py` | Pure utility functions; no protocol references |
534
+ | `gun101_challenge.py` | The challenge system is shared by both modes; no GKP-specific references |
535
+ | `pyproject.toml` | Package metadata; no protocol strings |
536
+ | `tests/` | Test logic uses method names, not protocol strings — no changes needed unless your tests assert on the `lock_mode` string value directly |
537
+
538
+ ---
539
+
540
+ ## License
541
+
542
+ MIT License — see [LICENSE](LICENSE) for full terms.