ghostbit-cli 1.3.0__tar.gz → 1.4.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.
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/PKG-INFO +1 -1
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/__init__.py +19 -2
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/_crypto.py +11 -5
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/ghostbit_cli.egg-info/PKG-INFO +1 -1
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/pyproject.toml +1 -1
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/README.md +0 -0
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/_api.py +0 -0
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/_completion.py +0 -0
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/_config.py +0 -0
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/_history.py +0 -0
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/ghostbit_cli.egg-info/SOURCES.txt +0 -0
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/ghostbit_cli.egg-info/dependency_links.txt +0 -0
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/ghostbit_cli.egg-info/entry_points.txt +0 -0
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/ghostbit_cli.egg-info/requires.txt +0 -0
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/ghostbit_cli.egg-info/top_level.txt +0 -0
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/setup.cfg +0 -0
- {ghostbit_cli-1.3.0 → ghostbit_cli-1.4.0}/setup.py +0 -0
|
@@ -27,6 +27,7 @@ from ._completion import cmd_completion as _cmd_completion_raw
|
|
|
27
27
|
from ._config import DEFAULT_SERVER, cmd_config, load_config
|
|
28
28
|
from ._crypto import (
|
|
29
29
|
decrypt,
|
|
30
|
+
decrypt_bytes,
|
|
30
31
|
derive_key,
|
|
31
32
|
encrypt,
|
|
32
33
|
gen_key,
|
|
@@ -114,7 +115,13 @@ def cmd_paste(args) -> None:
|
|
|
114
115
|
key = gen_key()
|
|
115
116
|
kdf_salt = None
|
|
116
117
|
|
|
117
|
-
|
|
118
|
+
if args.compress:
|
|
119
|
+
import gzip
|
|
120
|
+
|
|
121
|
+
payload_input: str | bytes = gzip.compress(content.encode())
|
|
122
|
+
else:
|
|
123
|
+
payload_input = content
|
|
124
|
+
ciphertext, nonce = encrypt(payload_input, key)
|
|
118
125
|
|
|
119
126
|
payload = {
|
|
120
127
|
"content": ciphertext,
|
|
@@ -124,6 +131,7 @@ def cmd_paste(args) -> None:
|
|
|
124
131
|
"expires_in": args.expires,
|
|
125
132
|
"burn": args.burn,
|
|
126
133
|
"max_views": args.max_views,
|
|
134
|
+
"compressed": args.compress,
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
result = api_create(server, payload)
|
|
@@ -269,7 +277,12 @@ def cmd_view(args) -> None:
|
|
|
269
277
|
key = base64.urlsafe_b64decode(padded)
|
|
270
278
|
|
|
271
279
|
try:
|
|
272
|
-
|
|
280
|
+
if data.get("compressed"):
|
|
281
|
+
import gzip
|
|
282
|
+
|
|
283
|
+
plaintext = gzip.decompress(decrypt_bytes(data["content"], data["nonce"], key)).decode()
|
|
284
|
+
else:
|
|
285
|
+
plaintext = decrypt(data["content"], data["nonce"], key)
|
|
273
286
|
except Exception: # noqa: BLE001
|
|
274
287
|
print("Error: decryption failed — wrong key or corrupted paste.", file=sys.stderr)
|
|
275
288
|
sys.exit(1)
|
|
@@ -501,6 +514,10 @@ current server: {current_server}
|
|
|
501
514
|
"--burn", "-b", action="store_true",
|
|
502
515
|
help="Delete after the first view.",
|
|
503
516
|
) # fmt: skip
|
|
517
|
+
parser.add_argument(
|
|
518
|
+
"--compress", "-z", action="store_true",
|
|
519
|
+
help="Gzip the plaintext locally BEFORE encryption (60–80%% smaller for text).",
|
|
520
|
+
) # fmt: skip
|
|
504
521
|
parser.add_argument(
|
|
505
522
|
"--max-views", "-m", type=int, default=None, metavar="N",
|
|
506
523
|
help="Delete after N views.",
|
|
@@ -42,17 +42,23 @@ def gen_salt() -> str:
|
|
|
42
42
|
return base64.b64encode(os.urandom(16)).decode()
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def encrypt(plaintext: str, key: bytes) -> tuple[str, str]:
|
|
45
|
+
def encrypt(plaintext: str | bytes, key: bytes) -> tuple[str, str]:
|
|
46
|
+
"""Encrypt a string or raw bytes with AES-256-GCM. Returns (ct_b64, nonce_b64)."""
|
|
46
47
|
nonce = os.urandom(12)
|
|
47
|
-
|
|
48
|
+
data = plaintext.encode() if isinstance(plaintext, str) else plaintext
|
|
49
|
+
ct = AESGCM(key).encrypt(nonce, data, None)
|
|
48
50
|
return base64.b64encode(ct).decode(), base64.b64encode(nonce).decode()
|
|
49
51
|
|
|
50
52
|
|
|
51
|
-
def
|
|
53
|
+
def decrypt_bytes(ciphertext_b64: str, nonce_b64: str, key: bytes) -> bytes:
|
|
54
|
+
"""Decrypt returning raw bytes — used by the compressed-paste path."""
|
|
52
55
|
ct = base64.b64decode(ciphertext_b64)
|
|
53
56
|
nonce = base64.b64decode(nonce_b64)
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
return AESGCM(key).decrypt(nonce, ct, None)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def decrypt(ciphertext_b64: str, nonce_b64: str, key: bytes) -> str:
|
|
61
|
+
return decrypt_bytes(ciphertext_b64, nonce_b64, key).decode()
|
|
56
62
|
|
|
57
63
|
|
|
58
64
|
def derive_key(password: str, salt_b64: str) -> bytes:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|