nexvault 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.
- nexvault-1.0.0/PKG-INFO +82 -0
- nexvault-1.0.0/README.md +70 -0
- nexvault-1.0.0/pyproject.toml +19 -0
- nexvault-1.0.0/setup.cfg +4 -0
- nexvault-1.0.0/src/nexvault/__init__.py +0 -0
- nexvault-1.0.0/src/nexvault/commands/fromEnv.py +90 -0
- nexvault-1.0.0/src/nexvault/commands/get.py +55 -0
- nexvault-1.0.0/src/nexvault/commands/list.py +43 -0
- nexvault-1.0.0/src/nexvault/commands/run.py +44 -0
- nexvault-1.0.0/src/nexvault/commands/set.py +113 -0
- nexvault-1.0.0/src/nexvault/commands/toEnv.py +50 -0
- nexvault-1.0.0/src/nexvault/helpers/json.py +94 -0
- nexvault-1.0.0/src/nexvault/helpers/passwords.py +38 -0
- nexvault-1.0.0/src/nexvault/initEG.py +105 -0
- nexvault-1.0.0/src/nexvault/main.py +85 -0
- nexvault-1.0.0/src/nexvault.egg-info/PKG-INFO +82 -0
- nexvault-1.0.0/src/nexvault.egg-info/SOURCES.txt +19 -0
- nexvault-1.0.0/src/nexvault.egg-info/dependency_links.txt +1 -0
- nexvault-1.0.0/src/nexvault.egg-info/entry_points.txt +2 -0
- nexvault-1.0.0/src/nexvault.egg-info/requires.txt +5 -0
- nexvault-1.0.0/src/nexvault.egg-info/top_level.txt +1 -0
nexvault-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nexvault
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: An encrypted environment variable manager for developers and teams.
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: argon2-cffi>=25.1.0
|
|
8
|
+
Requires-Dist: cryptography>=48.0.1
|
|
9
|
+
Requires-Dist: pyperclip>=1.11.0
|
|
10
|
+
Requires-Dist: rich>=15.0.0
|
|
11
|
+
Requires-Dist: typer>=0.26.7
|
|
12
|
+
|
|
13
|
+
# NexVault
|
|
14
|
+
|
|
15
|
+
NexVault is a secure command-line tool for storing, managing, and exporting environment variables using password-based encryption.
|
|
16
|
+
|
|
17
|
+
Instead of keeping secrets in plaintext `.env` files, NexVault stores them in an encrypted vault that can be exported whenever needed.
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- Password-protected encrypted storage
|
|
22
|
+
- Store and retrieve secrets securely
|
|
23
|
+
- List stored keys
|
|
24
|
+
- Export secrets to `.env`
|
|
25
|
+
- Import existing `.env` files
|
|
26
|
+
- Clipboard support for secret retrieval
|
|
27
|
+
- Built with modern cryptography
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install nexvault
|
|
33
|
+
````
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
Initialize a vault:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
nexvault init
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Store a secret:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
nexvault set API_KEY your-secret
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Retrieve it:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
nexvault get API_KEY
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Copy directly to clipboard:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
nexvault get API_KEY --copy
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Export to a `.env` file:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
nexvault toenv
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Import an existing `.env`:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
nexvault fromenv
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Security
|
|
74
|
+
|
|
75
|
+
NexVault uses password-derived encryption keys and authenticated encryption to protect stored secrets.
|
|
76
|
+
|
|
77
|
+
Your plaintext secrets are never stored unless you explicitly export them.
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
Apache License 2.0
|
|
82
|
+
|
nexvault-1.0.0/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# NexVault
|
|
2
|
+
|
|
3
|
+
NexVault is a secure command-line tool for storing, managing, and exporting environment variables using password-based encryption.
|
|
4
|
+
|
|
5
|
+
Instead of keeping secrets in plaintext `.env` files, NexVault stores them in an encrypted vault that can be exported whenever needed.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Password-protected encrypted storage
|
|
10
|
+
- Store and retrieve secrets securely
|
|
11
|
+
- List stored keys
|
|
12
|
+
- Export secrets to `.env`
|
|
13
|
+
- Import existing `.env` files
|
|
14
|
+
- Clipboard support for secret retrieval
|
|
15
|
+
- Built with modern cryptography
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install nexvault
|
|
21
|
+
````
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
Initialize a vault:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
nexvault init
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Store a secret:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
nexvault set API_KEY your-secret
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Retrieve it:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
nexvault get API_KEY
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Copy directly to clipboard:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
nexvault get API_KEY --copy
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Export to a `.env` file:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
nexvault toenv
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Import an existing `.env`:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
nexvault fromenv
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Security
|
|
62
|
+
|
|
63
|
+
NexVault uses password-derived encryption keys and authenticated encryption to protect stored secrets.
|
|
64
|
+
|
|
65
|
+
Your plaintext secrets are never stored unless you explicitly export them.
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
Apache License 2.0
|
|
70
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "nexvault"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "An encrypted environment variable manager for developers and teams."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"argon2-cffi>=25.1.0",
|
|
9
|
+
"cryptography>=48.0.1",
|
|
10
|
+
"pyperclip>=1.11.0",
|
|
11
|
+
"rich>=15.0.0",
|
|
12
|
+
"typer>=0.26.7",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
nexvault = "nexvault.main:app"
|
|
17
|
+
|
|
18
|
+
[tool.uv]
|
|
19
|
+
package = true
|
nexvault-1.0.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import os, json, shutil, sys, re
|
|
2
|
+
import pyperclip
|
|
3
|
+
from rich import print
|
|
4
|
+
from rich.prompt import Prompt, Confirm
|
|
5
|
+
|
|
6
|
+
import nexvault.helpers.passwords as passwords
|
|
7
|
+
import nexvault.helpers.json as jsonMethods
|
|
8
|
+
import nexvault.commands.set as setKey
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def fromENV():
|
|
12
|
+
if not os.path.exists(".env"):
|
|
13
|
+
print("[bold red]\n.env file doesn't exists.[/bold red]\n")
|
|
14
|
+
sys.exit(1)
|
|
15
|
+
|
|
16
|
+
while True:
|
|
17
|
+
password = Prompt.ask("Enter Password ", password=True)
|
|
18
|
+
if password == "":
|
|
19
|
+
print("[bold red]Password can't be left blank.[/ bold red]")
|
|
20
|
+
continue
|
|
21
|
+
|
|
22
|
+
else:
|
|
23
|
+
verification = passwords.verify_password(password)
|
|
24
|
+
if verification == "Incorrect":
|
|
25
|
+
print("[bold red]Incorrect Password, try again.[/ bold red]")
|
|
26
|
+
continue
|
|
27
|
+
break
|
|
28
|
+
|
|
29
|
+
break
|
|
30
|
+
try:
|
|
31
|
+
with open(".nexvault/key.enc", "rb") as f:
|
|
32
|
+
data = f.read()
|
|
33
|
+
data = jsonMethods.decrypt_json(data, password)
|
|
34
|
+
|
|
35
|
+
with open(".env", "r") as f:
|
|
36
|
+
envs = f.readlines()
|
|
37
|
+
|
|
38
|
+
for el in envs:
|
|
39
|
+
el = el.strip()
|
|
40
|
+
match = re.match(r"^([^=\s]+)=(.*)$", el)
|
|
41
|
+
if (
|
|
42
|
+
not el
|
|
43
|
+
or el.startswith("#")
|
|
44
|
+
or el.startswith("=")
|
|
45
|
+
or "=" not in el
|
|
46
|
+
or not match
|
|
47
|
+
):
|
|
48
|
+
print(f"\n[bold red]Ignored '{el[:7]}...' , corrupted ! [/bold red]")
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
listkeyvalue = el.split("=")
|
|
52
|
+
if listkeyvalue[0] not in data:
|
|
53
|
+
data[listkeyvalue[0]] = listkeyvalue[1].strip()
|
|
54
|
+
jsonMethods.increaseKey()
|
|
55
|
+
|
|
56
|
+
else:
|
|
57
|
+
print(
|
|
58
|
+
f"[bold blue]The value for given '{listkeyvalue[0]}' already exist.[/bold blue]"
|
|
59
|
+
)
|
|
60
|
+
cmd = Confirm.ask("Do you want to update the value ")
|
|
61
|
+
if cmd:
|
|
62
|
+
data[listkeyvalue[0]] = listkeyvalue[1].strip()
|
|
63
|
+
jsonMethods.increaseKey()
|
|
64
|
+
else:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
with open(".nexvault/key.enc", "wb") as f:
|
|
68
|
+
data = jsonMethods.encrypt_json(data, password)
|
|
69
|
+
f.write(data)
|
|
70
|
+
|
|
71
|
+
print("[bold green]\nValues are added to nexvault.[/bold green]")
|
|
72
|
+
cmd = Confirm.ask("Do you wanna delete .env file (recommended) ")
|
|
73
|
+
|
|
74
|
+
if cmd:
|
|
75
|
+
os.remove(".env")
|
|
76
|
+
print("[bold green]\nOperation Successfull[/bold green]\n")
|
|
77
|
+
sys.exit(0)
|
|
78
|
+
|
|
79
|
+
else:
|
|
80
|
+
print("[bold green]\nOperation Successfull[/bold green]\n")
|
|
81
|
+
sys.exit(0)
|
|
82
|
+
|
|
83
|
+
except FileNotFoundError:
|
|
84
|
+
print("[bold red]File reading error.[/bold red]")
|
|
85
|
+
sys.exit(1)
|
|
86
|
+
|
|
87
|
+
except Exception as e:
|
|
88
|
+
print(e)
|
|
89
|
+
print("[bold red]An Error Ocuured.[/bold red]")
|
|
90
|
+
sys.exit(1)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import os, json, shutil, sys
|
|
2
|
+
import pyperclip
|
|
3
|
+
from rich import print
|
|
4
|
+
from rich.prompt import Prompt, Confirm
|
|
5
|
+
|
|
6
|
+
import nexvault.helpers.passwords as passwords
|
|
7
|
+
import nexvault.helpers.json as jsonMethods
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def getKey(key, copy=False):
|
|
11
|
+
while True:
|
|
12
|
+
password = Prompt.ask("Enter Password ", password=True)
|
|
13
|
+
if password == "":
|
|
14
|
+
print("[bold red]Password can't be left blank.[/ bold red]")
|
|
15
|
+
continue
|
|
16
|
+
|
|
17
|
+
else:
|
|
18
|
+
verification = passwords.verify_password(password)
|
|
19
|
+
if verification == "Incorrect":
|
|
20
|
+
print("[bold red]Incorrect Password, try again.[/ bold red]")
|
|
21
|
+
continue
|
|
22
|
+
break
|
|
23
|
+
|
|
24
|
+
break
|
|
25
|
+
try:
|
|
26
|
+
with open(".nexvault/key.enc", "rb") as f:
|
|
27
|
+
data = f.read()
|
|
28
|
+
data = jsonMethods.decrypt_json(data, password)
|
|
29
|
+
if key not in data:
|
|
30
|
+
print(
|
|
31
|
+
"[bold blue]key is not added[/bold blue]\nAdd it using '[bold]set[/bold]' command.\n"
|
|
32
|
+
)
|
|
33
|
+
sys.exit(0)
|
|
34
|
+
if copy:
|
|
35
|
+
pyperclip.copy(data[key])
|
|
36
|
+
print("[bold green]\nValue copied to clipboard.\n[/bold green]")
|
|
37
|
+
else:
|
|
38
|
+
print(
|
|
39
|
+
"[bold yellow]Warn ! Your secret will be shown in terminal[/bold yellow]"
|
|
40
|
+
)
|
|
41
|
+
print(
|
|
42
|
+
"Always use '[bold]nexvault get <key> -c[/bold]' to direct copy to clipboard without showing in terminal."
|
|
43
|
+
)
|
|
44
|
+
print(
|
|
45
|
+
"Use 'clear' if in macOS or linux terminal or use 'cls' if in windows."
|
|
46
|
+
)
|
|
47
|
+
print(">> " + data[key] + "\n")
|
|
48
|
+
|
|
49
|
+
except FileNotFoundError:
|
|
50
|
+
print("[bold red]File reading error.[/bold red]")
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
|
|
53
|
+
except Exception:
|
|
54
|
+
print("[bold red]An Error Ocuured.[/bold red]")
|
|
55
|
+
sys.exit(1)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import os, json, shutil, sys
|
|
2
|
+
import pyperclip
|
|
3
|
+
from rich import print
|
|
4
|
+
from rich.prompt import Prompt, Confirm
|
|
5
|
+
|
|
6
|
+
import nexvault.helpers.passwords as passwords
|
|
7
|
+
import nexvault.helpers.json as jsonMethods
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def listKey():
|
|
11
|
+
while True:
|
|
12
|
+
password = Prompt.ask("Enter Password ", password=True)
|
|
13
|
+
if password == "":
|
|
14
|
+
print("[bold red]Password can't be left blank.[/ bold red]")
|
|
15
|
+
continue
|
|
16
|
+
|
|
17
|
+
else:
|
|
18
|
+
verification = passwords.verify_password(password)
|
|
19
|
+
if verification == "Incorrect":
|
|
20
|
+
print("[bold red]Incorrect Password, try again.[/ bold red]")
|
|
21
|
+
continue
|
|
22
|
+
break
|
|
23
|
+
|
|
24
|
+
break
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
with open(".nexvault/key.enc", "rb") as f:
|
|
28
|
+
data = f.read()
|
|
29
|
+
data = jsonMethods.decrypt_json(data, password)
|
|
30
|
+
|
|
31
|
+
i = 1
|
|
32
|
+
|
|
33
|
+
for key in data:
|
|
34
|
+
print(f"{i}) {key}")
|
|
35
|
+
i += 1
|
|
36
|
+
|
|
37
|
+
except FileNotFoundError:
|
|
38
|
+
print("[bold red]File reading error.[/bold red]")
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
|
|
41
|
+
except Exception:
|
|
42
|
+
print("[bold red]An Error Ocuured.[/bold red]")
|
|
43
|
+
sys.exit(1)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import os, subprocess
|
|
2
|
+
|
|
3
|
+
from rich import print
|
|
4
|
+
from rich.prompt import Prompt, Confirm
|
|
5
|
+
|
|
6
|
+
import nexvault.helpers.passwords as passwords
|
|
7
|
+
import nexvault.helpers.json as jsonMethods
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def runEG(cmd):
|
|
11
|
+
while True:
|
|
12
|
+
password = Prompt.ask("Enter Password ", password=True)
|
|
13
|
+
if password == "":
|
|
14
|
+
print("[bold red]Password can't be left blank.[/ bold red]")
|
|
15
|
+
continue
|
|
16
|
+
|
|
17
|
+
else:
|
|
18
|
+
verification = passwords.verify_password(password)
|
|
19
|
+
if verification == "Incorrect":
|
|
20
|
+
print("[bold red]Incorrect Password, try again.[/ bold red]")
|
|
21
|
+
continue
|
|
22
|
+
break
|
|
23
|
+
|
|
24
|
+
break
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
with open(".nexvault/key.enc", "rb") as f:
|
|
28
|
+
data = f.read()
|
|
29
|
+
data = jsonMethods.decrypt_json(data, password)
|
|
30
|
+
|
|
31
|
+
print("[bold green]\nCommand will execute.[/bold green]\n")
|
|
32
|
+
|
|
33
|
+
env = os.environ.copy()
|
|
34
|
+
env.update(data)
|
|
35
|
+
|
|
36
|
+
subprocess.run(cmd, env=env)
|
|
37
|
+
|
|
38
|
+
except FileNotFoundError:
|
|
39
|
+
print("[bold red]File reading error.[/bold red]")
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
|
|
42
|
+
except Exception:
|
|
43
|
+
print("[bold red]An Error Ocuured.[/bold red]")
|
|
44
|
+
sys.exit(1)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import os, json, shutil, sys
|
|
2
|
+
from rich import print
|
|
3
|
+
from rich.prompt import Prompt, Confirm
|
|
4
|
+
|
|
5
|
+
import nexvault.helpers.passwords as passwords
|
|
6
|
+
import nexvault.helpers.json as jsonMethods
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def setKey(key, value):
|
|
10
|
+
while True:
|
|
11
|
+
password = Prompt.ask("Enter Password ", password=True)
|
|
12
|
+
if password == "":
|
|
13
|
+
print("[bold red]Password can't be left blank.[/ bold red]")
|
|
14
|
+
continue
|
|
15
|
+
|
|
16
|
+
else:
|
|
17
|
+
verification = passwords.verify_password(password)
|
|
18
|
+
if verification == "Incorrect":
|
|
19
|
+
print("[bold red]Incorrect Password, try again.[/ bold red]")
|
|
20
|
+
continue
|
|
21
|
+
break
|
|
22
|
+
|
|
23
|
+
break
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
with open(".nexvault/key.enc", "rb") as f:
|
|
27
|
+
data = f.read()
|
|
28
|
+
data = jsonMethods.decrypt_json(data, password)
|
|
29
|
+
|
|
30
|
+
if key not in data:
|
|
31
|
+
data[key] = value
|
|
32
|
+
jsonMethods.increaseKey()
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
else:
|
|
36
|
+
print(f"[bold blue]The value for given '{key}' already exist.[/bold blue]")
|
|
37
|
+
cmd = Confirm.ask("Do you want to update the value ")
|
|
38
|
+
if cmd:
|
|
39
|
+
data[key] = value
|
|
40
|
+
jsonMethods.increaseKey()
|
|
41
|
+
pass
|
|
42
|
+
else:
|
|
43
|
+
print("[bold red]Operation cancelled by user[/bold red]")
|
|
44
|
+
sys.exit(0)
|
|
45
|
+
|
|
46
|
+
with open(".nexvault/key.enc", "wb") as f:
|
|
47
|
+
data = jsonMethods.encrypt_json(data, password)
|
|
48
|
+
f.write(data)
|
|
49
|
+
|
|
50
|
+
print("[bold green]\nKey and Value added.[/bold green]\n")
|
|
51
|
+
|
|
52
|
+
except FileNotFoundError:
|
|
53
|
+
print("[bold red]File reading error.[/bold red]")
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
|
|
56
|
+
except Exception:
|
|
57
|
+
print("[bold red]An Error Ocuured.[/bold red]")
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def setKeys(keyvalues):
|
|
62
|
+
while True:
|
|
63
|
+
password = Prompt.ask("Enter Password ", password=True)
|
|
64
|
+
if password == "":
|
|
65
|
+
print("[bold red]Password can't be left blank.[/ bold red]")
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
else:
|
|
69
|
+
verification = passwords.verify_password(password)
|
|
70
|
+
if verification == "Incorrect":
|
|
71
|
+
print("[bold red]Incorrect Password, try again.[/ bold red]")
|
|
72
|
+
continue
|
|
73
|
+
break
|
|
74
|
+
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
with open(".nexvault/key.enc", "rb") as f:
|
|
79
|
+
data = f.read()
|
|
80
|
+
data = jsonMethods.decrypt_json(data, password)
|
|
81
|
+
|
|
82
|
+
listofkeyvalues = keyvalues.split("---")
|
|
83
|
+
|
|
84
|
+
for el in listofkeyvalues:
|
|
85
|
+
listkeyvalue = el.split("=")
|
|
86
|
+
if listkeyvalue[0] not in data:
|
|
87
|
+
data[listkeyvalue[0]] = listkeyvalue[1]
|
|
88
|
+
jsonMethods.increaseKey()
|
|
89
|
+
|
|
90
|
+
else:
|
|
91
|
+
print(
|
|
92
|
+
f"[bold blue]The value for given '{listkeyvalue[0]}' already exist.[/bold blue]"
|
|
93
|
+
)
|
|
94
|
+
cmd = Confirm.ask("Do you want to update the value ")
|
|
95
|
+
if cmd:
|
|
96
|
+
data[listkeyvalue[0]] = listkeyvalue[1]
|
|
97
|
+
jsonMethods.increaseKey()
|
|
98
|
+
else:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
with open(".nexvault/key.enc", "wb") as f:
|
|
102
|
+
data = jsonMethods.encrypt_json(data, password)
|
|
103
|
+
f.write(data)
|
|
104
|
+
|
|
105
|
+
print("[bold green]\nKeys and Values added.[/bold green]\n")
|
|
106
|
+
|
|
107
|
+
except FileNotFoundError:
|
|
108
|
+
print("[bold red]File reading error.[/bold red]")
|
|
109
|
+
sys.exit(1)
|
|
110
|
+
|
|
111
|
+
except Exception:
|
|
112
|
+
print("[bold red]An Error Ocuured.[/bold red]")
|
|
113
|
+
sys.exit(1)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import os, json, shutil, sys
|
|
2
|
+
import pyperclip
|
|
3
|
+
from rich import print
|
|
4
|
+
from rich.prompt import Prompt, Confirm
|
|
5
|
+
|
|
6
|
+
import nexvault.helpers.passwords as passwords
|
|
7
|
+
import nexvault.helpers.json as jsonMethods
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def toENV(copy=False):
|
|
11
|
+
while True:
|
|
12
|
+
password = Prompt.ask("Enter Password ", password=True)
|
|
13
|
+
if password == "":
|
|
14
|
+
print("[bold red]Password can't be left blank.[/ bold red]")
|
|
15
|
+
continue
|
|
16
|
+
|
|
17
|
+
else:
|
|
18
|
+
verification = passwords.verify_password(password)
|
|
19
|
+
if verification == "Incorrect":
|
|
20
|
+
print("[bold red]Incorrect Password, try again.[/ bold red]")
|
|
21
|
+
continue
|
|
22
|
+
break
|
|
23
|
+
|
|
24
|
+
break
|
|
25
|
+
try:
|
|
26
|
+
with open(".nexvault/key.enc", "rb") as f:
|
|
27
|
+
data = f.read()
|
|
28
|
+
data = jsonMethods.decrypt_json(data, password)
|
|
29
|
+
|
|
30
|
+
env = ""
|
|
31
|
+
for key in data:
|
|
32
|
+
env += f"{key}={data[key]}\n"
|
|
33
|
+
|
|
34
|
+
if copy:
|
|
35
|
+
pyperclip.copy(env)
|
|
36
|
+
print("[bold green]\nAll env copied to clipboard.\n[/bold green]")
|
|
37
|
+
|
|
38
|
+
else:
|
|
39
|
+
with open(".env", "w") as f:
|
|
40
|
+
f.write(env)
|
|
41
|
+
|
|
42
|
+
print("[bold green]\n.env file is created in root folder.\n[/bold green]")
|
|
43
|
+
|
|
44
|
+
except FileNotFoundError:
|
|
45
|
+
print("[bold red]File reading error.[/bold red]")
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
|
|
48
|
+
except Exception:
|
|
49
|
+
print("[bold red]An Error Ocuured.[/bold red]")
|
|
50
|
+
sys.exit(1)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import base64, os, json, sys
|
|
2
|
+
|
|
3
|
+
from cryptography.fernet import Fernet, InvalidToken
|
|
4
|
+
from cryptography.hazmat.primitives import hashes
|
|
5
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def getSalt():
|
|
9
|
+
try:
|
|
10
|
+
with open(".nexvault/nexvault.lock", "r") as f:
|
|
11
|
+
data = json.load(f)
|
|
12
|
+
|
|
13
|
+
salt = data["salt"]
|
|
14
|
+
salt = bytes.fromhex(salt)
|
|
15
|
+
return salt
|
|
16
|
+
|
|
17
|
+
except FileNotFoundError:
|
|
18
|
+
print("[bold red]File reading error.[/bold red]")
|
|
19
|
+
sys.exit(1)
|
|
20
|
+
|
|
21
|
+
except Exception as e:
|
|
22
|
+
print("[bold red]An Error Ocurred.[/bold red]")
|
|
23
|
+
sys.exit(1)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def increaseKey(n=1):
|
|
27
|
+
try:
|
|
28
|
+
with open(".nexvault/nexvault.lock", "r") as f:
|
|
29
|
+
data = json.load(f)
|
|
30
|
+
|
|
31
|
+
data["keys"] += n
|
|
32
|
+
|
|
33
|
+
with open(".nexvault/nexvault.lock", "w") as f:
|
|
34
|
+
json.dump(data, f, indent=2)
|
|
35
|
+
|
|
36
|
+
except FileNotFoundError:
|
|
37
|
+
print("[bold red]File reading error.[/bold red]")
|
|
38
|
+
sys.exit(1)
|
|
39
|
+
|
|
40
|
+
except Exception:
|
|
41
|
+
print("[bold red]An Error Ocurred.[/bold red]")
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def encrypt_json(data, password):
|
|
46
|
+
try:
|
|
47
|
+
salt = getSalt()
|
|
48
|
+
kdf = PBKDF2HMAC(
|
|
49
|
+
algorithm=hashes.SHA256(), length=32, salt=salt, iterations=480_000
|
|
50
|
+
)
|
|
51
|
+
key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
|
|
52
|
+
return Fernet(key).encrypt(json.dumps(data).encode())
|
|
53
|
+
|
|
54
|
+
except TypeError:
|
|
55
|
+
print("[bold red]Invalid data provided for encryption.[/bold red]")
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
except ValueError:
|
|
59
|
+
print("[bold red]Invalid encryption parameters.[/bold red]")
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
|
|
62
|
+
except Exception:
|
|
63
|
+
print("[bold red]An Error Ocurred.[/bold red]")
|
|
64
|
+
sys.exit(1)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def decrypt_json(data, password):
|
|
68
|
+
try:
|
|
69
|
+
salt = getSalt()
|
|
70
|
+
kdf = PBKDF2HMAC(
|
|
71
|
+
algorithm=hashes.SHA256(), length=32, salt=salt, iterations=480_000
|
|
72
|
+
)
|
|
73
|
+
key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
|
|
74
|
+
return json.loads(Fernet(key).decrypt(data).decode())
|
|
75
|
+
|
|
76
|
+
except InvalidToken:
|
|
77
|
+
print("[bold red]Invalid password or corrupted encrypted data.[/bold red]")
|
|
78
|
+
sys.exit(1)
|
|
79
|
+
|
|
80
|
+
except json.JSONDecodeError:
|
|
81
|
+
print("[bold red]Failed to decode decrypted JSON data.[/bold red]")
|
|
82
|
+
sys.exit(1)
|
|
83
|
+
|
|
84
|
+
except TypeError:
|
|
85
|
+
print("[bold red]Invalid encrypted data provided.[/bold red]")
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
|
|
88
|
+
except ValueError:
|
|
89
|
+
print("[bold red]Invalid decryption parameters.[/bold red]")
|
|
90
|
+
sys.exit(1)
|
|
91
|
+
|
|
92
|
+
except Exception:
|
|
93
|
+
print("[bold red]An Error Ocurred.[/bold red]")
|
|
94
|
+
sys.exit(1)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from argon2 import PasswordHasher
|
|
2
|
+
from argon2.exceptions import VerifyMismatchError
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
ph = PasswordHasher()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def hash_password(password, file=".nexvault/.enc"):
|
|
9
|
+
try:
|
|
10
|
+
with open(file, "w") as secret:
|
|
11
|
+
secret.write(ph.hash(password) + "\n")
|
|
12
|
+
|
|
13
|
+
except FileNotFoundError:
|
|
14
|
+
print("[bold red]File reading error.[/bold red]\n")
|
|
15
|
+
sys.exit(1)
|
|
16
|
+
|
|
17
|
+
except Exception:
|
|
18
|
+
print("[bold red]An Error Ocurred.[/ bold red]\n")
|
|
19
|
+
sys.exit(1)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def verify_password(password, file=".nexvault/.enc"):
|
|
23
|
+
try:
|
|
24
|
+
with open(file, "r") as secret:
|
|
25
|
+
secretdata = secret.read()
|
|
26
|
+
try:
|
|
27
|
+
ph.verify(secretdata.strip(), password)
|
|
28
|
+
return "Correct"
|
|
29
|
+
except VerifyMismatchError:
|
|
30
|
+
return "Incorrect"
|
|
31
|
+
|
|
32
|
+
except FileNotFoundError:
|
|
33
|
+
print("[bold red]File reading error.[/bold red]\n")
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
|
|
36
|
+
except Exception:
|
|
37
|
+
print("[bold red]An Error Ocurred.[/bold red]\n")
|
|
38
|
+
sys.exit(1)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import os, json, shutil, sys
|
|
2
|
+
from rich import print
|
|
3
|
+
from rich.prompt import Prompt, Confirm
|
|
4
|
+
|
|
5
|
+
import nexvault.helpers.passwords as passwords
|
|
6
|
+
import nexvault.helpers.json as jsonMethods
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def initialise():
|
|
10
|
+
try:
|
|
11
|
+
if os.path.exists(".nexvault"):
|
|
12
|
+
print("[bold blue]nexvault is already initialised.[/bold blue]")
|
|
13
|
+
print(
|
|
14
|
+
"[bold red]Recreation will remove all your previous data and secrets permanently[/bold red]."
|
|
15
|
+
)
|
|
16
|
+
cmd = Confirm.ask("Do you want to recreate ")
|
|
17
|
+
if cmd:
|
|
18
|
+
shutil.rmtree(".nexvault")
|
|
19
|
+
create()
|
|
20
|
+
|
|
21
|
+
else:
|
|
22
|
+
print("[bold red]Operation Cancelled by User.[/bold red]")
|
|
23
|
+
sys.exit()
|
|
24
|
+
|
|
25
|
+
else:
|
|
26
|
+
create()
|
|
27
|
+
|
|
28
|
+
except Exception as e:
|
|
29
|
+
print(e)
|
|
30
|
+
print("[bold red]An Error Ocuured.[/bold red]\n")
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def create():
|
|
35
|
+
os.mkdir(".nexvault")
|
|
36
|
+
updateGit()
|
|
37
|
+
open(".nexvault/.enc", "w").close()
|
|
38
|
+
open(".nexvault/nexvault.lock", "w").close()
|
|
39
|
+
open(".nexvault/key.enc", "w").close()
|
|
40
|
+
createConifg()
|
|
41
|
+
askPassword()
|
|
42
|
+
print(
|
|
43
|
+
f"\n[bold green]nexvault initialised for '{os.path.basename(os.getcwd())}'[/bold green]\n"
|
|
44
|
+
)
|
|
45
|
+
sys.exit(0)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def createConifg():
|
|
49
|
+
try:
|
|
50
|
+
vault_name = Prompt.ask(
|
|
51
|
+
"Enter your vault name ", default=os.path.basename(os.getcwd())
|
|
52
|
+
)
|
|
53
|
+
data = {
|
|
54
|
+
"vault": vault_name,
|
|
55
|
+
"version": "0.1.0",
|
|
56
|
+
"author": "",
|
|
57
|
+
"description": "",
|
|
58
|
+
"project": os.path.basename(os.getcwd()),
|
|
59
|
+
"salt": os.urandom(16).hex(),
|
|
60
|
+
"keys": 0,
|
|
61
|
+
}
|
|
62
|
+
with open(".nexvault/nexvault.lock", "w") as config:
|
|
63
|
+
json.dump(data, config, indent=2)
|
|
64
|
+
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
except FileNotFoundError:
|
|
68
|
+
print("[bold red]File reading error.[/ bold red]\n")
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
|
|
71
|
+
except Exception:
|
|
72
|
+
print("[bold red]An Error Ocuured.[/ bold red]\n")
|
|
73
|
+
sys.exit(1)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def askPassword():
|
|
77
|
+
while True:
|
|
78
|
+
password = Prompt.ask("Create a password ", password=True)
|
|
79
|
+
if password == "":
|
|
80
|
+
print("[bold red]Password can't be left blank.[/ bold red]")
|
|
81
|
+
continue
|
|
82
|
+
break
|
|
83
|
+
|
|
84
|
+
while True:
|
|
85
|
+
confirm = Prompt.ask("Confirm password ", password=True)
|
|
86
|
+
if confirm != password:
|
|
87
|
+
print("[bold red]Password doesn't match, try again.[/ bold red]")
|
|
88
|
+
continue
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
passwords.hash_password(password)
|
|
92
|
+
with open(".nexvault/key.enc", "wb") as f:
|
|
93
|
+
f.write(jsonMethods.encrypt_json({}, password))
|
|
94
|
+
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def updateGit():
|
|
99
|
+
if os.path.exists(".gitignore"):
|
|
100
|
+
with open(".gitignore", "a") as f:
|
|
101
|
+
f.write("\n#nexvault\n.nexvault/\n.nexvault\n*.env\n*.enc\n*.lock\n")
|
|
102
|
+
|
|
103
|
+
else:
|
|
104
|
+
with open(".gitignore", "w") as f:
|
|
105
|
+
f.write("\n#nexvault\n.nexvault/\n.nexvault\n*.env\n*.enc\n*.lock\n")
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
import nexvault.initEG as initEG
|
|
5
|
+
import nexvault.commands.get as getKey
|
|
6
|
+
import nexvault.commands.set as setKey
|
|
7
|
+
import nexvault.commands.list as listKey
|
|
8
|
+
import nexvault.commands.toEnv as changetoenv
|
|
9
|
+
import nexvault.commands.fromEnv as changefromenv
|
|
10
|
+
import nexvault.commands.run as runEG
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(
|
|
13
|
+
help="NexVault is a secure command-line tool for storing, managing, and exporting encrypted environment variables."
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.command(help="Initialize a new NexVault vault in the current project.")
|
|
18
|
+
def init():
|
|
19
|
+
initEG.initialise()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@app.command(help="Store a single key-value pair securely in the vault.")
|
|
23
|
+
def set(
|
|
24
|
+
key: str = typer.Argument(..., help="The environment variable name."),
|
|
25
|
+
value: str = typer.Argument(..., help="The value to associate with the key."),
|
|
26
|
+
):
|
|
27
|
+
setKey.setKey(key, value)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.command(help="Store multiple key-value pairs in a single operation.")
|
|
31
|
+
def setmul(
|
|
32
|
+
keys: List[str] = typer.Argument(
|
|
33
|
+
..., help="Multiple KEY=VALUE pairs to add to the vault."
|
|
34
|
+
),
|
|
35
|
+
):
|
|
36
|
+
string = "---".join(keys)
|
|
37
|
+
setKey.setKeys(string)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@app.command(help="Retrieve the value associated with a stored key.")
|
|
41
|
+
def get(
|
|
42
|
+
key: str = typer.Argument(..., help="The key to retrieve."),
|
|
43
|
+
copy: bool = typer.Option(
|
|
44
|
+
False,
|
|
45
|
+
"--copy",
|
|
46
|
+
"-c",
|
|
47
|
+
help="Copy the retrieved value to the clipboard instead of displaying it.",
|
|
48
|
+
),
|
|
49
|
+
):
|
|
50
|
+
getKey.getKey(key, copy)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.command(help="Display all keys currently stored in the vault.")
|
|
54
|
+
def list():
|
|
55
|
+
listKey.listKey()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@app.command(help="Export all stored secrets to a .env file.")
|
|
59
|
+
def toenv(
|
|
60
|
+
copy: bool = typer.Option(
|
|
61
|
+
False,
|
|
62
|
+
"--copy",
|
|
63
|
+
"-c",
|
|
64
|
+
help="Copy the generated .env content to the clipboard instead of creating a file.",
|
|
65
|
+
),
|
|
66
|
+
):
|
|
67
|
+
changetoenv.toENV(copy)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@app.command(help="Import environment variables from a .env file into the vault.")
|
|
71
|
+
def fromenv():
|
|
72
|
+
changefromenv.fromENV()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@app.command(help="Run a command with NexVault environment variables injected.")
|
|
76
|
+
def run(
|
|
77
|
+
cmd: List[str] = typer.Argument(
|
|
78
|
+
..., help="The command and its arguments to execute."
|
|
79
|
+
),
|
|
80
|
+
):
|
|
81
|
+
runEG.runEG(cmd)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
app()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nexvault
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: An encrypted environment variable manager for developers and teams.
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: argon2-cffi>=25.1.0
|
|
8
|
+
Requires-Dist: cryptography>=48.0.1
|
|
9
|
+
Requires-Dist: pyperclip>=1.11.0
|
|
10
|
+
Requires-Dist: rich>=15.0.0
|
|
11
|
+
Requires-Dist: typer>=0.26.7
|
|
12
|
+
|
|
13
|
+
# NexVault
|
|
14
|
+
|
|
15
|
+
NexVault is a secure command-line tool for storing, managing, and exporting environment variables using password-based encryption.
|
|
16
|
+
|
|
17
|
+
Instead of keeping secrets in plaintext `.env` files, NexVault stores them in an encrypted vault that can be exported whenever needed.
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- Password-protected encrypted storage
|
|
22
|
+
- Store and retrieve secrets securely
|
|
23
|
+
- List stored keys
|
|
24
|
+
- Export secrets to `.env`
|
|
25
|
+
- Import existing `.env` files
|
|
26
|
+
- Clipboard support for secret retrieval
|
|
27
|
+
- Built with modern cryptography
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install nexvault
|
|
33
|
+
````
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
Initialize a vault:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
nexvault init
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Store a secret:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
nexvault set API_KEY your-secret
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Retrieve it:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
nexvault get API_KEY
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Copy directly to clipboard:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
nexvault get API_KEY --copy
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Export to a `.env` file:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
nexvault toenv
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Import an existing `.env`:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
nexvault fromenv
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Security
|
|
74
|
+
|
|
75
|
+
NexVault uses password-derived encryption keys and authenticated encryption to protect stored secrets.
|
|
76
|
+
|
|
77
|
+
Your plaintext secrets are never stored unless you explicitly export them.
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
Apache License 2.0
|
|
82
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/nexvault/__init__.py
|
|
4
|
+
src/nexvault/initEG.py
|
|
5
|
+
src/nexvault/main.py
|
|
6
|
+
src/nexvault.egg-info/PKG-INFO
|
|
7
|
+
src/nexvault.egg-info/SOURCES.txt
|
|
8
|
+
src/nexvault.egg-info/dependency_links.txt
|
|
9
|
+
src/nexvault.egg-info/entry_points.txt
|
|
10
|
+
src/nexvault.egg-info/requires.txt
|
|
11
|
+
src/nexvault.egg-info/top_level.txt
|
|
12
|
+
src/nexvault/commands/fromEnv.py
|
|
13
|
+
src/nexvault/commands/get.py
|
|
14
|
+
src/nexvault/commands/list.py
|
|
15
|
+
src/nexvault/commands/run.py
|
|
16
|
+
src/nexvault/commands/set.py
|
|
17
|
+
src/nexvault/commands/toEnv.py
|
|
18
|
+
src/nexvault/helpers/json.py
|
|
19
|
+
src/nexvault/helpers/passwords.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nexvault
|