EncrypC.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. import hashlib
  4. import os
  5. import sys
  6. import threading
  7. import tkinter as tk
  8. from pathlib import Path
  9. from tkinter import *
  10. from tkinter import filedialog, messagebox
  11. from Cryptodome.Cipher import AES
  12. class EncryptionTool:
  13. def __init__(
  14. self,
  15. user_file,
  16. user_key,
  17. user_salt,
  18. ):
  19. # get the path to input file
  20. self.user_file = user_file
  21. self.input_file_size = os.path.getsize(self.user_file)
  22. self.chunk_size = 1024
  23. self.total_chunks = self.input_file_size // self.chunk_size + 1
  24. # convert the key and salt to bytes
  25. self.user_key = bytes(user_key, "utf-8")
  26. self.user_salt = bytes(user_key[::-1], "utf-8")
  27. # get the file extension
  28. self.file_extension = self.user_file.split(".")[-1]
  29. # hash type for hashing key and salt
  30. self.hash_type = "SHA256"
  31. # encrypted file name
  32. self.encrypt_output_file = (
  33. ".".join(self.user_file.split(".")[:-1])
  34. + "."
  35. + self.file_extension
  36. + ".encr"
  37. )
  38. # decrypted file name
  39. self.decrypt_output_file = self.user_file[:-5].split(".")
  40. self.decrypt_output_file = (
  41. ".".join(self.decrypt_output_file[:-1])
  42. + "_decrypted."
  43. + self.decrypt_output_file[-1]
  44. )
  45. # dictionary to store hashed key and salt
  46. self.hashed_key_salt = dict()
  47. # hash key and salt into 16 bit hashes
  48. self.hash_key_salt()
  49. def read_in_chunks(self, file_object, chunk_size=1024):
  50. """Lazy function (generator) to read a file piece by piece.
  51. Default chunk size: 1k.
  52. """
  53. while True:
  54. data = file_object.read(chunk_size)
  55. if not data:
  56. break
  57. yield data
  58. def encrypt(self):
  59. # create a cipher object
  60. cipher_object = AES.new(
  61. self.hashed_key_salt["key"], AES.MODE_CFB, self.hashed_key_salt["salt"]
  62. )
  63. self.abort() # if the output file already exists, remove it first
  64. input_file = open(self.user_file, "rb")
  65. output_file = open(self.encrypt_output_file, "ab")
  66. done_chunks = 0
  67. for piece in self.read_in_chunks(input_file, self.chunk_size):
  68. encrypted_content = cipher_object.encrypt(piece)
  69. output_file.write(encrypted_content)
  70. done_chunks += 1
  71. yield done_chunks / self.total_chunks * 100
  72. input_file.close()
  73. output_file.close()
  74. # clean up the cipher object
  75. del cipher_object
  76. def decrypt(self):
  77. # exact same as above function except in reverse
  78. cipher_object = AES.new(
  79. self.hashed_key_salt["key"], AES.MODE_CFB, self.hashed_key_salt["salt"]
  80. )
  81. self.abort() # if the output file already exists, remove it first
  82. input_file = open(self.user_file, "rb")
  83. output_file = open(self.decrypt_output_file, "xb")
  84. done_chunks = 0
  85. for piece in self.read_in_chunks(input_file):
  86. decrypted_content = cipher_object.decrypt(piece)
  87. output_file.write(decrypted_content)
  88. done_chunks += 1
  89. yield done_chunks / self.total_chunks * 100
  90. input_file.close()
  91. output_file.close()
  92. # clean up the cipher object
  93. del cipher_object
  94. def abort(self):
  95. if os.path.isfile(self.encrypt_output_file):
  96. os.remove(self.encrypt_output_file)
  97. if os.path.isfile(self.decrypt_output_file):
  98. os.remove(self.decrypt_output_file)
  99. def hash_key_salt(self):
  100. # --- convert key to hash
  101. # create a new hash object
  102. hasher = hashlib.new(self.hash_type)
  103. hasher.update(self.user_key)
  104. # turn the output key hash into 32 bytes (256 bits)
  105. self.hashed_key_salt["key"] = bytes(hasher.hexdigest()[:32], "utf-8")
  106. # clean up hash object
  107. del hasher
  108. # --- convert salt to hash
  109. # create a new hash object
  110. hasher = hashlib.new(self.hash_type)
  111. hasher.update(self.user_salt)
  112. # turn the output salt hash into 16 bytes (128 bits)
  113. self.hashed_key_salt["salt"] = bytes(hasher.hexdigest()[:16], "utf-8")
  114. # clean up hash object
  115. del hasher
  116. class MainWindow:
  117. """GUI Wrapper"""
  118. # configure root directory path relative to this file
  119. THIS_FOLDER_G = ""
  120. if getattr(sys, "frozen", False):
  121. # frozen
  122. THIS_FOLDER_G = os.path.dirname(sys.executable)
  123. else:
  124. # unfrozen
  125. THIS_FOLDER_G = os.path.dirname(os.path.realpath(__file__))
  126. def __init__(self, root):
  127. self.root = root
  128. self._cipher = None
  129. self._file_url = tk.StringVar()
  130. self._secret_key = tk.StringVar()
  131. self._secret_key_check = tk.StringVar()
  132. self._salt = tk.StringVar()
  133. self._status = tk.StringVar()
  134. self._status.set("---")
  135. self.should_cancel = False
  136. root.title("EncrypC")
  137. root.configure(bg="#eeeeee")
  138. try:
  139. icon_img = tk.Image(
  140. "photo", file=self.THIS_FOLDER_G + "./files/encrypc.ico"
  141. )
  142. root.call("wm", "iconphoto", root._w, icon_img)
  143. except Exception:
  144. pass
  145. self.menu_bar = tk.Menu(root, bg="#eeeeee", relief=tk.FLAT)
  146. self.menu_bar.add_command(label="Help!", command=self.show_help_callback)
  147. self.menu_bar.add_command(label="About", command=self.show_about)
  148. root.configure(menu=self.menu_bar)
  149. self.file_entry_label = tk.Label(
  150. root,
  151. text="Enter File Path Or Click SELECT FILE Button",
  152. bg="#eeeeee",
  153. anchor=tk.W,
  154. )
  155. self.file_entry_label.grid(
  156. padx=12,
  157. pady=(8, 0),
  158. ipadx=0,
  159. ipady=1,
  160. row=0,
  161. column=0,
  162. columnspan=4,
  163. sticky=tk.W + tk.E + tk.N + tk.S,
  164. )
  165. self.file_entry = tk.Entry(
  166. root,
  167. textvariable=self._file_url,
  168. bg="#fff",
  169. exportselection=0,
  170. relief=tk.FLAT,
  171. )
  172. self.file_entry.grid(
  173. padx=15,
  174. pady=6,
  175. ipadx=8,
  176. ipady=8,
  177. row=1,
  178. column=0,
  179. columnspan=4,
  180. sticky=tk.W + tk.E + tk.N + tk.S,
  181. )
  182. self.select_btn = tk.Button(
  183. root,
  184. text="SELECT FILE",
  185. command=self.selectfile_callback,
  186. width=42,
  187. bg="#3498db",
  188. fg="#ffffff",
  189. bd=2,
  190. relief=tk.FLAT,
  191. )
  192. self.select_btn.grid(
  193. padx=15,
  194. pady=8,
  195. ipadx=24,
  196. ipady=6,
  197. row=2,
  198. column=0,
  199. columnspan=4,
  200. sticky=tk.W + tk.E + tk.N + tk.S,
  201. )
  202. self.key_entry_label1 = tk.Label(
  203. root,
  204. text="Enter Key (To be Remembered while Decryption)",
  205. bg="#eeeeee",
  206. anchor=tk.W,
  207. )
  208. self.key_entry_label1.grid(
  209. padx=12,
  210. pady=(8, 0),
  211. ipadx=0,
  212. ipady=1,
  213. row=3,
  214. column=0,
  215. columnspan=4,
  216. sticky=tk.W + tk.E + tk.N + tk.S,
  217. )
  218. self.key_entry1 = tk.Entry(
  219. root,
  220. textvariable=self._secret_key,
  221. bg="#fff",
  222. exportselection=0,
  223. relief=tk.FLAT,
  224. )
  225. self.key_entry1.grid(
  226. padx=15,
  227. pady=6,
  228. ipadx=8,
  229. ipady=8,
  230. row=4,
  231. column=0,
  232. columnspan=4,
  233. sticky=tk.W + tk.E + tk.N + tk.S,
  234. )
  235. self.key_entry_label2 = tk.Label(
  236. root, text="Re-enter Key (Validation)", bg="#eeeeee", anchor=tk.W
  237. )
  238. self.key_entry_label2.grid(
  239. padx=12,
  240. pady=(8, 0),
  241. ipadx=0,
  242. ipady=1,
  243. row=5,
  244. column=0,
  245. columnspan=4,
  246. sticky=tk.W + tk.E + tk.N + tk.S,
  247. )
  248. self.key_entry2 = tk.Entry(
  249. root,
  250. textvariable=self._secret_key_check,
  251. bg="#fff",
  252. exportselection=0,
  253. relief=tk.FLAT,
  254. )
  255. self.key_entry2.grid(
  256. padx=15,
  257. pady=6,
  258. ipadx=8,
  259. ipady=8,
  260. row=6,
  261. column=0,
  262. columnspan=4,
  263. sticky=tk.W + tk.E + tk.N + tk.S,
  264. )
  265. self.encrypt_btn = tk.Button(
  266. root,
  267. text="ENCRYPT",
  268. command=self.e_check_callback,
  269. bg="#27ae60",
  270. fg="#ffffff",
  271. bd=2,
  272. relief=tk.FLAT,
  273. )
  274. self.encrypt_btn.grid(
  275. padx=15,
  276. pady=8,
  277. ipadx=24,
  278. ipady=6,
  279. row=7,
  280. column=0,
  281. columnspan=2,
  282. sticky=tk.W + tk.E + tk.N + tk.S,
  283. )
  284. self.decrypt_btn = tk.Button(
  285. root,
  286. text="DECRYPT",
  287. command=self.d_check_callback,
  288. bg="#27ae60",
  289. fg="#ffffff",
  290. bd=2,
  291. relief=tk.FLAT,
  292. )
  293. self.decrypt_btn.grid(
  294. padx=15,
  295. pady=8,
  296. ipadx=24,
  297. ipady=6,
  298. row=7,
  299. column=2,
  300. columnspan=2,
  301. sticky=tk.W + tk.E + tk.N + tk.S,
  302. )
  303. self.reset_btn = tk.Button(
  304. root,
  305. text="CLEAR",
  306. command=self.reset_callback,
  307. bg="#717d7e",
  308. fg="#ffffff",
  309. bd=2,
  310. relief=tk.FLAT,
  311. )
  312. self.reset_btn.grid(
  313. padx=15,
  314. pady=8,
  315. ipadx=24,
  316. ipady=6,
  317. row=8,
  318. column=0,
  319. columnspan=2,
  320. sticky=tk.W + tk.E + tk.N + tk.S,
  321. )
  322. self.stop_btn = tk.Button(
  323. root,
  324. text="STOP",
  325. command=self.cancel_callback,
  326. bg="#aaaaaa",
  327. fg="#ffffff",
  328. bd=2,
  329. state="disabled",
  330. relief=tk.FLAT,
  331. )
  332. self.stop_btn.grid(
  333. padx=15,
  334. pady=8,
  335. ipadx=24,
  336. ipady=6,
  337. row=8,
  338. column=2,
  339. columnspan=2,
  340. sticky=tk.W + tk.E + tk.N + tk.S,
  341. )
  342. self.status_label = tk.Label(
  343. root,
  344. textvariable=self._status,
  345. bg="#eeeeee",
  346. anchor=tk.W,
  347. justify=tk.LEFT,
  348. relief=tk.FLAT,
  349. wraplength=350,
  350. )
  351. self.status_label.grid(
  352. padx=12,
  353. pady=(0, 12),
  354. ipadx=0,
  355. ipady=1,
  356. row=9,
  357. column=0,
  358. columnspan=4,
  359. sticky=tk.W + tk.E + tk.N + tk.S,
  360. )
  361. tk.Grid.columnconfigure(root, 0, weight=1)
  362. tk.Grid.columnconfigure(root, 1, weight=1)
  363. tk.Grid.columnconfigure(root, 2, weight=1)
  364. tk.Grid.columnconfigure(root, 3, weight=1)
  365. def selectfile_callback(self):
  366. try:
  367. name = filedialog.askopenfile()
  368. self._file_url.set(name.name)
  369. except Exception as e:
  370. self._status.set(e)
  371. self.status_label.update()
  372. def freeze_controls(self):
  373. self.file_entry.configure(state="disabled")
  374. self.key_entry1.configure(state="disabled")
  375. self.key_entry2.configure(state="disabled")
  376. self.select_btn.configure(state="disabled", bg="#aaaaaa")
  377. self.encrypt_btn.configure(state="disabled", bg="#aaaaaa")
  378. self.decrypt_btn.configure(state="disabled", bg="#aaaaaa")
  379. self.reset_btn.configure(state="disabled", bg="#aaaaaa")
  380. self.stop_btn.configure(state="normal", bg="#e74c3c")
  381. self.status_label.update()
  382. def unfreeze_controls(self):
  383. self.file_entry.configure(state="normal")
  384. self.key_entry1.configure(state="normal")
  385. self.key_entry2.configure(state="normal")
  386. self.select_btn.configure(state="normal", bg="#3498db")
  387. self.encrypt_btn.configure(state="normal", bg="#27ae60")
  388. self.decrypt_btn.configure(state="normal", bg="#27ae60")
  389. self.reset_btn.configure(state="normal", bg="#717d7e")
  390. self.stop_btn.configure(state="disabled", bg="#aaaaaa")
  391. self.status_label.update()
  392. def e_check_callback(self):
  393. newPath = Path(self._file_url.get())
  394. if newPath.is_file():
  395. pass
  396. else:
  397. messagebox.showinfo("EncrypC", "Please Enter a valid File URL !!")
  398. return
  399. if len(self._secret_key.get()) == 0:
  400. messagebox.showinfo("EncrypC", "Please Enter a valid Secret Key !!")
  401. return
  402. elif self._secret_key.get() != self._secret_key_check.get():
  403. messagebox.showinfo("EncrypC", "Passwords do not match !!")
  404. return
  405. self.encrypt_callback()
  406. def d_check_callback(self):
  407. newPath = Path(self._file_url.get())
  408. if newPath.is_file():
  409. pass
  410. else:
  411. messagebox.showinfo("EncrypC", "Please Enter a valid File URL !!")
  412. return
  413. if self._file_url.get()[-4:] != "encr":
  414. messagebox.showinfo(
  415. "EncrypC",
  416. """Provided File is not an Encrypted File !!
  417. Please Enter an Encrypted File to Decrypt.""",
  418. )
  419. return
  420. if len(self._secret_key.get()) == 0:
  421. messagebox.showinfo("EncrypC", "Please Enter a Secret Key !!")
  422. return
  423. elif self._secret_key.get() != self._secret_key_check.get():
  424. messagebox.showinfo("EncrypC", "Passwords do not match !!")
  425. return
  426. self.decrypt_callback()
  427. def encrypt_callback(self):
  428. t1 = threading.Thread(target=self.encrypt_execute)
  429. t1.start()
  430. def encrypt_execute(self):
  431. self.freeze_controls()
  432. try:
  433. self._cipher = EncryptionTool(
  434. self._file_url.get(), self._secret_key.get(), self._salt.get()
  435. )
  436. for percentage in self._cipher.encrypt():
  437. if self.should_cancel:
  438. break
  439. percentage = "{0:.2f}%".format(percentage)
  440. self._status.set(percentage)
  441. self.status_label.update()
  442. if self.should_cancel:
  443. self._cipher.abort()
  444. self._status.set("Cancellation Successful !!")
  445. messagebox.showinfo("EncrypC", "Cancellation Successful !!")
  446. self._cipher = None
  447. self.should_cancel = False
  448. self.unfreeze_controls()
  449. return
  450. self._cipher = None
  451. self.should_cancel = False
  452. self._status.set("File Hash Successful !!")
  453. messagebox.showinfo("EncrypC", "File Hash Successful !!")
  454. except Exception as e:
  455. self._status.set(e)
  456. self.unfreeze_controls()
  457. def decrypt_callback(self):
  458. t2 = threading.Thread(target=self.decrypt_execute)
  459. t2.start()
  460. def decrypt_execute(self):
  461. self.freeze_controls()
  462. try:
  463. self._cipher = EncryptionTool(
  464. self._file_url.get(), self._secret_key.get(), self._salt.get()
  465. )
  466. for percentage in self._cipher.decrypt():
  467. if self.should_cancel:
  468. break
  469. percentage = "{0:.2f}%".format(percentage)
  470. self._status.set(percentage)
  471. self.status_label.update()
  472. if self.should_cancel:
  473. self._cipher.abort()
  474. self._status.set("Cancellation Successful !!")
  475. messagebox.showinfo("EncrypC", "Cancellation Successful !!")
  476. self._cipher = None
  477. self.should_cancel = False
  478. self.unfreeze_controls()
  479. return
  480. self._cipher = None
  481. self.should_cancel = False
  482. self._status.set("File Decryption Successful !!")
  483. messagebox.showinfo("EncrypC", "File Decryption Successful !!")
  484. except Exception as e:
  485. self._status.set(e)
  486. self.unfreeze_controls()
  487. def reset_callback(self):
  488. self._cipher = None
  489. self._file_url.set("")
  490. self._secret_key.set("")
  491. self._salt.set("")
  492. self._status.set("---")
  493. def cancel_callback(self):
  494. self.should_cancel = True
  495. def show_help_callback(self):
  496. messagebox.showinfo(
  497. "Tutorial",
  498. """1. Open the Application and Click SELECT FILE Button to select your file e.g. "mydoc.pdf" (OR You can add path manually).
  499. 2. Enter your Key (This should be alphanumeric letters). Remember this so you can Decrypt the file later. (Else you'll lose your file permanently)
  500. 3. Click ENCRYPT Button to encrypt the file. A new encrypted file with ".encr" extention e.g. "mydoc.pdf.encr" will be created in the same directory where the "mydoc.pdf" is.
  501. 4. When you want to Decrypt a file you, will select the file with the ".encr" extention and Enter your Key which you chose at the time of Hash. Click DECRYPT Button to decrypt. The decrypted file will be of the same name as before with the suffix "decrypted" for e.g. "mydoc_decrypted.pdf".
  502. 5. Click CLEAR Button to reset the input fields and status bar.""",
  503. )
  504. def show_about(self):
  505. messagebox.showinfo(
  506. "EncrypC v1.2.0",
  507. """EncrypC is a File Hash Tool based on AES Algorithm.
  508. Managed by Dhruv Panchal.
  509. https://github.com/dhhruv""",
  510. )
  511. if __name__ == "__main__":
  512. ROOT = tk.Tk()
  513. MAIN_WINDOW = MainWindow(ROOT)
  514. bundle_dir = getattr(sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__)))
  515. path_to_ico = os.path.abspath(os.path.join(bundle_dir, "encrypc.ico"))
  516. ROOT.iconbitmap(path_to_ico)
  517. ROOT.resizable(height=False, width=False)
  518. ROOT.mainloop()