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