crypto_engine.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. # ============================================================================
  2. # This file is part of Pwman3.
  3. #
  4. # Pwman3 is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License, version 2
  6. # as published by the Free Software Foundation;
  7. #
  8. # Pwman3 is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with Pwman3; if not, write to the Free Software
  15. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  16. # ============================================================================
  17. # Copyright (C) 2014 Oz Nahum <nahumoz@gmail.com>
  18. # ============================================================================
  19. from __future__ import print_function
  20. from Crypto.Cipher import AES
  21. from Crypto.Protocol.KDF import PBKDF2
  22. import base64
  23. import os
  24. import sys
  25. import binascii
  26. import time
  27. from pwman.util.callback import Callback
  28. import pwman.util.config as config
  29. if sys.version_info.major > 2: # pragma: no cover
  30. raw_input = input
  31. EncodeAES = lambda c, s: base64.b64encode(c.encrypt(s))
  32. DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip()
  33. def get_digest(password, salt):
  34. """
  35. Get a digest based on clear text password
  36. """
  37. iterations = 5000
  38. return PBKDF2(password, salt, dkLen=32, count=iterations)
  39. def authenticate(password, salt, digest):
  40. """
  41. salt and digest are stored in a file or a database
  42. """
  43. dig = get_digest(password, salt)
  44. return binascii.hexlify(dig) == digest
  45. def write_password(reader=raw_input):
  46. """
  47. Write a secret password as a hash and the salt used for this hash
  48. to a file
  49. """
  50. salt = base64.b64encode(os.urandom(32))
  51. passwd = reader("Please type in the secret key:")
  52. key = get_digest(passwd, salt)
  53. f = open('passwords.txt', 'wt')
  54. hpk = salt+'$6$'.encode('utf8')+binascii.hexlify(key)
  55. f.write(hpk.decode('utf-8'))
  56. f.close()
  57. def get_digest_from_file(filename):
  58. """
  59. Read a digested password and salt from the file
  60. """
  61. f = open(filename, 'rt')
  62. salt, digest = f.readline().split('$6$')
  63. return salt.encode('utf-8'), digest.encode('utf-8')
  64. def get_cipher(password, salt):
  65. """
  66. Create a chiper object from a hashed password
  67. """
  68. iv = os.urandom(AES.block_size)
  69. dig = get_digest(password, salt)
  70. chiper = AES.new(dig, AES.MODE_ECB, iv)
  71. return chiper
  72. def cli_auth(reader=raw_input):
  73. """
  74. Read password from the user, if the password is correct,
  75. finish the execution an return the password and salt which
  76. are read from the file.
  77. """
  78. salt, digest = get_digest_from_file('passwords.txt')
  79. while True:
  80. password = reader("Please type in your password:").encode('utf-8')
  81. if authenticate(password, salt, digest):
  82. return password, salt
  83. def prepare_data(text, block_size):
  84. """
  85. prepare data before encryption so the lenght matches the expected
  86. lenght by the algorithm.
  87. """
  88. num_blocks = len(text)//block_size + 1
  89. newdatasize = block_size*num_blocks
  90. return text.ljust(newdatasize)
  91. def save_a_secret_message(reader=raw_input):
  92. """
  93. PoC to show we can encrypt a message
  94. """
  95. secret_msg = """This is a very important message! Learn Cryptography!!!"""
  96. # the secret message will be encrypted with the secret password found
  97. # in the file
  98. passwd, salt = cli_auth(reader=reader)
  99. cipher = get_cipher(passwd, salt)
  100. # explictly destroy password, so now there is no clear text reference
  101. # to the input given by the user
  102. del(passwd)
  103. msg = EncodeAES(cipher, prepare_data(secret_msg, AES.block_size))
  104. with open('secret.enc', 'wt') as s:
  105. s.write(msg.decode())
  106. print("The cipher message is:", msg.decode())
  107. def read_a_secret_message(reader=raw_input):
  108. """
  109. PoC to show we can decrypt a message
  110. """
  111. passwd, salt = cli_auth(reader)
  112. cipher = get_cipher(passwd, salt)
  113. del(passwd)
  114. with open('secret.enc') as s:
  115. msg = s.readline()
  116. print("The decrypted secret message is:")
  117. decoded = DecodeAES(cipher, msg)
  118. print(decoded)
  119. class CryptoEngine(object): # pagma: no cover
  120. _timeoutcount = 0
  121. _instance = None
  122. _instance_new = None
  123. _callback = None
  124. @classmethod
  125. def get(cls, dbver=0.5):
  126. if CryptoEngine._instance:
  127. return CryptoEngine._instance
  128. if CryptoEngine._instance_new:
  129. return CryptoEngine._instance_new
  130. algo = config.get_value("Encryption", "algorithm")
  131. if not algo:
  132. raise Exception("Parameters missing, no algorithm given")
  133. try:
  134. timeout = int(config.get_value("Encryption", "timeout"))
  135. except ValueError:
  136. timeout = -1
  137. salt, digest = get_digest_from_file('passwords.txt')
  138. kwargs = {'algorithm': algo,
  139. 'timeout': timeout, 'salt': salt, 'digest': digest}
  140. if dbver >= 0.5:
  141. CryptoEngine._instance_new = CryptoEngine(**kwargs)
  142. return CryptoEngine._instance_new
  143. def __init__(self, salt=None, digest=None, algorithm='AES',
  144. timeout=-1):
  145. """
  146. Initialise the Cryptographic Engine
  147. """
  148. self._algo = algorithm
  149. self._digest = digest if digest else None
  150. self._salt = salt if salt else None
  151. self._timeout = timeout
  152. self._cipher = None
  153. def authenticate(self, password):
  154. """
  155. salt and digest are stored in a file or a database
  156. """
  157. dig = get_digest(password, self._salt)
  158. if binascii.hexlify(dig) == self._digest:
  159. CryptoEngine._timeoutcount = time.time()
  160. self._cipher = get_cipher(password, self._salt)
  161. return True
  162. return False
  163. def _is_authenticated(self):
  164. if not self._is_timedout() and self._cipher is not None:
  165. return True
  166. return False
  167. def _is_timedout(self):
  168. if self._timeout > 0:
  169. if (time.time() - CryptoEngine._timeoutcount) > self._timeout:
  170. self._cipher = None
  171. return True
  172. return False
  173. def changepassword(self, reader=raw_input):
  174. self._keycrypted = self._create_password(reader=reader)
  175. return self._keycrypted
  176. @property
  177. def callback(self):
  178. """
  179. return call back function
  180. """
  181. return self._callback
  182. @callback.setter
  183. def callback(self, callback):
  184. if isinstance(callback, Callback):
  185. self._callback = callback
  186. self._getsecret = callback.getsecret
  187. self._getnewsecret = callback.getnewsecret
  188. else:
  189. raise Exception("callback must be an instance of Callback!")
  190. def _create_password(self, reader=raw_input):
  191. """
  192. Create a secret password as a hash and the salt used for this hash.
  193. Change reader to manipulate how input is given.
  194. """
  195. salt = base64.b64encode(os.urandom(32))
  196. passwd = reader("Please type in the secret key:")
  197. key = self._get_digest(passwd, salt)
  198. hpk = salt+'$6$'.encode('utf8')+binascii.hexlify(key)
  199. return hpk.decode('utf-8')
  200. def _get_digest(self, password, salt):
  201. """
  202. Get a digest based on clear text password
  203. """
  204. iterations = 5000
  205. return PBKDF2(password, salt, dkLen=32, count=iterations)
  206. if __name__ == '__main__': # pragma: no cover
  207. if '-i' in sys.argv:
  208. write_password()
  209. save_a_secret_message()
  210. read_a_secret_message()