secret_coder.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  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(self.__cipher_length)
  75. @property
  76. def __cipher_length(self) -> int:
  77. """ This return lenght of random cipher. """
  78. return 16
  79. def __cipher(self, iv: bytes) -> object:
  80. """ This return new AES coder to work with coders. """
  81. return Crypto.Cipher.AES.new(
  82. self.password,
  83. self.mode,
  84. iv = iv
  85. )
  86. def __hash(self, secret: str) -> bytes:
  87. """
  88. This function hash given string, and return it as
  89. bytes.
  90. Parameters:
  91. secret (str): Content to hash
  92. Returns:
  93. (bytes): Hashed content
  94. """
  95. secret = secret.encode("UTF-8")
  96. hashed = hashlib.sha256(secret)
  97. return hashed.digest()
  98. @property
  99. def separator(self) -> str:
  100. """ This return separator used to packing secret. """
  101. return ":"
  102. def __pack(self, hashed: bytes, cipher: bytes, crypted: bytes) -> str:
  103. """
  104. This function pack full content required for secret, to coder format.
  105. Parameters:
  106. hashed (bytes): Hashed secret
  107. cipher (bytes): Ciper used in crypter
  108. crypted (bytes): Crypted secret
  109. Returns:
  110. (str): Coded crypted secret
  111. """
  112. hashed = hashed.hex()
  113. cipher = cipher.hex()
  114. crypted = crypted.hex()
  115. return hashed + self.separator + cipher + self.separator + crypted
  116. def __unpack(self, crypted: str) -> [bytes, bytes, bytes]:
  117. """
  118. This function unpack given coded secret into bytes parts. When
  119. secret is not valid raise TypeError.
  120. Parameters:
  121. crypted (str): Packed secret
  122. Returns:
  123. (bytes): Hashed secret before encryption to validate password
  124. (bytes): Cipher required by AES to work
  125. (bytes): Crypted secret to encrypt
  126. """
  127. splited = crypted.split(self.separator)
  128. if len(splited) != 3:
  129. raise TypeError("Secret is in invalid format.")
  130. hashed = bytes.fromhex(splited[0])
  131. cipher = bytes.fromhex(splited[1])
  132. crypted = bytes.fromhex(splited[2])
  133. if len(cipher) != self.__cipher_length:
  134. raise TypeError("Secret is in invalid format.")
  135. return hashed, cipher, crypted