Skip to content

Commit 78e36ec

Browse files
authored
PR #29: Password Manager
Secure Password Manager using Python (CLI + GUI) Merge pull request #29 from Chaitanya6Nli/secure-password-manager
2 parents 9eb2644 + 044b167 commit 78e36ec

File tree

7 files changed

+599
-0
lines changed

7 files changed

+599
-0
lines changed

Secure_Password_Manager/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
saved_passwords.txt
2+
secret.key

Secure_Password_Manager/README

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# 🔐 Secure Password Manager (CLI + GUI)
2+
3+
This is a simple and secure Password Manager built with Python. It includes both a **Tkinter-based GUI (Graphical User Interface)** and a **CLI (Command Line Interface)** version.
4+
5+
---
6+
7+
## 📌 Features
8+
9+
* ✅ Generate strong, random passwords
10+
* ✅ Choose what characters to include (lowercase, uppercase, digits, special characters)
11+
* ✅ Real-time password strength feedback
12+
* ✅ Save passwords with labels (e.g., Gmail, Netflix)
13+
* ✅ Encode/Encrypt passwords for security
14+
* ✅ View saved passwords with automatic decryption
15+
* ✅ GUI built using Python's `tkinter` library
16+
* ✅ CLI version with command-line options using `argparse`
17+
18+
---
19+
20+
## 💡 How It Works
21+
22+
### Tkinter GUI
23+
24+
The graphical interface is built using `tkinter`, Python’s standard GUI toolkit. It provides a clean interface for generating, encrypting, and viewing passwords.
25+
26+
### Generate Password
27+
28+
1. Choose the password length.
29+
2. Select character types using checkboxes.
30+
3. Hit the **Generate Password** button.
31+
4. The password is displayed with its strength (Weak, Medium, or Strong).
32+
5. The password is then **encrypted** and saved to a file.
33+
34+
### View Saved Passwords
35+
36+
1. Click the **View Saved Passwords** button in the GUI.
37+
2. If the encryption key exists, the program decrypts and displays all saved passwords in a pop-up window.
38+
3. If the key is missing or corrupted, an error message is shown.
39+
40+
---
41+
42+
## 🛠️ Technologies Used
43+
44+
| Component | Tool/Library |
45+
|----------|--------------|
46+
| Programming Language | Python |
47+
| GUI Library | Tkinter |
48+
| Encryption | cryptography (Fernet) |
49+
| Encoding (CLI version) | base64 |
50+
| CLI Parser | argparse |
51+
| Version Control | Git |
52+
53+
---
54+
55+
## 🗃️ Project Structure
56+
57+
```bash
58+
Password_Manager/
59+
├── password_manager_gui.py # Tkinter-based GUI application
60+
├── password_manager_cli.py # CLI-based password generator
61+
├── view_passwords_cli.py # View decoded Base64 passwords (CLI)
62+
├── decrypt_passwords_cli.py # Decrypt encrypted passwords (CLI)
63+
├── saved_passwords.txt # File where encrypted passwords are saved
64+
├── secret.key # File containing Fernet encryption key
65+
├── README.md
66+
└── .gitignore
67+
```
68+
69+
---
70+
71+
## ⚠️ Security Note
72+
73+
This project uses **Fernet encryption** to protect your saved passwords.
74+
75+
Files like `secret.key` and `saved_passwords.txt` are excluded from version control using `.gitignore`.
76+
77+
> Do not share your `secret.key` with anyone. If you lose it, saved passwords cannot be decrypted.
78+
79+
---
80+
81+
## 🧠 Ideal For
82+
83+
- Python beginners learning `tkinter`
84+
- Students building secure Python projects
85+
- Learning encryption basics with `cryptography`
86+
- Practicing Git and GitHub workflow with versioned features
87+
88+
---
89+
90+
## ⚙️ Setup Instructions
91+
92+
1. Clone the repo
93+
`git clone https://github.com/Your-Username/Secure_Password_Manager.git`
94+
95+
2. Install dependencies
96+
`pip install cryptography`
97+
98+
3. Run GUI
99+
`python password_manager_gui.py`
100+
101+
4. Or use CLI
102+
`python password_manager_cli.py --length 12 --label Gmail --lower --upper --digits --specials`
103+
104+
---
105+
106+
## ⭐ GitHub Ready
107+
108+
This project was built using proper Git practices:
109+
110+
* Every feature added on a new branch
111+
* Meaningful commit messages
112+
* Clean merge history
113+
114+
---
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from cryptography import fernet
2+
import os
3+
4+
KEY_FILE = "secret.key"
5+
PASSWORD_FILE = "saved_passwords.txt"
6+
7+
# STEP 1: Load the encryption key
8+
def load_key():
9+
if not os.path.exists(KEY_FILE):
10+
print("❌ Encryption key not found. Cannot decrypt passwords.")
11+
return None
12+
with open(KEY_FILE, "rb") as f:
13+
return f.read()
14+
15+
# STEP 2: Decrypt the passwords
16+
def decrypt_password(encrypted_text, fernet_cipher_suite): # Renamed parameter for clarity
17+
try:
18+
return fernet_cipher_suite.decrypt(encrypted_text.encode()).decode()
19+
except Exception:
20+
return "[Decryption Failed]"
21+
22+
# STEP 3: Read and decrypt all entries
23+
def view_passwords():
24+
if not os.path.exists(PASSWORD_FILE):
25+
print("❌ No saved passwords found")
26+
return
27+
28+
key = load_key()
29+
if not key:
30+
return
31+
32+
# Fernet = fernet(key) # Original line causing TypeError
33+
active_fernet_cipher = fernet.Fernet(key) # Correctly instantiate Fernet class from the module
34+
35+
print("🔐 Saved Encrypted Passwords\n" + "=" * 40)
36+
with open(PASSWORD_FILE, "r") as file:
37+
lines = file.readlines()
38+
39+
current_block = {}
40+
41+
for line in lines:
42+
line = line.strip()
43+
44+
if line.startswith("[") and "]" in line:
45+
current_block["timestamp"] = line.strip("[]")
46+
elif line.startswith("Label:"):
47+
current_block["label"] = line.split("Label:")[1].strip()
48+
elif line.startswith("Encrypted Password:"):
49+
current_block["encrypted"] = line.split("Encrypted Password:")[1].strip()
50+
elif line.startswith("Included -"):
51+
current_block["options"] = line.split("Included -")[1].strip()
52+
elif line.startswith("-" * 10):
53+
# print everything together
54+
print(f"\n📅 Date/Time: {current_block.get('timestamp', '[Unknown]')}")
55+
print(f"🏷️ Label: {current_block.get('label', '[None]')}")
56+
print(f"🔓 Password: {decrypt_password(current_block.get('encrypted', ''), active_fernet_cipher)}") # Pass the Fernet instance
57+
print(f"⚙️ Options: {current_block.get('options', '[Not specified]')}")
58+
print("-" * 40)
59+
current_block = {} # Reset for next block
60+
61+
# STEP 4: Entry point
62+
if __name__ == '__main__':
63+
view_passwords()
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import random, datetime, base64, argparse, os
2+
from cryptography.fernet import Fernet
3+
4+
# 🔑 File where secret key will be stored
5+
KEY_FILE = "secret.key"
6+
7+
# ------------------------------------------------------
8+
# STEP 1: Generate a new encryption key (only once))
9+
def generate_key():
10+
key = Fernet.generate_key()
11+
with open(KEY_FILE, "wb") as f:
12+
f.write(key)
13+
14+
# STEP 2: Load the ecryption key
15+
def load_key():
16+
if not os.path.exists(KEY_FILE):
17+
print("No Key found. Creating a new one...")
18+
generate_key()
19+
with open(KEY_FILE, "rb") as f:
20+
return f.read()
21+
22+
# STEP 3: Generate password based on user settings
23+
def generate_password(length, use_lower, use_upper, use_digits, use_specials):
24+
lowercase = 'abcdefghijklmnopqrstuvwxyz'
25+
uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
26+
digits = '0123456789'
27+
specials = '!@#$%^&*()'
28+
29+
# Build character set based on user input
30+
character_set = ''
31+
32+
if use_lower:
33+
character_set = character_set + lowercase
34+
if use_upper:
35+
character_set = character_set + uppercase
36+
if use_digits:
37+
character_set = character_set + digits
38+
if use_specials:
39+
character_set = character_set + specials
40+
41+
if not character_set:
42+
return "Error: No character sets selected. Cannot generate password."
43+
44+
password = ''
45+
for i in range(length):
46+
password = password + random.choice(character_set)
47+
return password
48+
49+
# STEP 4: Check password strength
50+
def check_strength(length, use_lower, use_upper, use_digits, use_specials):
51+
score = 0
52+
53+
# Add points for character variety
54+
score = score + use_lower + use_upper + use_digits + use_specials
55+
56+
if length >= 12:
57+
score = score + 1
58+
59+
if score <= 2:
60+
return "Weak"
61+
elif score == 3 or score == 4:
62+
return "Medium"
63+
else:
64+
return "Strong"
65+
66+
# STEP 5: Command-line interface using argparse
67+
def main():
68+
parser = argparse.ArgumentParser(description="🔐 Password Generator Tool")
69+
70+
parser.add_argument('--length', type=int, required=True, help='Password length (e.g., 8, 12, 16)')
71+
parser.add_argument('--label', type=str, required=True, help='Purpose or label for the password (e.g, Google, Gmail)')
72+
parser.add_argument('--lower', action='store_true', help='Include lowercase letters')
73+
parser.add_argument('--upper', action='store_true', help='Include uppercase letters')
74+
parser.add_argument('--digits', action='store_true', help='Include digits')
75+
parser.add_argument('--specials', action='store_true', help='Include special characters')
76+
77+
args = parser.parse_args()
78+
79+
# Validate length
80+
if args.length <= 0:
81+
print("❌ Password length must be positive. Try again.")
82+
return
83+
84+
# Generate and evaluate password
85+
password = generate_password(args.length, args.lower, args.upper, args.digits, args.specials)
86+
if password.startswith("Error"):
87+
print(password)
88+
return
89+
90+
print(f"✅ Your generated password is: {password}")
91+
strength = check_strength(args.length, args.lower, args.upper, args.digits, args.specials)
92+
print(f"💪 Password Strenght: {strength}")
93+
94+
# STEP 6: Encrypt password using Fernet
95+
key = load_key()
96+
fernet = Fernet(key)
97+
encrypted_password = fernet.encrypt(password.encode()).decode() # This variable already holds the Fernet encrypted string.
98+
99+
# STEP 7: Save encrypted password to file
100+
with open("saved_passwords.txt", "a") as file:
101+
timestamp = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S")
102+
# The 'encoded_password' (base64 of original password) was being saved previously.
103+
# We should save the 'encrypted_password' (Fernet encrypted string) instead.
104+
file.write(f"\n[{timestamp}]\n")
105+
file.write(f"Label: {args.label}\n")
106+
file.write(f"Encrypted Password: {encrypted_password}\n") # Save the Fernet encrypted password and use "Encrypted Password:" label
107+
file.write(f"Included - Lowercase: {args.lower}, Uppercase: {args.upper}, Digits: {args.digits}, Special Characters: {args.specials}\n")
108+
file.write("-" * 40 + "\n")
109+
110+
print("🔒 Password encrypted and saved to 'saved_passwords.txt")
111+
112+
113+
if __name__ == '__main__':
114+
main()
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import base64
2+
3+
def view_passwords(filename="saved_passwords.txt"):
4+
try:
5+
with open(filename, "r") as file:
6+
lines = file.readlines()
7+
8+
# Temperory variable to store each password block info
9+
timestamp = label = encoded_password = ""
10+
included_options = ""
11+
12+
print("\n🔐 Saved Passwords:\n")
13+
14+
for line in lines:
15+
line = line.strip()
16+
17+
if line.startswith("["): # Timestamp line
18+
timestamp = line.strip("[]")
19+
20+
elif line.startswith("Label:"):
21+
label = line.split("Label:")[1].strip()
22+
23+
elif line.startswith("Encoded Password:"):
24+
encoded_password = line.split("Encoded Password:")[1].strip()
25+
try:
26+
decoded_password = base64.b64decode(encoded_password.encode()).decode()
27+
except Exception as e:
28+
decoded_password = f"[Error decoding password: {e}]"
29+
30+
elif line.startswith("Included"):
31+
included_options = line.split("Included -")[1].strip()
32+
33+
elif line.startswith("-" * 10): # Block ends here
34+
print(f"📅 Date/Time: {timestamp}")
35+
print(f"🏷️ Label: {label}")
36+
print(f"🔓 Decoded Password: {decoded_password}")
37+
print(f"🔧 Options Included: {included_options}")
38+
print("-" * 40)
39+
40+
except FileNotFoundError:
41+
print("❌ saved_passwords.txt not found.")
42+
43+
except Exception as e:
44+
print(f"❌ An error occured: {e}")
45+
46+
# Run the function
47+
if __name__ == '__main__':
48+
view_passwords()

0 commit comments

Comments
 (0)