| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 |
- 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)
|