Commit b3b0635c authored by Benjamin REED's avatar Benjamin REED

basic working build

parents
import sys
import secrets
import os
import random
from cryptography.fernet import Fernet
from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d
from cryptography.hazmat.backends import default_backend as backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
DEFAULT_DATABASE_PATH = "db.txt"
USAGES = {
"new": "new <length> [ <label> <character set> ] ( 'length' = num characters in password, 'label' = optional name for the password 'charset' = characters that can be in the password (use '-<characters>' to exclude a list of characters))",
"echo": "echo <string content> ( 'string_content' = any text to be echoed )",
"dbg": "dbg ( Prints a debug output of the manager's state )",
}
DEFAULT_KEYGEN_ITERATIONS = 100_000
LOWERCASE_ALPHABET = set([chr(e) for e in range(ord('a'), ord('a') + 25)])
UPPERCASE_ALPHABET = set([chr(e) for e in range(ord('A'), ord('A') + 25)])
NUMBERS = set([chr(e) for e in range(ord('0'), ord('0') + 10)])
SPECIAL = set([chr(e) for e in range(ord('!'), ord('!') + 14)]) ^ set(',') # no commas in our passwords, sorry
CHARSET_MAP = {
"l": LOWERCASE_ALPHABET,
"u": UPPERCASE_ALPHABET,
"n": NUMBERS,
"s": SPECIAL,
}
def derive_key(password: bytes, salt: bytes, iterations: int = DEFAULT_KEYGEN_ITERATIONS) -> bytes:
kdf = PBKDF2HMAC(
algorithm = hashes.SHA256(), length=32, salt=salt,
iterations=iterations, backend=backend)
return b64e(kdf.derive(password))
def password_encrypt(message: bytes, password: str, iterations: int = DEFAULT_KEYGEN_ITERATIONS) -> bytes:
salt = secrets.token_bytes(16)
key = derive_key(password.encode(), salt, iterations)
return b64e(
b'%b%b%b' % (
salt,
iterations.to_bytes(4, 'big'),
b64d(Fernet(key).encrypt(message)),
)
)
def password_decrypt(token: bytes, password: str) -> (bytes, bytes):
decoded = b64d(token)
salt, iters, token = decoded[:16], decoded[16:20], b64e(decoded[20:])
iterations = int.from_bytes(iters, 'big')
key = derive_key(password.encode(), salt, iterations)
return (key, Fernet(key).decrypt(token))
def encrypt(message: bytes, key: bytes) -> bytes:
return Fernet(key).encrypt(message)
def decrypt(token: bytes, key: bytes) -> bytes:
return Fernet(key).decrypt(token)
class State:
def __init__(self, filepath, password):
self.filepath = filepath
try:
file = open(filepath, 'r')
except:
raise FileNotFoundError
content = file.read()
lines = content.split('\n')
head = lines[0]
try:
key, decrypt = password_decrypt(head, password)
except:
print("Password does not match for this database, and as such it cannot be decrypted.")
raise Exception
self.passwords = {}
self.key = key
self.head = head
if content == "":
file.close()
return
for i, line in enumerate(lines):
if i == 0:
continue # lmao so hacky! whaddeva!!
if line == "":
continue
parts = line.split(",")
if len(parts) != 2:
print(f"Error parsing password on line {i}: {line}\n ^^^^ -> Expected comma.")
continue
self.passwords[parts[0]] = parts[1].encode()
file.close()
def add_password(self, label, password):
if label == "":
label = str(len(self.passwords) + 1)
pourmoi = self.key
self.passwords[label] = Fernet(self.key).encrypt(password.encode())
print(repr(self.passwords[label]))
#self.passwords[label] = encrypt(password.encode, self.key)
def get_password(self, label):
return Fernet(self.key).decrypt(self.passwords[label])
def save(self):
content = self.head + "\n" + "\n".join(map(lambda x: f"{x[0]},{x[1].decode('utf-8')}", self.passwords.items()))
file = open(self.filepath, 'w')
file.write(content)
file.close()
def caesar_cipher(inp, offset):
pass
def generate_password(length, charset):
s = []
print(charset)
for i in range(0, length):
s.append(random.choice(list(charset)))
return ''.join(s)
def aes_256(inp):
pass
def handle_args(db: str, password: str):
state = State(db, password)
return state
def main():
filepath = ""
if len(sys.argv) < 2:
print(f"No database path supplied. Usage: {sys.argv[0]} <database path> <password>")
return
else:
filepath = sys.argv[1]
if len(sys.argv) < 3:
print(f"No password supplied. Usage: {sys.argv[0]} <database path> <password>");
return
password = sys.argv[2]
try:
print("?")
state = handle_args(filepath, password)
except FileNotFoundError:
print(f"Error while opening database file, {filepath} does not exist or cannot be opened.")
ask = input(f"Create {filepath}? (Y/N)")
ask = ask.strip()
ask = ask.lower()
if ask == "y":
file = open(filepath, "xb")
salt = secrets.token_bytes(16)
key = password_encrypt("super secret hihi >_>".encode(), password)
file.write(key)
file.write('\n'.encode())
file.close()
return main()
else:
return
except Exception:
return
print("Welcome to SmashPass. Type 'h' or 'help' for help")
while True:
command_input = input(">")
command_input = command_input.strip()
args = command_input.split(" ")
if len(args) == 0:
continue
command = args[0]
if command == "q" or command == "quit" or command == "exit":
break
elif command == "help" or command == "h" or command == "?":
if len(args) == 1:
print(USAGES.keys())
else:
cmd = args[1]
print(USAGES[cmd])
elif command == "echo":
if len(args) < 2:
print(f"Too few arguments for {command}, usage: {USAGES[command]}")
continue
print(" ".join(args[1:]))
elif command == "dbg":
print(vars(state))
elif command == "new": # Form: new <length> [ <label> ]
if len(args) < 2:
print(f"Too few arguments for {command}, usage: {USAGES[command]}")
continue
try:
length = int(args[1])
except:
print(f"Error parsing length as number, usage: {USAGES[command]}")
continue
if len(args) == 3:
label = args[2]
else:
label = ""
charset = "all"
if len(args) == 4:
charset = args[3]
chars = LOWERCASE_ALPHABET
if charset == "all":
chars = LOWERCASE_ALPHABET | UPPERCASE_ALPHABET | NUMBERS | SPECIAL
else:
s = charset.split("-")
for c in list(s[0]):
chars |= CHARSET_MAP[c]
if len(s) > 1:
for c in s[1]: # list of chars to exclude
chars ^= set(c)
password = generate_password(length, chars)
print(str(password))
state.add_password(label, password)
elif command == "list" or command == "ls":
if len(args) == 1:
for l in state.passwords:
print(f"{l}: {state.get_password(l).decode('utf-8')}")
elif len(args) == 2:
print(state.get_password(args[1]))
state.save()
print("Thanks for using!")
if __name__ == "__main__":
main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment