import base64 import os from typing import Iterator from cryptography.fernet import Fernet from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC def _generate_key(salt: bytes, master_pass: bytes) -> bytes: kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000, backend=default_backend(), ) key = base64.urlsafe_b64encode(kdf.derive(master_pass)) return key def encrypt( login: str, passwd: str, master_pass: str, ) -> tuple[bytes, bytes, bytes]: """Encrypts login and password of a user using their master password as a key. Returns a tuple of encrypted login, password and salt""" salt = os.urandom(64) key = _generate_key(salt, master_pass.encode("utf-8")) f = Fernet(key) enc_login = base64.urlsafe_b64decode(f.encrypt(login.encode("utf-8"))) enc_password = base64.urlsafe_b64decode(f.encrypt(passwd.encode("utf-8"))) return (enc_login, enc_password, salt) def decrypt( enc_login: bytes, enc_pass: bytes, master_pass: str, salt: bytes, ) -> tuple[str, str]: """Decrypts login and password using their master password as a key. Returns a tuple of decrypted login and password""" key = _generate_key(salt, master_pass.encode("utf-8")) f = Fernet(key) login = f.decrypt(base64.urlsafe_b64encode(enc_login)).decode("utf-8") password = f.decrypt(base64.urlsafe_b64encode(enc_pass)).decode("utf-8") return (login, password) def decrypt_multiple( accounts: Iterator[tuple[str, bytes, bytes, bytes]], master_pass: str ) -> Iterator[tuple[str, str, str]]: """Gets an iterator of tuples, where values represent account's name, salt, encrypted login and encrypted password. Return an iterator of names, logins and passwords as a tuple""" for account in accounts: name, salt, enc_login, enc_passwd = account login, passwd = decrypt( enc_login, enc_passwd, master_pass, salt, ) yield (name, login, passwd)