diff --git a/lib/banner.py b/lib/banner.py new file mode 100644 index 0000000..7cc8fde --- /dev/null +++ b/lib/banner.py @@ -0,0 +1,36 @@ +def banner(): + print('''\033[49m \033[m + \033[49m \033[m + \033[49m \033[38;5;14;49m▄▄▄▄▄\033[49m \033[m + \033[49m \033[38;5;14;49m▄\033[38;5;14;48;5;14m▄\033[48;5;14m \033[38;5;14;49m▄\033[49m \033[m + \033[49m \033[38;5;14;49m▄\033[48;5;14m \033[38;5;14;49m▄\033[49m \033[m + \033[49m \033[38;5;14;49m▄\033[48;5;14m \033[49;38;5;14m▀\033[49m \033[49;38;5;14m▀\033[48;5;14m \033[38;5;14;49m▄\033[49m \033[m + \033[49m \033[38;5;14;49m▄\033[48;5;14m \033[49;38;5;14m▀\033[49m \033[49;38;5;14m▀\033[48;5;14m \033[38;5;14;49m▄\033[49m \033[m + \033[49m \033[38;5;14;49m▄\033[48;5;14m \033[49m \033[49;38;5;14m▀\033[48;5;14m \033[38;5;14;49m▄\033[49m \033[m + \033[49m \033[38;5;14;49m▄\033[48;5;14m \033[38;5;14;49m▄\033[49m \033[49;38;5;14m▀\033[48;5;14m \033[38;5;14;49m▄\033[49m \033[m + \033[49m \033[38;5;14;49m▄\033[48;5;14m \033[38;5;14;49m▄\033[49m \033[48;5;14m \033[49m \033[m + \033[49m \033[38;5;14;49m▄\033[48;5;14m \033[49;38;5;14m▀▀▀▀▀▀▀\033[48;5;14m \033[38;5;14;49m▄\033[49m \033[38;5;14;49m▄\033[48;5;14m \033[38;5;14;48;5;14m▄\033[49m \033[m + \033[49m \033[38;5;14;49m▄\033[48;5;14m \033[49;38;5;14m▀\033[49m \033[38;5;14;49m▄\033[48;5;14m \033[38;5;14;49m▄\033[49m \033[38;5;14;49m▄\033[48;5;14m \033[38;5;14;48;5;14m▄\033[49m \033[m + \033[49m \033[48;5;14m \033[49;38;5;14m▀\033[49m \033[38;5;14;49m▄▄▄▄\033[48;5;14m \033[49;38;5;14m▀\033[49m \033[48;5;14m \033[38;5;14;49m▄\033[49m \033[49;38;5;14m▀\033[48;5;14m \033[49;38;5;14m▀\033[49m \033[m + \033[49m \033[48;5;14m \033[38;5;14;49m▄\033[48;5;14m \033[49;38;5;14m▀▀▀▀\033[48;5;14m \033[49m \033[48;5;14m \033[38;5;14;49m▄\033[49m \033[49;38;5;14m▀\033[48;5;14m \033[49;38;5;14m▀\033[49m \033[m + \033[49m \033[49;38;5;14m▀\033[48;5;14m \033[49m \033[49;38;5;14m▀\033[38;5;14;48;5;14m▄\033[48;5;14m \033[49m \033[48;5;14m \033[49m \033[48;5;14m \033[49m \033[m + \033[49m \033[49;38;5;14m▀\033[38;5;14;48;5;14m▄\033[48;5;14m \033[49m \033[49;38;5;14m▀\033[38;5;14;48;5;14m▄\033[48;5;14m \033[38;5;14;49m▄\033[49m \033[38;5;14;49m▄\033[48;5;14m \033[49m \033[48;5;14m \033[49;38;5;14m▀\033[49m \033[m + \033[49m \033[49;38;5;14m▀\033[38;5;14;48;5;14m▄\033[48;5;14m \033[38;5;14;49m▄\033[49m \033[49;38;5;14m▀\033[38;5;14;48;5;14m▄\033[48;5;14m \033[49m \033[38;5;14;49m▄\033[48;5;14m \033[49;38;5;14m▀\033[49m \033[m + \033[49m \033[49;38;5;14m▀\033[38;5;14;48;5;14m▄\033[48;5;14m \033[38;5;14;49m▄\033[49m \033[49;38;5;14m▀\033[38;5;14;48;5;14m▄\033[48;5;14m \033[49m \033[38;5;14;49m▄\033[48;5;14m \033[49;38;5;14m▀\033[49m \033[m + \033[49m \033[49;38;5;14m▀\033[38;5;14;48;5;14m▄\033[48;5;14m \033[38;5;14;49m▄▄▄▄▄\033[38;5;14;48;5;14m▄\033[38;5;14;49m▄▄\033[48;5;14m \033[49;38;5;14m▀\033[49m \033[m + \033[49m \033[49;38;5;14m▀\033[38;5;14;48;5;14m▄\033[48;5;14m \033[49;38;5;14m▀\033[49m \033[m + \033[49m \033[49;38;5;14m▀\033[48;5;14m \033[49;38;5;14m▀\033[49m \033[m + \033[49m \033[49;38;5;14m▀▀▀▀▀\033[49m \033[m + \033[49m \033[m + \033[49m \033[m + ''') + print(''' + _____ _ _ _ + | __ \\ | | /\\ | | | | + | |__) |_ _ ___ _____ _____ _ __ __| | / \\ | | ___| |__ ___ _ __ ___ _ _ + | ___/ _` / __/ __\\ \\ /\\ / / _ \\| '__/ _` | / /\\ \\ | |/ __| '_ \\ / _ \\ '_ ` _ \\| | | | + | | | (_| \\__ \\__ \\\\ V V / (_) | | | (_| |/ ____ \\| | (__| | | | __/ | | | | | |_| | + |_| \\__,_|___/___/ \\_/\\_/ \\___/|_| \\__,_/_/ \\_\\_|\\___|_| |_|\\___|_| |_| |_|\\__, | + __/ | + |___/ + ''') \ No newline at end of file diff --git a/lib/db.py b/lib/db.py new file mode 100644 index 0000000..23436a6 --- /dev/null +++ b/lib/db.py @@ -0,0 +1,99 @@ +#imports +from sqlalchemy import create_engine, Column, String, Integer +from sqlalchemy.orm import sessionmaker, declarative_base +from os.path import exists +from os import mkdir +from contextlib import contextmanager + +#sql alchemy base for tables to inherit from +Base = declarative_base() + +# Ensure the directory for the database exists +if not exists("db"): + mkdir("db") + +# Define the Password model +class Password(Base): + __tablename__ = "Password" + domain = Column("domain", String, primary_key=True) + ccred = Column("ccred", String) + + def __init__(self, domain, ccred): + self.domain = domain + self.ccred = ccred + + +# Database setup +engine = create_engine("sqlite:///db/creds.db") +Base.metadata.create_all(bind=engine) + +Session = sessionmaker(bind=engine) + +#manage context +@contextmanager +def get_session(): + session = Session() + try: + yield session + session.commit() + except Exception as e: + session.rollback() + print(f"Database error: {e}") + finally: + session.close() + +#store credential +def store(domain,ccred): + p = Password(domain,ccred) + with get_session() as session: + try: + session.add(p) + return "Stored credential" + except Exception as e: + return "Error storing password: {e}" + +#load credemtial +def fetch(domain): + #with context manager + with get_session() as session: + + #query passwords by domain + p = session.query(Password).filter(Password.domain == domain).first() + + #if one is found + if p: + return p.ccred + + #error + else: + return f"No cred found for '{domain}'" +def fetchall(): + #with context manager + with get_session() as session: + + #query passwords by domain + p = session.query(Password) + + #if one is found + if p: + return p + + #error + else: + return "No creds found" +#load credemtial +def update(domain,ccred): + #with context manager + with get_session() as session: + + #query passwords by domain + p = session.query(Password).filter(Password.domain == domain).first() + + #if one is found + if p: + p.ccred=ccred + return "updated credential" + + #error + else: + return f"No cred found for '{domain}'" \ No newline at end of file diff --git a/lib/parse.py b/lib/parse.py new file mode 100644 index 0000000..0455a06 --- /dev/null +++ b/lib/parse.py @@ -0,0 +1,23 @@ +import argparse +def parser(): + parser = argparse.ArgumentParser(description="passwordalchemy args") + subparse=parser.add_subparsers(dest="command") + + fetchparser=subparse.add_parser("fetch") + fetchparser.add_argument("-pk","--privatekey",required=True, help="path to private key pem file") + fetchparser.add_argument("-d", "--domain",required=True) + + storeparser=subparse.add_parser("store") + storeparser.add_argument("-d", "--domain",required=True) + storeparser.add_argument("-p", "--password",required=True) + + updateparser=subparse.add_parser("update") + updateparser.add_argument("-d", "--domain",required=True) + updateparser.add_argument("-p", "--password",required=True) + + gkparser=subparse.add_parser("genkeys") + gkparser.add_argument("-pk","--privatekey") + gkparser.add_argument("-opk","--oldprivatekey") + + args = parser.parse_args() + return args \ No newline at end of file diff --git a/passwordalchemy.py b/passwordalchemy.py new file mode 100644 index 0000000..811e0d2 --- /dev/null +++ b/passwordalchemy.py @@ -0,0 +1,83 @@ +import rsa +import base64 +import lib.db +import lib.parse +import lib.banner + +class mainfuncs: + #generate keyset. first or new + @staticmethod + def genkeys(args): + #generate keypair + (pubkey, privkey) = rsa.newkeys(900) + #if a key is already present + if args.oldprivatekey: + with open(args.oldprivatekey,"r") as pemfile: + oldprivatekey=rsa.PrivateKey._load_pkcs1_pem(pemfile.read()) + #save public key + with open("pub.pem","w") as pemfile: + pemfile.write(pubkey._save_pkcs1_pem().decode("utf-8")) + #if path to save in not specified + if not args.privatekey: + print("save this pem to a secure location to decrypt your passwords. If you lose it you can't recover your credentials") + print(privkey._save_pkcs1_pem().decode("utf-8")) + print("you can copy and paste it. and leave the prompt blank") + args.privatekey=input("or we can save it for you here: ") + #save private key + with open(args.privatekey,"w") as pemfile: + pemfile.write(privkey._save_pkcs1_pem().decode("utf-8")) + #if a key is already present + if args.oldprivatekey: + creds=lib.db.fetchall() + #if creds are present + if creds: + for cred in creds: + with open(args.privatekey) as pemfile: + privkey=rsa.PrivateKey._load_pkcs1_pem(pemfile.read()) + pcred=rsa.decrypt(base64.b64decode(cred.ccred),oldprivatekey).decode("utf-8") + ccred=base64.b64encode(rsa.encrypt(pcred.encode("utf-8"),pubkey)).decode("utf-8") + lib.db.update(cred.domain,ccred) + + #fetch a credential + @staticmethod + def fetch(args): + #send to db library fetch function + ccred=lib.db.fetch(args.domain) + with open(args.privatekey) as pemfile: + #load rsa private key + privkey=rsa.PrivateKey._load_pkcs1_pem(pemfile.read()) + #print credential + print(rsa.decrypt(base64.b64decode(ccred),privkey).decode("utf-8")) + + #store a credential + @staticmethod + def store(args): + with open("pub.pem") as pemfile: + #load rsa public key + pubkey=rsa.PublicKey._load_pkcs1_pem(pemfile.read()) + #encrypted credential + ccred=base64.b64encode(rsa.encrypt(args.password.encode("utf-8"),pubkey)).decode("utf-8") + #send to db library store function + print(lib.db.store(args.domain,ccred)) + + #update a credential + @staticmethod + def update(args): + with open("pub.pem") as pemfile: + #load rsa public key + pubkey=rsa.PublicKey._load_pkcs1_pem(pemfile.read()) + #encrypted credential + ccred=base64.b64encode(rsa.encrypt(args.password.encode("utf-8"),pubkey)).decode("utf-8") + #send to db update store function + print(lib.db.update(args.domain,ccred)) + +if __name__ == "__main__": + #banner + lib.banner.banner() + #command line arguments + args=lib.parse.parser() + #get function name from arguments + if args.command: + command=getattr(mainfuncs,args.command) + #execute + command(args) \ No newline at end of file