import os import hashlib import asyncio class password(): """ This class represents password in the system, it is also used to hash password, to protect it agains attacks. Methods ------- get_salt_length() : int Return size in bytes of salt for passwords. get_algorithm() : str Return name of algorithm used to hash password. async comare(password: str) : bool Check that password given as parameter is same as hashed password stored by password object which that function run on. result() : str Get hashed result password, to store it in database. async from_plain_text(password: str) : password It create new password object from given plain password. It get random salt, and create new hash. async from_hash(hashed: str) : password It create new password object from already hashed password, it is usefull to compare password with plain text password. """ def __init__(self, hashed: str) -> None: """ It create new password from hashed content, it would not being called outside this class. Parameters ---------- hashed : str Already hashed password """ self.__hash = hashed @staticmethod def get_salt_length() -> int: """ It return salt length for the password in the bytes. To add salt to password it must be string, and it is converted to hex values. That mean, size of string salt is twice of salt lenght in bytes. Returns ------- int Lenght of the passwords salt. """ return 12 @staticmethod def get_algorithm() -> str: """ That return name of the algorithm which would be used to hash password. Returns ------- str Name of the password which would be used to hash password. """ return "sha512" @staticmethod def get_iterations() -> int: """ That return count of the iterations of the algorithm. Returns ------- int Count of the iterations of the algorithm. """ return 50000 @staticmethod async def __generate_hash( content: str, salt: str, iterations: int ) -> str: """ That function generate hash of the password from the password and its salt. That function is async, because hashing algorithm take a lot of time. Parameters ---------- content : str Plain password. salt : str Salt to use with password. iterations : int Count of iterations for hashing algorithm. Returns ------- str Hashed password. """ content = content.encode("UTF-8") salt = salt.encode("UTF-8") hashed = await asyncio.to_thread( hashlib.pbkdf2_hmac, password.get_algorithm(), content, salt, iterations ) hashed = hashed.hex() salt = salt.decode("UTF-8") iterations = str(iterations) return hashed + ":" + salt + ":" + iterations def __get_salt(self) -> str: """ That function return salt used to hash password from that hash. It could raise error, when hash is not correct. Raises ------ RuntimeError When salt syntax is not correct. Returns ------- str Salt of the hash. """ try: _, salt, _ = self.__hash.split(":") return salt except: raise RuntimeError("Hash is incorrect.") def __get_own_iterations(self) -> int: """ That return count of iterations stored in the hash. It is required, to make old passwords work after change of the password iterations in the app. Raises ------ RuntimeError When hash syntax is not correct. Returns ------- int Count of iterations from hash. """ try: _, _, iterations = self.__hash.split(":") return int(iterations) except: raise RuntimeError("Hash is incorrect.") async def compare(self, target: str) -> bool: """ That function compare given plain text password with already hashed password. It is async, because it must calculate hash of the given plain text with hashed password salt, to compare result hashes. Parameters ---------- str Plain text to compare hashed password with. Returns ------- bool True when passwords are same, False when not. """ salt = self.__get_salt() iterations = self.__get_own_iterations() hashed_target = await self.__generate_hash( target, salt, iterations ) return self.__hash == hashed_target def result(self) -> str: """ It return hash of the password. Returns ------- str Hash of the password. """ return self.__hash def __repr__(self) -> str: """ It return part of the password hash, to make it readable while debugging. Returns ------- str Part of the hashed password. """ return "Password: \"" + self.__hash[0:20] + "\"." def __str__(self) -> str: """ Return password hash. Returns ------- str Password hash. """ return self.__hash async def from_plain_text(content: str) -> object: """ It create new hashed password from plain text. It is async, because hashing process take a lot of time. Parameters ---------- content : str Plain text password to make hash from. Returns ------- password New password object with that password as hash. """ salt = os.urandom(password.get_salt_length()) salt = salt.hex() iterations = password.get_iterations() hashed = await password.__generate_hash( content, salt, iterations ) return password(hashed) async def from_hash(hashed: str) -> object: """ It create new password object from already hashed password content It is async, to make it similar to from_plain_text. Parameters ---------- hashed : str Already hashed password. Returns ------- password New password object from given hash. """ return password(hashed)