secret_coder.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import os
  2. import hashlib
  3. import Crypto.Cipher.AES
  4. class bad_password(Exception):
  5. """
  6. This exception is user to mark when bad password had been used.
  7. """
  8. pass
  9. class secret_coder:
  10. """
  11. This class is required for crypting and decrypting secret by passwords.
  12. It store crypted secret as strings in coded form, which make it easies to
  13. store in database, and send over REST api.
  14. Format:
  15. <hashed_secret>:<cipher>:<crypted_secret>
  16. - hashed_secret: SHA256 of the secret, to validate success of the decrypt.
  17. - cipher: Cipher from AES.
  18. - crypted_secret: Crypted secret to encrypt with password.
  19. """
  20. def __init__(self, password: str) -> None:
  21. """
  22. This functio initialize new coder. It require password, which would
  23. be used to encrypting and decrypting secrets.
  24. Parameters:
  25. password (str): Password to use as crypto key
  26. """
  27. password = password.encode("UTF-8")
  28. hashed = hashlib.sha256(password)
  29. self.__password = hashed.digest()
  30. @property
  31. def password(self) -> bytes:
  32. """ It return password as crypto key. """
  33. return self.__password
  34. def encrypt(self, plain: str) -> str:
  35. """
  36. This function encrypt new secret, and return it in specified format.
  37. Parameters:
  38. plain (str): Secret to encode
  39. Returns:
  40. (str): Encrypted secret
  41. """
  42. cipher = self.__iv
  43. coder = self.__cipher(cipher)
  44. crypted = coder.encrypt(plain.encode("UTF-8"))
  45. hashed = self.__hash(plain)
  46. return self.__pack(hashed, cipher, crypted)
  47. def decrypt(self, crypted: str) -> str:
  48. """
  49. This function decrypt secret in given format, and return decrypted
  50. secret. When password which initialize coder is bad, then
  51. bad_password Exception had been raiser.
  52. Parameters:
  53. crypted (str): Crypted secret in coder format
  54. Returns:
  55. (str): Decrypted secret
  56. """
  57. hashed, cipher, crypted = self.__unpack(crypted)
  58. coder = self.__cipher(cipher)
  59. result = coder.decrypt(crypted)
  60. try:
  61. result = result.decode("UTF-8")
  62. except:
  63. raise bad_password("Crypto key is not correct for this secret.")
  64. if hashed != self.__hash(result):
  65. raise bad_password("Crypto key is not correct for this secret.")
  66. return result
  67. @property
  68. def mode(self) -> int:
  69. """ This return AES mode which had been used by coder. """
  70. return Crypto.Cipher.AES.MODE_CFB
  71. @property
  72. def __iv(self) -> bytes:
  73. """ This return new random cipher for AES. """
  74. return os.urandom(secret_coder.__get_cipher_length())
  75. def __get_hash_length() -> int:
  76. """ This return lenght of hash in bytes. """
  77. return int(256 / 8)
  78. def __get_cipher_length() -> int:
  79. """ This return lenght of random cipher. """
  80. return 16
  81. def __cipher(self, iv: bytes) -> object:
  82. """ This return new AES coder to work with coders. """
  83. return Crypto.Cipher.AES.new(
  84. self.password,
  85. self.mode,
  86. iv = iv
  87. )
  88. def __hash(self, secret: str) -> bytes:
  89. """
  90. This function hash given string, and return it as
  91. bytes.
  92. Parameters:
  93. secret (str): Content to hash
  94. Returns:
  95. (bytes): Hashed content
  96. """
  97. secret = secret.encode("UTF-8")
  98. hashed = hashlib.sha256(secret)
  99. return hashed.digest()
  100. @property
  101. def separator(self) -> str:
  102. """ This return separator used to packing secret. """
  103. return secret_coder.__get_separator()
  104. def __get_separator() -> str:
  105. """ This return separator, which could be used in static function. """
  106. return ":"
  107. def __pack(self, hashed: bytes, cipher: bytes, crypted: bytes) -> str:
  108. """
  109. This function pack full content required for secret, to coder format.
  110. Parameters:
  111. hashed (bytes): Hashed secret
  112. cipher (bytes): Ciper used in crypter
  113. crypted (bytes): Crypted secret
  114. Returns:
  115. (str): Coded crypted secret
  116. """
  117. hashed = hashed.hex()
  118. cipher = cipher.hex()
  119. crypted = crypted.hex()
  120. return hashed + self.separator + cipher + self.separator + crypted
  121. def __unpack(self, crypted: str) -> [bytes, bytes, bytes]:
  122. """
  123. This function unpack given coded secret into bytes parts. When
  124. secret is not valid raise TypeError.
  125. Parameters:
  126. crypted (str): Packed secret
  127. Returns:
  128. (bytes): Hashed secret before encryption to validate password
  129. (bytes): Cipher required by AES to work
  130. (bytes): Crypted secret to encrypt
  131. """
  132. splited = crypted.split(self.separator)
  133. if len(splited) != 3:
  134. raise TypeError("Secret is in invalid format.")
  135. hashed = bytes.fromhex(splited[0])
  136. cipher = bytes.fromhex(splited[1])
  137. crypted = bytes.fromhex(splited[2])
  138. if len(cipher) != secret_coder.__get_cipher_length():
  139. raise TypeError("Secret is in invalid format.")
  140. if len(hashed) != secret_coder.__get_hash_length():
  141. raise TypeError("Secret is in invalid format.")
  142. return hashed, cipher, crypted
  143. def validate(coded: str) -> bool:
  144. """
  145. This validate that coded secret is correct. That check hashed lenght
  146. and cipher lenght. This is static function.
  147. Parameters:
  148. coded (str): Codec secret to check correction of
  149. Returns:
  150. (bool): True when secret is corret, False when not.
  151. """
  152. splited = coded.split(secret_coder.__get_separator())
  153. if len(splited) != 3:
  154. return False
  155. hashed = bytes.fromhex(splited[0])
  156. cipher = bytes.fromhex(splited[1])
  157. if len(cipher) != secret_coder.__get_cipher_length():
  158. return False
  159. if len(hashed) != secret_coder.__get_hash_length():
  160. return False
  161. return True