Skip to content

Commit 9a9bfd9

Browse files
committed
Initial commit
Alpha version
0 parents  commit 9a9bfd9

File tree

6 files changed

+386
-0
lines changed

6 files changed

+386
-0
lines changed

.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Cache
2+
__pycache__
3+
4+
# IDE folder
5+
.idea
6+
7+
# Virtual env
8+
venv
9+
10+
# Builds' folders
11+
dist
12+
build
13+
14+
# Files that are generated specified per user
15+
*.key
16+
*.db
17+
settings.py

PyPassword/__init__.py

Whitespace-only changes.

PyPassword/__main__.py

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
# -*- coding: utf-8 -*-
2+
import base64
3+
import os
4+
import random
5+
import sqlite3
6+
import string
7+
from getpass import getpass
8+
9+
import pyperclip
10+
from cryptography.fernet import Fernet, InvalidToken
11+
from cryptography.hazmat.backends import default_backend
12+
from cryptography.hazmat.primitives import hashes
13+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
14+
15+
16+
class Color:
17+
# Sources:
18+
# https://stackoverflow.com/questions/8924173/how-do-i-print-bold-text-in-python
19+
# http://ascii-table.com/ansi-escape-sequences.php
20+
# PURPLE = '\033[95m'
21+
# CYAN = '\033[96m'
22+
# DARKCYAN = '\033[36m'
23+
# BLUE = '\033[94m'
24+
# GREEN = '\033[92m'
25+
# YELLOW = '\033[93m'
26+
# RED = '\033[91m'
27+
# BOLD = '\033[1m'
28+
UNDERLINE = '\033[4m'
29+
END = '\033[0m'
30+
31+
32+
class Program:
33+
name = 'Python Password' # Work in progress
34+
version = '0.1' # Alpha
35+
author = 'Jakub S.'
36+
git_hub = 'https://github.com/AnonymousX86/Python-Password'
37+
38+
39+
def header():
40+
foo = ''
41+
for i in range(len(Program.name) + len(Program.version) + 10):
42+
foo += '-'
43+
foo += '\n' + \
44+
f'+++ {Program.name} v{Program.version} +++\n'
45+
for i in range(len(Program.name) + len(Program.version) + 10):
46+
foo += '-'
47+
foo += '\n'
48+
return foo
49+
50+
51+
def mark(txt: str):
52+
"""Underlines text"""
53+
return Color.UNDERLINE + txt + Color.END
54+
55+
56+
def clear():
57+
"""Clears console (Windows only)."""
58+
os.system('cls')
59+
60+
61+
def confirm():
62+
input('\n---\n\nPress ENTER to continue...')
63+
64+
65+
def show_records(records=None):
66+
# Default query - passwords' names
67+
if records is None:
68+
records = query('SELECT `name` FROM `passwords`;')
69+
70+
if records is None:
71+
print('Nothing to show!')
72+
73+
else:
74+
for record in records:
75+
print(f'- {record[0]}\n')
76+
77+
78+
def check_files():
79+
"""Checking if database and key file exists. If not - creates them."""
80+
print('Checking files integrity...')
81+
82+
# Settings file
83+
try:
84+
from PyPassword import settings
85+
except ImportError:
86+
print('Settings file not found! Creating one...')
87+
open(file('settings.py'), 'x')
88+
89+
custom_salt = input(
90+
'Provide custom salt or leave empty for random (it should be super secret adn super safe): ')
91+
if custom_salt == '':
92+
custom_salt = os.urandom(16)
93+
94+
with open(file('settings.py'), 'w') as f:
95+
f.write(f"# -*- coding: utf-8 -*-\nsalt = b'{custom_salt}'\n")
96+
finally:
97+
print('Settings OK!')
98+
99+
# Database
100+
try:
101+
open(file('passwords.db'), 'rb')
102+
except FileNotFoundError:
103+
print('Database file not found! Creating one...')
104+
open(file('passwords.db'), 'x')
105+
query('''create table passwords(id INTEGER constraint passwords_pk primary key autoincrement, name varchar not
106+
null, password varchar not null );''')
107+
query('create unique index passwords_name_uindex on passwords (name);')
108+
finally:
109+
print('Database OK!')
110+
111+
# ``key`` file
112+
try:
113+
open(file('master.key'), 'r')
114+
except FileNotFoundError:
115+
print('Key file not found! Creating one...')
116+
generate_key()
117+
finally:
118+
print('Key OK!')
119+
120+
121+
def file(filename: str):
122+
"""
123+
Add absolute path to file name.
124+
:param filename: File name.
125+
:return: Absolute path to file with specified name.
126+
"""
127+
return os.path.join(os.path.dirname(__file__), filename)
128+
129+
130+
def query(q_input: str, q_args=None):
131+
"""
132+
Executes query in SQLite database.
133+
:param q_input: SQL query.
134+
:param q_args: Query arguments (``?`` symbol for placeholders).
135+
:return: All records or ``None``.
136+
"""
137+
if q_args is None:
138+
q_args = []
139+
conn = sqlite3.connect(file('passwords.db'))
140+
c = conn.cursor()
141+
c.execute(q_input, q_args)
142+
records = c.fetchall()
143+
conn.commit()
144+
c.close()
145+
conn.close()
146+
return records
147+
148+
149+
def rand_password(length: int = 16):
150+
"""
151+
Most safely password.
152+
:param length: Password length (default: 16).
153+
:return: Very safe password.
154+
"""
155+
possibilities = list(str(string.ascii_letters + string.digits + string.punctuation))
156+
result = ''
157+
for i in range(length):
158+
result += random.choice(possibilities)
159+
return result
160+
161+
162+
# Option 1
163+
def generate_key():
164+
password_input = input('Provide master password: ')
165+
password = password_input.encode()
166+
167+
try:
168+
from PyPassword import settings
169+
170+
except ImportError:
171+
print('No settings are found! Please restart the program, so it could create them for you')
172+
confirm()
173+
174+
else:
175+
kdf = PBKDF2HMAC(
176+
algorithm=hashes.SHA256(),
177+
length=32,
178+
salt=settings.salt,
179+
iterations=100000,
180+
backend=default_backend()
181+
)
182+
183+
key = base64.urlsafe_b64encode(kdf.derive(password))
184+
185+
try:
186+
with open(file('master.key'), 'wb') as f:
187+
f.write(key)
188+
except FileNotFoundError:
189+
print('File removed! Please do not remove it')
190+
else:
191+
print('Key file generated')
192+
193+
194+
# Option 2
195+
def get_password():
196+
print('Available passwords:\n')
197+
show_records()
198+
199+
password_input = input('>>> ')
200+
if password_input.lower() in ('', 'cancel'):
201+
return
202+
203+
else:
204+
to_decrypt = query('SELECT `password` FROM `passwords` WHERE `name` LIKE ?;', [password_input])[0][0]
205+
if (n := type(to_decrypt)) is not None:
206+
if n is bytes:
207+
with open(file('master.key'), 'rb') as f:
208+
key = f.read()
209+
f = Fernet(key)
210+
211+
try:
212+
pyperclip.copy(str(f.decrypt(to_decrypt).decode('utf-8')))
213+
except InvalidToken:
214+
print('You do not have permissions to see that password,'
215+
' please re-generate key with matching master password and salt.')
216+
else:
217+
print('Password copied to clipboard!')
218+
else:
219+
raise SyntaxError('Bad password type, that\'s an error.')
220+
else:
221+
raise KeyError('That password does not exits')
222+
223+
224+
# Option 3
225+
def set_password():
226+
password_name = input('Provide password name (visible): ')
227+
password_value = getpass('Provide password value (or leave empty for random): ')
228+
229+
if password_value == '':
230+
password_value = rand_password()
231+
232+
with open(file('master.key'), 'rb') as f:
233+
key = f.read()
234+
f = Fernet(key)
235+
password_encrypted = f.encrypt(password_value.encode())
236+
237+
try:
238+
query('INSERT INTO passwords (`name`, `password`) VALUES (?, ?);', [password_name, password_encrypted])
239+
240+
except sqlite3.IntegrityError:
241+
print('That password already exists!')
242+
243+
244+
# Option 4
245+
def del_password():
246+
print('Available passwords:\n')
247+
show_records()
248+
249+
password_input = input('>>> ')
250+
to_del = query('SELECT `password` FROM `passwords` WHERE `name` LIKE ?;', [password_input])[0][0]
251+
if (n := type(to_del)) is not None:
252+
if n is bytes:
253+
del_confirm = input('If you want to proceed, please type once more password name: ')
254+
255+
with open(file('master.key'), 'rb') as f:
256+
key = f.read()
257+
f = Fernet(key)
258+
to_del = f.encrypt(to_del).decode('utf-8')
259+
260+
if to_del == del_confirm:
261+
query('DELETE FROM `passwords` WHERE `name` LIKE ?;', [to_del])
262+
263+
else:
264+
print('Action cancelled')
265+
else:
266+
raise SyntaxError('Bad password type, that\'s an error')
267+
else:
268+
raise KeyError('That password does not exits')
269+
270+
271+
# Option 5
272+
def quick_start():
273+
print(f'Program name: {Program.name}\n'
274+
f'Current version: {Program.version}\n'
275+
f'Author: {Program.author}\n'
276+
f'GitHub: {Program.git_hub}')
277+
278+
279+
if __name__ == '__main__':
280+
clear()
281+
check_files()
282+
while True:
283+
clear()
284+
print(
285+
header(),
286+
' Select option:\n'
287+
' 1. {0}e-generate master key\n'
288+
' 2. {1}et password\n'
289+
' 3. {2}ave password\n'
290+
' 4. {3}elete password\n'
291+
' 5. {4}nfo\n'
292+
' 0. {5}xit'.format(
293+
# Options' numbers
294+
mark('R'), # 1
295+
mark('G'), # 2
296+
mark('S'), # 3
297+
mark('D'), # 4
298+
mark('I'), # 5
299+
mark('E') # 0
300+
))
301+
choice = input('>>> ')
302+
303+
clear()
304+
305+
if choice in ('1', 'R', 'r'):
306+
generate_key()
307+
308+
elif choice in ('2', 'G', 'g'):
309+
get_password()
310+
311+
elif choice in ('3', 'S', 's'):
312+
set_password()
313+
314+
elif choice in ('4', 'D', 'd'):
315+
del_password()
316+
317+
elif choice in ('5', 'H', 'h'):
318+
quick_start()
319+
320+
elif choice in ('0', 'E', 'e'):
321+
print(f' * Thanks for using {Program.name}! *')
322+
exit()
323+
324+
confirm()

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Python Password
2+
3+
**Hello world!** This is my first project of usable program.
4+
5+
*For now it's named as above, because I have no better idea how to name it. Here are some words about it.*
6+
7+
## What it the program about?
8+
Python Password is intended to safely store passwords.
9+
##### Current version 0.1 (Alpha)
10+
11+
## How does it work?
12+
At first launch program is creating local database (SQLite database). Of course program is searching for file
13+
every time it's opened In that database passwords will be stored and `master.key` file, where's stored key
14+
used to encrypt end decrypt passwords. That `.key` file is generated based on user password and random
15+
ASCII symbol or also user input so you can replicate the key with 2 simple inputs.
16+
17+
## Quick start guide
18+
1. At very first launch, program will create 3 files:
19+
20+
- `settings.py`,
21+
- `master.key`,
22+
- `passwords.db`.
23+
24+
All of them are needed for proper program functioning. Each subsequent start of the program is trying to find those
25+
files. Every missing file will be re-created.
26+
27+
2. After program will check files, you will se typical console GUI. You can provide option number, or underlined letter.
28+
Options are:
29+
30+
1. **Re-generate master key** - if you want to change your master password.
31+
2. **Get password** - program shows all saved passwords' names and asks which you want to get. Password isn't
32+
prompted, it's being copied to clipboard. (Same as <kbd>Ctrl + C</kbd>, <kbd>Ctrl + V</kbd>)
33+
3. **Save password** - used for saving password.
34+
4. **Delete password** - after double inserting password's name, that password is being deleted from local database.
35+
5. **Info** - useful links and info about the program.
36+
6. **Exit** - I think I don't have to describe it.
37+
38+
3. Program is working in loop, so it will continue working unless you will close the windows or select `Exit` option.
39+
40+
oIf you want to help me improve this program, please contact me via Discord: `Anonymous©#7296`, or this repo's issues.
41+
42+
### Thanks for all of your help!

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
cryptography
2+
pyperclip

runtime.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
python-3.8.2

0 commit comments

Comments
 (0)