jkey 0.1.2__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.
jkey-0.1.2/PKG-INFO ADDED
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: jkey
3
+ Version: 0.1.2
4
+ Summary: Python library for password management and TOTP verification.
5
+ Author-email: jiaoyuan <imjiaoyuan@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/imjiaoyuan/jkey
8
+ Project-URL: Repository, https://github.com/imjiaoyuan/jkey
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: opencv-python-headless>=4.9.0
12
+
13
+ # jkey
14
+
15
+ Python library for password management and TOTP verification.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ uv tool install jkey
21
+ ```
22
+
23
+ Or run without installing:
24
+
25
+ ```bash
26
+ uv run jkey --help
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ```bash
32
+ # Initialize vault (set master password)
33
+ jkey pv init
34
+
35
+ # Add a 2FA account
36
+ jkey 2fa add github JBSWY3DPEHPK3PXP
37
+
38
+ # Get current TOTP code
39
+ jkey 2fa get github
40
+
41
+ # Import from QR code image
42
+ jkey 2fa qr ./github.jpg
43
+
44
+ # Generate a random password
45
+ jkey pm gen -L 24
46
+
47
+ # Store a password
48
+ jkey pm add my-site
49
+
50
+ # List all stored passwords
51
+ jkey pm ls
52
+
53
+ # Encrypt/decrypt any file
54
+ jkey pv encrypt secret.pdf
55
+ jkey pv decrypt secret.pdf.jkey -o secret.pdf
56
+ ```
57
+
58
+ ## Commands
59
+
60
+ | Command | Description |
61
+ |---------|-------------|
62
+ | `jkey 2fa ls [keyword]` | List TOTP accounts and codes |
63
+ | `jkey 2fa get <account>` | Show TOTP code for an account |
64
+ | `jkey 2fa add <name> <secret>` | Add a TOTP account |
65
+ | `jkey 2fa qr <image>` | Import from QR code image |
66
+ | `jkey 2fa rm <account>` | Remove a TOTP account |
67
+ | `jkey pm gen [-L N]` | Generate a random password |
68
+ | `jkey pm ls [keyword]` | List stored passwords |
69
+ | `jkey pm get <name>` | Show a stored password |
70
+ | `jkey pm add <name>` | Store a password (prompts for input) |
71
+ | `jkey pm rm <name>` | Delete a stored password |
72
+ | `jkey pm import <csv>` | Import passwords from CSV (name,password) |
73
+ | `jkey pv init` | Initialize the encrypted vault |
74
+ | `jkey pv unlock` | Unlock the vault |
75
+ | `jkey pv lock` | Lock the vault |
76
+ | `jkey pv set-pw` | Change master password |
77
+ | `jkey pv encrypt <file>` | Encrypt a file |
78
+ | `jkey pv decrypt <file>` | Decrypt a `.jkey` file |
79
+ | `jkey pv export totp` | Export TOTP secrets (re-enters master password) |
80
+ | `jkey pv export passwords` | Export passwords as CSV |
81
+ | `jkey pv export recovery` | Export recovery codes |
82
+ | `jkey pv export qr -o <dir>` | Export QR code images |
83
+ | `jkey pv export all -o <dir>` | Export everything |
84
+
85
+ Set `JKEY_PASS` environment variable to skip the password prompt.
86
+
87
+ ## How It Works
88
+
89
+ Data is encrypted with AES-256-CBC + HMAC-SHA256 and stored in `~/.config/jkey/`:
90
+
91
+ ```
92
+ ~/.config/jkey/
93
+ ├── totp.jkey # Encrypted TOTP secrets
94
+ ├── passwords.jkey # Encrypted passwords
95
+ ├── recovery.jkey # Encrypted recovery codes
96
+ └── qr/ # Encrypted QR images
97
+ ```
98
+
99
+ Back up `~/.config/jkey/` to migrate to another machine.
100
+
101
+ ## Dependencies
102
+
103
+ - `opencv-python-headless` — QR code scanning
104
+
105
+ Pure Python, no OpenSSL or libsodium required.
jkey-0.1.2/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # jkey
2
+
3
+ Python library for password management and TOTP verification.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ uv tool install jkey
9
+ ```
10
+
11
+ Or run without installing:
12
+
13
+ ```bash
14
+ uv run jkey --help
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ # Initialize vault (set master password)
21
+ jkey pv init
22
+
23
+ # Add a 2FA account
24
+ jkey 2fa add github JBSWY3DPEHPK3PXP
25
+
26
+ # Get current TOTP code
27
+ jkey 2fa get github
28
+
29
+ # Import from QR code image
30
+ jkey 2fa qr ./github.jpg
31
+
32
+ # Generate a random password
33
+ jkey pm gen -L 24
34
+
35
+ # Store a password
36
+ jkey pm add my-site
37
+
38
+ # List all stored passwords
39
+ jkey pm ls
40
+
41
+ # Encrypt/decrypt any file
42
+ jkey pv encrypt secret.pdf
43
+ jkey pv decrypt secret.pdf.jkey -o secret.pdf
44
+ ```
45
+
46
+ ## Commands
47
+
48
+ | Command | Description |
49
+ |---------|-------------|
50
+ | `jkey 2fa ls [keyword]` | List TOTP accounts and codes |
51
+ | `jkey 2fa get <account>` | Show TOTP code for an account |
52
+ | `jkey 2fa add <name> <secret>` | Add a TOTP account |
53
+ | `jkey 2fa qr <image>` | Import from QR code image |
54
+ | `jkey 2fa rm <account>` | Remove a TOTP account |
55
+ | `jkey pm gen [-L N]` | Generate a random password |
56
+ | `jkey pm ls [keyword]` | List stored passwords |
57
+ | `jkey pm get <name>` | Show a stored password |
58
+ | `jkey pm add <name>` | Store a password (prompts for input) |
59
+ | `jkey pm rm <name>` | Delete a stored password |
60
+ | `jkey pm import <csv>` | Import passwords from CSV (name,password) |
61
+ | `jkey pv init` | Initialize the encrypted vault |
62
+ | `jkey pv unlock` | Unlock the vault |
63
+ | `jkey pv lock` | Lock the vault |
64
+ | `jkey pv set-pw` | Change master password |
65
+ | `jkey pv encrypt <file>` | Encrypt a file |
66
+ | `jkey pv decrypt <file>` | Decrypt a `.jkey` file |
67
+ | `jkey pv export totp` | Export TOTP secrets (re-enters master password) |
68
+ | `jkey pv export passwords` | Export passwords as CSV |
69
+ | `jkey pv export recovery` | Export recovery codes |
70
+ | `jkey pv export qr -o <dir>` | Export QR code images |
71
+ | `jkey pv export all -o <dir>` | Export everything |
72
+
73
+ Set `JKEY_PASS` environment variable to skip the password prompt.
74
+
75
+ ## How It Works
76
+
77
+ Data is encrypted with AES-256-CBC + HMAC-SHA256 and stored in `~/.config/jkey/`:
78
+
79
+ ```
80
+ ~/.config/jkey/
81
+ ├── totp.jkey # Encrypted TOTP secrets
82
+ ├── passwords.jkey # Encrypted passwords
83
+ ├── recovery.jkey # Encrypted recovery codes
84
+ └── qr/ # Encrypted QR images
85
+ ```
86
+
87
+ Back up `~/.config/jkey/` to migrate to another machine.
88
+
89
+ ## Dependencies
90
+
91
+ - `opencv-python-headless` — QR code scanning
92
+
93
+ Pure Python, no OpenSSL or libsodium required.
@@ -0,0 +1,36 @@
1
+ [project]
2
+ name = "jkey"
3
+ version = "0.1.2"
4
+ description = "Python library for password management and TOTP verification."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ authors = [{name = "jiaoyuan", email = "imjiaoyuan@gmail.com"}]
8
+ license = {text = "MIT"}
9
+ dependencies = ["opencv-python-headless>=4.9.0"]
10
+
11
+ [project.urls]
12
+ Homepage = "https://github.com/imjiaoyuan/jkey"
13
+ Repository = "https://github.com/imjiaoyuan/jkey"
14
+
15
+ [project.scripts]
16
+ jkey = "jkey.cli:main"
17
+
18
+ [build-system]
19
+ requires = ["setuptools>=64"]
20
+ build-backend = "setuptools.build_meta"
21
+
22
+ [tool.setuptools.packages.find]
23
+ where = ["src"]
24
+
25
+ [dependency-groups]
26
+ dev = ["pytest>=8", "ruff>=0.11"]
27
+
28
+ [tool.ruff]
29
+ line-length = 120
30
+ target-version = "py310"
31
+
32
+ [tool.ruff.lint]
33
+ select = ["E", "F", "W", "I"]
34
+
35
+ [tool.pytest.ini_options]
36
+ testpaths = ["tests"]
jkey-0.1.2/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,124 @@
1
+ import base64
2
+ import hashlib
3
+ import hmac
4
+ import os
5
+ import struct
6
+ import time
7
+
8
+ from jkey.pv.core import load_recovery, load_totp, save_recovery, save_totp
9
+
10
+
11
+ def _hotp(secret: bytes, counter: int, digits: int = 6) -> str:
12
+ msg = struct.pack(">Q", counter)
13
+ h = hmac.new(secret, msg, hashlib.sha1).digest()
14
+ offset = h[-1] & 0xf
15
+ truncated = struct.unpack(">I", h[offset:offset+4])[0] & 0x7fffffff
16
+ return str(truncated % (10 ** digits)).zfill(digits)
17
+
18
+
19
+ def _b32_decode(s: str) -> bytes:
20
+ s = s.upper().replace(" ", "")
21
+ remainder = len(s) % 8
22
+ if remainder:
23
+ s += "=" * (8 - remainder)
24
+ return base64.b32decode(s)
25
+
26
+
27
+ def _validate_b32_secret(s: str) -> bool:
28
+ try:
29
+ _b32_decode(s)
30
+ return True
31
+ except Exception:
32
+ return False
33
+
34
+
35
+ def totp(secret_key: str, digits: int = 6, interval: int = 30) -> str:
36
+ secret = _b32_decode(secret_key)
37
+ counter = int(time.time()) // interval
38
+ return _hotp(secret, counter, digits)
39
+
40
+
41
+ def _import_recovery_file(account: str, recovery_path: str | None):
42
+ if not recovery_path:
43
+ return
44
+ if not os.path.exists(recovery_path):
45
+ print(f"Warning: Recovery file not found: {recovery_path}")
46
+ return
47
+ try:
48
+ with open(recovery_path, "r", encoding="utf-8") as f:
49
+ codes = [line.strip() for line in f if line.strip()]
50
+ if codes:
51
+ data = load_recovery()
52
+ if data is None:
53
+ return
54
+ data[account] = codes
55
+ save_recovery(data)
56
+ print(f"Imported {len(codes)} recovery codes for {account}")
57
+ except OSError as e:
58
+ print(f"Warning: Could not read recovery file: {e}")
59
+
60
+
61
+ def list_accounts(keyword: str | None = None):
62
+ data = load_totp()
63
+ if data is None:
64
+ return
65
+ if not data:
66
+ print("No 2FA accounts found.")
67
+ return
68
+ keys = sorted(data.keys())
69
+ if keyword:
70
+ keys = [k for k in keys if keyword.lower() in k.lower()]
71
+ if not keys:
72
+ print(f"No accounts matching '{keyword}'.")
73
+ return
74
+ for acc_id in keys:
75
+ secret = data[acc_id]
76
+ try:
77
+ print(f"{acc_id}: {totp(secret)}")
78
+ except Exception as e:
79
+ print(f"Error processing '{acc_id}': {e}")
80
+
81
+
82
+ def show_code(account: str):
83
+ data = load_totp()
84
+ if data is None:
85
+ return
86
+ if account not in data:
87
+ print(f"Error: Account '{account}' not found.")
88
+ return
89
+ secret = data[account]
90
+ try:
91
+ print(f"{account}: {totp(secret)}")
92
+ except Exception as e:
93
+ print(f"Error processing '{account}': {e}")
94
+
95
+
96
+ def add_account(name: str, secret: str, recovery_path: str | None = None):
97
+ if not _validate_b32_secret(secret):
98
+ print(f"Error: Invalid base32 secret for '{name}'.")
99
+ return
100
+ data = load_totp()
101
+ if data is None:
102
+ return
103
+ data[name] = secret
104
+ save_totp(data)
105
+ print(f"Added 2FA account: {name}")
106
+ _import_recovery_file(name, recovery_path)
107
+
108
+
109
+ def remove_account(account: str):
110
+ data = load_totp()
111
+ if data is None:
112
+ return
113
+ if account not in data:
114
+ print(f"Error: Account '{account}' not found.")
115
+ return
116
+ del data[account]
117
+ save_totp(data)
118
+
119
+ rc = load_recovery()
120
+ if rc and account in rc:
121
+ del rc[account]
122
+ save_recovery(rc)
123
+
124
+ print(f"Removed 2FA account: {account}")
@@ -0,0 +1,65 @@
1
+ import importlib
2
+ import os
3
+ from urllib.parse import parse_qs, unquote, urlparse
4
+
5
+ import cv2
6
+
7
+ from jkey.pv.core import load_totp, save_qr_image, save_totp
8
+
9
+ _core = importlib.import_module("jkey.2fa.core")
10
+ _import_recovery_file = _core._import_recovery_file
11
+
12
+
13
+ def scan_and_add(image_path: str, recovery_path: str | None = None):
14
+ if not os.path.exists(image_path):
15
+ print(f"Error: File not found: {image_path}")
16
+ return
17
+
18
+ img = cv2.imread(image_path)
19
+ if img is None:
20
+ print(f"Error: Could not read image: {image_path}")
21
+ return
22
+
23
+ detector = cv2.QRCodeDetector()
24
+ data, _, _ = detector.detectAndDecode(img)
25
+ if not data:
26
+ print("Error: No QR code found in the image.")
27
+ return
28
+
29
+ parsed = urlparse(data)
30
+ if parsed.scheme != "otpauth":
31
+ print(f"Error: Not a valid otpauth:// URL: {data}")
32
+ return
33
+
34
+ params = parse_qs(parsed.query)
35
+ secret = params.get("secret", [None])[0]
36
+ if not secret:
37
+ print("Error: No secret found in QR code.")
38
+ return
39
+
40
+ path = unquote(parsed.path).lstrip("/")
41
+ issuer = params.get("issuer", [None])[0]
42
+
43
+ if issuer and path.startswith(issuer + ":"):
44
+ name = path[len(issuer) + 1:]
45
+ elif issuer and ":" in path:
46
+ name = path
47
+ else:
48
+ name = path
49
+
50
+ if not name:
51
+ name = issuer or "unknown"
52
+
53
+ data = load_totp()
54
+ if data is None:
55
+ return
56
+ data[name] = secret
57
+ save_totp(data)
58
+ print(f"Added 2FA account: {name}")
59
+
60
+ try:
61
+ save_qr_image(name, cv2.imencode(".jpg", img)[1].tobytes())
62
+ except Exception:
63
+ pass
64
+
65
+ _import_recovery_file(name, recovery_path)
File without changes
@@ -0,0 +1,4 @@
1
+ from jkey.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()