| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- import os
- import hashlib
- import Crypto.Cipher.AES
- class bad_password(Exception):
- """
- This exception is user to mark when bad password had been used.
- """
- pass
- class secret_coder:
- """
- This class is required for crypting and decrypting secret by passwords.
- It store crypted secret as strings in coded form, which make it easies to
- store in database, and send over REST api.
- Format:
- <hashed_secret>:<cipher>:<crypted_secret>
- - hashed_secret: SHA256 of the secret, to validate success of the decrypt.
- - cipher: Cipher from AES.
- - crypted_secret: Crypted secret to encrypt with password.
- """
- def __init__(self, password: str) -> None:
- """
- This functio initialize new coder. It require password, which would
- be used to encrypting and decrypting secrets.
- Parameters:
- password (str): Password to use as crypto key
- """
- password = password.encode("UTF-8")
- hashed = hashlib.sha256(password)
- self.__password = hashed.digest()
- @property
- def password(self) -> bytes:
- """ It return password as crypto key. """
- return self.__password
- def encrypt(self, plain: str) -> str:
- """
- This function encrypt new secret, and return it in specified format.
- Parameters:
- plain (str): Secret to encode
- Returns:
- (str): Encrypted secret
- """
- cipher = self.__iv
- coder = self.__cipher(cipher)
- crypted = coder.encrypt(plain.encode("UTF-8"))
- hashed = self.__hash(plain)
- return self.__pack(hashed, cipher, crypted)
- def decrypt(self, crypted: str) -> str:
- """
- This function decrypt secret in given format, and return decrypted
- secret. When password which initialize coder is bad, then
- bad_password Exception had been raiser.
- Parameters:
- crypted (str): Crypted secret in coder format
- Returns:
- (str): Decrypted secret
- """
- hashed, cipher, crypted = self.__unpack(crypted)
- coder = self.__cipher(cipher)
- result = coder.decrypt(crypted)
- try:
- result = result.decode("UTF-8")
- except:
- raise bad_password("Crypto key is not correct for this secret.")
- if hashed != self.__hash(result):
- raise bad_password("Crypto key is not correct for this secret.")
-
- return result
-
- @property
- def mode(self) -> int:
- """ This return AES mode which had been used by coder. """
-
- return Crypto.Cipher.AES.MODE_CFB
- @property
- def __iv(self) -> bytes:
- """ This return new random cipher for AES. """
- return os.urandom(secret_coder.__get_cipher_length())
- def __get_hash_length() -> int:
- """ This return lenght of hash in bytes. """
- return int(256 / 8)
- def __get_cipher_length() -> int:
- """ This return lenght of random cipher. """
- return 16
- def __cipher(self, iv: bytes) -> object:
- """ This return new AES coder to work with coders. """
- return Crypto.Cipher.AES.new(
- self.password,
- self.mode,
- iv = iv
- )
- def __hash(self, secret: str) -> bytes:
- """
- This function hash given string, and return it as
- bytes.
- Parameters:
- secret (str): Content to hash
- Returns:
- (bytes): Hashed content
- """
- secret = secret.encode("UTF-8")
- hashed = hashlib.sha256(secret)
-
- return hashed.digest()
- @property
- def separator(self) -> str:
- """ This return separator used to packing secret. """
- return secret_coder.__get_separator()
- def __get_separator() -> str:
- """ This return separator, which could be used in static function. """
-
- return ":"
- def __pack(self, hashed: bytes, cipher: bytes, crypted: bytes) -> str:
- """
- This function pack full content required for secret, to coder format.
- Parameters:
- hashed (bytes): Hashed secret
- cipher (bytes): Ciper used in crypter
- crypted (bytes): Crypted secret
- Returns:
- (str): Coded crypted secret
- """
- hashed = hashed.hex()
- cipher = cipher.hex()
- crypted = crypted.hex()
- return hashed + self.separator + cipher + self.separator + crypted
- def __unpack(self, crypted: str) -> [bytes, bytes, bytes]:
- """
- This function unpack given coded secret into bytes parts. When
- secret is not valid raise TypeError.
- Parameters:
- crypted (str): Packed secret
- Returns:
- (bytes): Hashed secret before encryption to validate password
- (bytes): Cipher required by AES to work
- (bytes): Crypted secret to encrypt
- """
- splited = crypted.split(self.separator)
- if len(splited) != 3:
- raise TypeError("Secret is in invalid format.")
- hashed = bytes.fromhex(splited[0])
- cipher = bytes.fromhex(splited[1])
- crypted = bytes.fromhex(splited[2])
- if len(cipher) != secret_coder.__get_cipher_length():
- raise TypeError("Secret is in invalid format.")
-
- if len(hashed) != secret_coder.__get_hash_length():
- raise TypeError("Secret is in invalid format.")
-
- return hashed, cipher, crypted
- def validate(coded: str) -> bool:
- """
- This validate that coded secret is correct. That check hashed lenght
- and cipher lenght. This is static function.
- Parameters:
- coded (str): Codec secret to check correction of
- Returns:
- (bool): True when secret is corret, False when not.
- """
-
- splited = coded.split(secret_coder.__get_separator())
- if len(splited) != 3:
- return False
- hashed = bytes.fromhex(splited[0])
- cipher = bytes.fromhex(splited[1])
- if len(cipher) != secret_coder.__get_cipher_length():
- return False
- if len(hashed) != secret_coder.__get_hash_length():
- return False
- return True
|