| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 | import osimport hashlibimport asyncioclass 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)
 |