1+ import hashlib
2+ import hmac
3+ import scrypt
4+ import secrets
5+ import os
6+ import time
7+ import re
8+ import psutil
9+ from Crypto .Cipher import AES
10+ from Crypto .Util .Padding import pad , unpad
11+ import base64
12+ import torch
13+ import torchcsprng as csprng
14+ import numpy as np
15+
16+ def hkdf_scrypt (password , salt , length , n , r , p ):
17+ # HKDF extraction step
18+ prk = hmac .new (salt .encode (), password .encode (), hashlib .sha256 ).digest ()
19+
20+ # HKDF expansion step
21+ info = b'Scrypt key derivation'
22+ t = b''
23+ okm = b''
24+ while len (okm ) < length :
25+ t = hmac .new (prk , t + info + bytes ([len (t ) + 1 ]), hashlib .sha256 ).digest ()
26+ okm += scrypt .hash (t , salt .encode (), n , r , p , length )
27+
28+ return okm [:length ]
29+
30+
31+ def encrypt_file (file_path , key , ratio , device ):
32+ # Read the file contents
33+ with open (file_path , 'rb' ) as file :
34+ plaintext = file .read ()
35+
36+ # Workload division according to the ratio between cpu and gpu
37+ workload_division = int (len (plaintext ) * ratio )
38+
39+ # plaintext for cpu
40+ cpu_plaintext = plaintext [:workload_division ]
41+
42+ # Generate a random IV (Initialization Vector)
43+ iv = secrets .token_bytes (AES .block_size )
44+
45+ # Create AES cipher object with key and mode (ECB)
46+ cipher = AES .new (key , AES .MODE_ECB )
47+
48+ # Pad the cpu plaintext to match AES block size
49+ padded_cpu_plaintext = pad (cpu_plaintext , AES .block_size )
50+
51+ # Encrypt the padded cpu plaintext
52+ cpu_ciphertext = cipher .encrypt (padded_cpu_plaintext )
53+
54+ if (ratio != 1 ): # if GPU is now available, only use CPU
55+ # Encrypt in GPU
56+
57+ # transfer key into tensor to use in GPU
58+ key_array = np .frombuffer (key , dtype = np .uint8 )
59+ gpu_key = torch .from_numpy (key_array )
60+ gpu_key = gpu_key .to (device )
61+
62+ # plaintext for gpu
63+ gpu_plaintext = plaintext [workload_division :]
64+
65+ # transfer gpu_plaintext into tensor to use in GPU
66+ gpu_plaintext_array = np .frombuffer (gpu_plaintext , dtype = np .uint8 )
67+ gpu_plaintext_tensor = torch .from_numpy (gpu_plaintext_array )
68+ gpu_plaintext_tensor = gpu_plaintext_tensor .to (device )
69+
70+ # Pad the gpu_plaintext_tensor to match AES block size
71+ padded_gpu_plaintext = pad (gpu_plaintext , 16 )
72+
73+ padded_gpu_plaintext_array = np .frombuffer (padded_gpu_plaintext , dtype = np .uint8 )
74+ padded_gpu_plaintext_tensor = torch .from_numpy (padded_gpu_plaintext_array )
75+ padded_gpu_plaintext_tensor = padded_gpu_plaintext_tensor .to (device )
76+
77+ # encrypt padded_gpu_plaintext_tensor in GPU
78+ gpu_encrypted = torch .empty (len (padded_gpu_plaintext_tensor ), dtype = torch .int8 , device = device )
79+ csprng .encrypt (padded_gpu_plaintext_tensor , gpu_encrypted , gpu_key , "aes128" , "ecb" )
80+
81+ # transfer gpu_encrypted results into bytes to use in cpu
82+ gpu_encrypted_numpy = gpu_encrypted .cpu ().numpy ()
83+ gpu_ciphertext = gpu_encrypted_numpy .tobytes ()
84+
85+ # merge iv, cpu_ciphertext, gpu_ciphertext
86+ results = iv + cpu_ciphertext + gpu_ciphertext
87+ else :
88+ results = iv + cpu_ciphertext
89+
90+ # Write the IV and encrypted data back to the file
91+ with open (file_path , 'wb' ) as file :
92+ file .write (results )
93+
94+ def decrypt_file (file_path , key , ratio , device ):
95+ # Read the file contents
96+ with open (file_path , 'rb' ) as file :
97+ ciphertext = file .read ()
98+
99+ # Extract the IV and ciphertext from the file
100+ iv = ciphertext [:AES .block_size ]
101+ ciphertext = ciphertext [AES .block_size :]
102+
103+ # Workload division according to the ratio between cpu and gpu
104+ workload_division = 16 + (int ((len (ciphertext ) - 16 ) * ratio / 16 )) * 16
105+
106+ # ciphertext for cput
107+ cpu_ciphertext = ciphertext [:workload_division ]
108+
109+ # Create AES cipher object with key and mode (ECB)
110+ cipher = AES .new (key , AES .MODE_ECB )
111+
112+ # Decrypt the cpu_ciphertext
113+ cpu_plaintext = cipher .decrypt (cpu_ciphertext )
114+
115+ # Unpad the decrypted cpu_plaintext
116+ cpu_plaintext = unpad (cpu_plaintext , AES .block_size )
117+
118+
119+ if (ratio != 1 ): # if GPU is now available, only use CPU
120+ # Decrypt in GPU
121+
122+ # transfer key into tensor to use in GPU
123+ key_array = np .frombuffer (key , dtype = np .uint8 )
124+ gpu_key = torch .from_numpy (key_array )
125+ gpu_key = gpu_key .to (device )
126+
127+ # cipertext for gpu
128+ gpu_ciphertext = ciphertext [workload_division :]
129+
130+ # transfer gpu_ciphertext into tensor to use in GPU
131+ encrypted_input_array = np .frombuffer (gpu_ciphertext , dtype = np .uint8 )
132+ encrypted_input_tensor = torch .from_numpy (encrypted_input_array )
133+ encrypted_input_tensor = encrypted_input_tensor .to (device )
134+
135+ # Decrypt encrypted_input_tensor in GPU
136+ gpu_decrypted = torch .empty_like (encrypted_input_tensor )
137+ csprng .decrypt (encrypted_input_tensor , gpu_decrypted , gpu_key , "aes128" , "ecb" )
138+
139+ # transfer gpu_encrypted results into bytes to use in cpu
140+ gpu_decrypted_numpy = gpu_decrypted .cpu ().numpy ()
141+ gpu_plaintext = gpu_decrypted_numpy .tobytes ()
142+
143+ # Unpad the decrypted gpu_plaintext
144+ gpu_plaintext = unpad (gpu_plaintext , 16 )
145+
146+ # Merge cpu_plaintext and gpu_plaintext
147+ results = cpu_plaintext + gpu_plaintext
148+ else :
149+ results = cpu_plaintext
150+
151+ # Write the decrypted data back to the file
152+ with open (file_path , 'wb' ) as file :
153+ file .write (results )
154+
155+
156+ def cpu_gpu_ratio ():
157+
158+ #Define the ration between cpu and gpu
159+ gpu_total = torch .cuda .device_count ()
160+
161+ # Usage CPU
162+ cpu_percent = psutil .cpu_percent (interval = 0.1 )
163+
164+ # Usage RAM
165+ memory_percent = psutil .virtual_memory ().percent
166+
167+ print (f"CPU Usage: { cpu_percent } % - Memory Usage: { memory_percent } " )
168+
169+ # Set the Ratio between CPU and GPU according to the CPU and RAM Usage
170+ if (gpu_total == 0 ):
171+ ratio = 1
172+ elif (memory_percent > 90 or cpu_percent > 90 ):
173+ ratio = 0.1
174+ elif (memory_percent > 70 or cpu_percent > 70 ):
175+ ratio = 0.3
176+ elif (memory_percent > 40 or cpu_percent > 40 ):
177+ ratio = 0.5
178+ else :
179+ ratio = 0.7
180+ return ratio
181+
182+
183+ def encrypt_folder (folder_path , key , mode , ratio , device ):
184+ start_time = time .time ()
185+ for root , dirs , files in os .walk (folder_path ):
186+ for file in files :
187+ file_path = os .path .join (root , file )
188+ if mode == 'encrypt' :
189+ encrypt_file (file_path , key , ratio , device )
190+ print (f'Encrypted: { file_path } ' )
191+ elif mode == 'decrypt' :
192+ decrypt_file (file_path , key , ratio , device )
193+ print (f'Decrypted: { file_path } ' )
194+
195+ # Monitor system resources while processing files
196+ cpu_percent = psutil .cpu_percent (interval = 0.1 )
197+ memory_percent = psutil .virtual_memory ().percent
198+ print (f"CPU Usage: { cpu_percent } % - Memory Usage: { memory_percent } % -Ratio between CPU and GPU: { ratio } " )
199+
200+ end_time = time .time ()
201+ execution_time = end_time - start_time
202+ print (f"Execution Time: { execution_time :.3f} seconds ({ execution_time * 1000 :.3f} milliseconds)" )
203+
204+ if __name__ == '__main__' :
205+ # Define regular expressions for input validation
206+ password_regex = re .compile (r'^.{8,}$' ) # Minimum 8 characters
207+ salt_regex = re .compile (r'^[a-fA-F0-9]{32}$' ) # 32 hexadecimal characters
208+ folder_path_regex = re .compile (r'^[a-zA-Z0-9_./\\-]+$' ) # Alphanumeric, underscore, dot, forward slash, backslash, and hyphen
209+
210+ # Prompt the user for inputs
211+ while True :
212+ try :
213+ password = "1234567890" # password = input("Enter the password: ")
214+ if not password_regex .match (password ):
215+ print ("Invalid password. Password must be at least 8 characters long." )
216+ continue
217+
218+ salt = "12345678901234567890123456789012" # salt = input("Enter the salt: ")
219+ if not salt_regex .match (salt ):
220+ print ("Invalid salt. Salt must be a 32-character hexadecimal string." )
221+ continue
222+
223+ length = 16 # length = int(input("Enter the desired key length in bytes: "))
224+ n = 2 ^ 10 # n = int(input("Enter the value for 'n': "))
225+ r = 8 # r = int(input("Enter the value for 'r': "))
226+ p = 5 # p = int(input("Enter the value for 'p': "))
227+
228+
229+
230+ break
231+ except ValueError :
232+ print ("Invalid input. Please try again." )
233+
234+ # Prompt the user to input the folder path
235+ # while True:
236+
237+ # if not folder_path_regex.match(folder_path):
238+ # print("Invalid folder path. Folder path must be alphanumeric and can contain underscore, dot, forward slash, backslash, and hyphen.")
239+ # continue
240+ # if os.path.isdir(folder_path):
241+ # break
242+ # else:
243+ # print("Invalid folder path. Please try again.")
244+ #folder_path = input("Enter the folder path: ")
245+ mode = 'encrypt'
246+ folder_path = "E:\enc"
247+
248+
249+ # Prompt the user to select the mode: 'encrypt' or 'decrypt'
250+ # while True:
251+ # mode = 'encrypt' #mode = input("Enter the mode ('encrypt' or 'decrypt'): ")
252+ # if mode in ['encrypt', 'decrypt']:
253+ # breakon
254+ # else:
255+ # print("Invalid mode. Please try again.")
256+
257+ device = torch .device ('cuda' if torch .cuda .is_available () else 'cpu' )
258+ ratio = cpu_gpu_ratio ()
259+
260+ # Call the hkdf_scrypt function with user inputs
261+ derived_key = hkdf_scrypt (password , salt , length , n , r , p )
262+
263+ # Encode the derived key using base64
264+ encoded_key = base64 .b64encode (derived_key ).decode ()
265+
266+ if mode == 'encrypt' :
267+ # Store the encoded key in a secure location (e.g., a file or database)
268+ with open ("backup_key.txt" , "w" ) as file :
269+ file .write (encoded_key )
270+
271+ # Encrypt or decrypt the files within the folder using AES-256
272+ encrypt_folder (folder_path , derived_key , mode , ratio , device )
0 commit comments