I'm trying to set up encryption on a portion of a project that I'm working on. Right now, I'm trying an implementation similar to this. As an extra layer of security, I have all of my Python source code compiled into .pyd
files via Cython. What I want to do is unencrypt a .pyd
module, import it, and then delete the unencrypted file immediately afterwords while keeping the module available in memory. How do I do this?
Here is the code I'm using for encryption/decryption:
from hashlib import md5
from Crypto.Cipher import AES
from Crypto import Random
def derive_key_and_iv(password, salt, key_length, iv_length):
d = d_i = ''.encode()
while len(d) < key_length + iv_length:
d_i = md5(d_i + password.encode() + salt).digest()
d += d_i
return d[:key_length], d[key_length:key_length+iv_length]
def encrypt(in_file, out_file, password, key_length=32):
bs = AES.block_size
salt = Random.new().read(bs - len('Salted__'))
key, iv = derive_key_and_iv(password, salt, key_length, bs)
cipher = AES.new(key, AES.MODE_CBC, iv)
out_file.write('Salted__'.encode() + salt)
finished = False
while not finished:
chunk = in_file.read(1024 * bs)
if len(chunk) == 0 or len(chunk) % bs != 0:
padding_length = (bs - len(chunk) % bs) or bs
chunk += (padding_length * chr(padding_length)).encode()
finished = True
out_file.write(cipher.encrypt(chunk))
def decrypt(in_file, out_file, password, key_length=32):
bs = AES.block_size
salt = in_file.read(bs)[len('Salted__'):]
key, iv = derive_key_and_iv(password, salt, key_length, bs)
cipher = AES.new(key, AES.MODE_CBC, iv)
next_chunk = b''
finished = False
while not finished:
chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024 * bs))
if len(next_chunk) == 0:
padding_length = chunk[-1]
chunk = chunk[:-padding_length]
finished = True
out_file.write(chunk)
I am using a test module called test_me_pls
with the following code, which is compiled into test_me_pls.pyd
:
def square(x):
return x * x
To test, I have done the following:
>>> import os
>>> origpath = r'C:\test\test_me_pls.pyd'
>>> encpath = r'C:\test\blue.rrr'
>>> decpath = r'C:\test\test_me_pls.pyd'
>>> key = 'hello'
>>> with open(origpath,'rb') as inf, open(encpath,'wb') as outf:
... encrypt(inf,outf,key)
>>> os.remove(origpath)
>>> import test_me_pls
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named 'test_me_pls'
So far so good. I got the file encrypted and deleted the unencrypted .pyd
file. Just to make sure it worked how I was expecting, I tried to import the deleted/encrypted module and the import failed as expected.
Next, I decrypt, import the module, and test it out:
>>> with open(encpath,'rb') as inf, open(decpath,'wb') as outf:
... decrypt(inf,outf,key)
>>> import test_me_pls
>>> test_me_pls.square(5)
25
Okay, decryption worked.
Now that I have the module imported, I want to delete the unencrypted .pyd
file:
>>> os.remove('test_me_pls.pyd')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
PermissionError: [WinError 5] Access is denied: 'test_me_pls.pyd'
Okay. Not what I was hoping for. And just to beat a dead horse:
>>> test_me_pls.__file__
'.\\test_me_pls.pyd'
>>> os.remove(test_me_pls.__file__)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
PermissionError: [WinError 5] Access is denied: '.\\test_me_pls.pyd'
Is there a way to do this?
NOTE: I understand that if someone really wants to get at my code, it doesn't matter that I have it compiled and encrypted because if they know what they're doing they can still get to the unencrypted
.pyd
file by using a debugger and setting a break point at the appropriate spot, uncompiling the .pyd
file, etc. etc. insert reverse engineering techniques here. What I do want is something that will prevent casual attempts to look at the code in this module. The lock on the front door to my house can easily be picked by someone with the right knowledge and tools, but that doesn't mean I don't want to bother locking my door.
2nd NOTE: If it makes any difference, I'm using Python 3.3