gbkomi 0.1.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.
- gbkomi-0.1.0/PKG-INFO +26 -0
- gbkomi-0.1.0/README.md +15 -0
- gbkomi-0.1.0/pyproject.toml +27 -0
- gbkomi-0.1.0/setup.cfg +4 -0
- gbkomi-0.1.0/src/gbkomi/__init__.py +2 -0
- gbkomi-0.1.0/src/gbkomi/cli.py +65 -0
- gbkomi-0.1.0/src/gbkomi/crypto.py +33 -0
- gbkomi-0.1.0/src/gbkomi/exceptions.py +10 -0
- gbkomi-0.1.0/src/gbkomi/vault.py +79 -0
- gbkomi-0.1.0/src/gbkomi.egg-info/PKG-INFO +26 -0
- gbkomi-0.1.0/src/gbkomi.egg-info/SOURCES.txt +13 -0
- gbkomi-0.1.0/src/gbkomi.egg-info/dependency_links.txt +1 -0
- gbkomi-0.1.0/src/gbkomi.egg-info/entry_points.txt +2 -0
- gbkomi-0.1.0/src/gbkomi.egg-info/requires.txt +2 -0
- gbkomi-0.1.0/src/gbkomi.egg-info/top_level.txt +1 -0
gbkomi-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gbkomi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Secure encrypted token storage for Python
|
|
5
|
+
Author-email: Your Name <qqqwwweeeshah@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: cryptography>=42.0.0
|
|
10
|
+
Requires-Dist: argon2-cffi>=23.1.0
|
|
11
|
+
|
|
12
|
+
# gbkomi
|
|
13
|
+
|
|
14
|
+
Secure encrypted token storage for Python using Argon2id + AES-256-GCM.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- Store secrets in encrypted `.gbkomi` files
|
|
19
|
+
- Password-based encryption
|
|
20
|
+
- CLI for managing secrets
|
|
21
|
+
- Authenticated encryption with AES-GCM
|
|
22
|
+
- Strong key derivation with Argon2id
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
```bash
|
|
26
|
+
pip install gbkomi
|
gbkomi-0.1.0/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# gbkomi
|
|
2
|
+
|
|
3
|
+
Secure encrypted token storage for Python using Argon2id + AES-256-GCM.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Store secrets in encrypted `.gbkomi` files
|
|
8
|
+
- Password-based encryption
|
|
9
|
+
- CLI for managing secrets
|
|
10
|
+
- Authenticated encryption with AES-GCM
|
|
11
|
+
- Strong key derivation with Argon2id
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
```bash
|
|
15
|
+
pip install gbkomi
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gbkomi"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Secure encrypted token storage for Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Your Name", email = "qqqwwweeeshah@gmail.com" }
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"cryptography>=42.0.0",
|
|
17
|
+
"argon2-cffi>=23.1.0"
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
gbkomi = "gbkomi.cli:main"
|
|
22
|
+
|
|
23
|
+
[tool.setuptools]
|
|
24
|
+
package-dir = {"" = "src"}
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["src"]
|
gbkomi-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import getpass
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from .vault import create_vault, set_secret, get_secret, delete_secret, list_secrets
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def prompt_password(confirm: bool = False) -> str:
|
|
9
|
+
pwd = getpass.getpass("Password: ")
|
|
10
|
+
if confirm:
|
|
11
|
+
pwd2 = getpass.getpass("Confirm password: ")
|
|
12
|
+
if pwd != pwd2:
|
|
13
|
+
print("Passwords do not match.", file=sys.stderr)
|
|
14
|
+
sys.exit(1)
|
|
15
|
+
return pwd
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main():
|
|
19
|
+
parser = argparse.ArgumentParser(prog="gbkomi")
|
|
20
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
21
|
+
|
|
22
|
+
p_init = sub.add_parser("init")
|
|
23
|
+
p_init.add_argument("path")
|
|
24
|
+
|
|
25
|
+
p_set = sub.add_parser("set")
|
|
26
|
+
p_set.add_argument("path")
|
|
27
|
+
p_set.add_argument("key")
|
|
28
|
+
p_set.add_argument("value")
|
|
29
|
+
|
|
30
|
+
p_get = sub.add_parser("get")
|
|
31
|
+
p_get.add_argument("path")
|
|
32
|
+
p_get.add_argument("key")
|
|
33
|
+
|
|
34
|
+
p_list = sub.add_parser("list")
|
|
35
|
+
p_list.add_argument("path")
|
|
36
|
+
|
|
37
|
+
p_del = sub.add_parser("delete")
|
|
38
|
+
p_del.add_argument("path")
|
|
39
|
+
p_del.add_argument("key")
|
|
40
|
+
|
|
41
|
+
args = parser.parse_args()
|
|
42
|
+
|
|
43
|
+
if args.command == "init":
|
|
44
|
+
pwd = prompt_password(confirm=True)
|
|
45
|
+
create_vault(args.path, pwd)
|
|
46
|
+
print(f"Vault created: {args.path}")
|
|
47
|
+
|
|
48
|
+
elif args.command == "set":
|
|
49
|
+
pwd = prompt_password()
|
|
50
|
+
set_secret(args.path, pwd, args.key, args.value)
|
|
51
|
+
print(f"Saved: {args.key}")
|
|
52
|
+
|
|
53
|
+
elif args.command == "get":
|
|
54
|
+
pwd = prompt_password()
|
|
55
|
+
print(get_secret(args.path, pwd, args.key))
|
|
56
|
+
|
|
57
|
+
elif args.command == "list":
|
|
58
|
+
pwd = prompt_password()
|
|
59
|
+
for key in list_secrets(args.path, pwd):
|
|
60
|
+
print(key)
|
|
61
|
+
|
|
62
|
+
elif args.command == "delete":
|
|
63
|
+
pwd = prompt_password()
|
|
64
|
+
delete_secret(args.path, pwd, args.key)
|
|
65
|
+
print(f"Deleted: {args.key}")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
3
|
+
from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def derive_key(password: str, salt: bytes) -> bytes:
|
|
7
|
+
kdf = Argon2id(
|
|
8
|
+
salt=salt,
|
|
9
|
+
length=32,
|
|
10
|
+
iterations=3,
|
|
11
|
+
lanes=4,
|
|
12
|
+
memory_cost=65536,
|
|
13
|
+
)
|
|
14
|
+
return kdf.derive(password.encode("utf-8"))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def encrypt_bytes(data: bytes, password: str) -> dict:
|
|
18
|
+
salt = os.urandom(16)
|
|
19
|
+
nonce = os.urandom(12)
|
|
20
|
+
key = derive_key(password, salt)
|
|
21
|
+
aesgcm = AESGCM(key)
|
|
22
|
+
ciphertext = aesgcm.encrypt(nonce, data, None)
|
|
23
|
+
return {
|
|
24
|
+
"salt": salt,
|
|
25
|
+
"nonce": nonce,
|
|
26
|
+
"ciphertext": ciphertext,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def decrypt_bytes(salt: bytes, nonce: bytes, ciphertext: bytes, password: str) -> bytes:
|
|
31
|
+
key = derive_key(password, salt)
|
|
32
|
+
aesgcm = AESGCM(key)
|
|
33
|
+
return aesgcm.decrypt(nonce, ciphertext, None)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import base64
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from .crypto import encrypt_bytes, decrypt_bytes
|
|
6
|
+
from .exceptions import VaultDecryptionError, VaultFormatError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
MAGIC = "GBK1"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _b64e(data: bytes) -> str:
|
|
13
|
+
return base64.b64encode(data).decode("utf-8")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _b64d(data: str) -> bytes:
|
|
17
|
+
return base64.b64decode(data.encode("utf-8"))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def create_vault(path: str, password: str) -> None:
|
|
21
|
+
payload = {}
|
|
22
|
+
save_vault(path, payload, password)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def load_vault(path: str, password: str) -> dict:
|
|
26
|
+
p = Path(path)
|
|
27
|
+
raw = json.loads(p.read_text(encoding="utf-8"))
|
|
28
|
+
|
|
29
|
+
if raw.get("magic") != MAGIC:
|
|
30
|
+
raise VaultFormatError("Invalid vault file format.")
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
plaintext = decrypt_bytes(
|
|
34
|
+
_b64d(raw["salt"]),
|
|
35
|
+
_b64d(raw["nonce"]),
|
|
36
|
+
_b64d(raw["ciphertext"]),
|
|
37
|
+
password,
|
|
38
|
+
)
|
|
39
|
+
except Exception as e:
|
|
40
|
+
raise VaultDecryptionError("Unable to decrypt vault. Wrong password or corrupted file.") from e
|
|
41
|
+
|
|
42
|
+
return json.loads(plaintext.decode("utf-8"))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def save_vault(path: str, data: dict, password: str) -> None:
|
|
46
|
+
plaintext = json.dumps(data, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
|
|
47
|
+
encrypted = encrypt_bytes(plaintext, password)
|
|
48
|
+
|
|
49
|
+
payload = {
|
|
50
|
+
"magic": MAGIC,
|
|
51
|
+
"version": 1,
|
|
52
|
+
"salt": _b64e(encrypted["salt"]),
|
|
53
|
+
"nonce": _b64e(encrypted["nonce"]),
|
|
54
|
+
"ciphertext": _b64e(encrypted["ciphertext"]),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
Path(path).write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def set_secret(path: str, password: str, key: str, value: str) -> None:
|
|
61
|
+
data = load_vault(path, password)
|
|
62
|
+
data[key] = value
|
|
63
|
+
save_vault(path, data, password)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_secret(path: str, password: str, key: str) -> str:
|
|
67
|
+
data = load_vault(path, password)
|
|
68
|
+
return data[key]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def delete_secret(path: str, password: str, key: str) -> None:
|
|
72
|
+
data = load_vault(path, password)
|
|
73
|
+
data.pop(key, None)
|
|
74
|
+
save_vault(path, data, password)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def list_secrets(path: str, password: str) -> list[str]:
|
|
78
|
+
data = load_vault(path, password)
|
|
79
|
+
return sorted(data.keys())
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gbkomi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Secure encrypted token storage for Python
|
|
5
|
+
Author-email: Your Name <qqqwwweeeshah@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: cryptography>=42.0.0
|
|
10
|
+
Requires-Dist: argon2-cffi>=23.1.0
|
|
11
|
+
|
|
12
|
+
# gbkomi
|
|
13
|
+
|
|
14
|
+
Secure encrypted token storage for Python using Argon2id + AES-256-GCM.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- Store secrets in encrypted `.gbkomi` files
|
|
19
|
+
- Password-based encryption
|
|
20
|
+
- CLI for managing secrets
|
|
21
|
+
- Authenticated encryption with AES-GCM
|
|
22
|
+
- Strong key derivation with Argon2id
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
```bash
|
|
26
|
+
pip install gbkomi
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/gbkomi/__init__.py
|
|
4
|
+
src/gbkomi/cli.py
|
|
5
|
+
src/gbkomi/crypto.py
|
|
6
|
+
src/gbkomi/exceptions.py
|
|
7
|
+
src/gbkomi/vault.py
|
|
8
|
+
src/gbkomi.egg-info/PKG-INFO
|
|
9
|
+
src/gbkomi.egg-info/SOURCES.txt
|
|
10
|
+
src/gbkomi.egg-info/dependency_links.txt
|
|
11
|
+
src/gbkomi.egg-info/entry_points.txt
|
|
12
|
+
src/gbkomi.egg-info/requires.txt
|
|
13
|
+
src/gbkomi.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gbkomi
|