Python knows how to execute clear text Python source code. If you encrypt the source file, it no longer contains valid Python source and cannot be directly executed.
There are 2 possible ways here. First is to only obfuscate your source. You should be aware that obfuscation is not security, and a user could recover some Python source with some work (not necessarily the original source, because comments and doc strings could have been stripped off and variable names could have been changed). You could read How do I protect Python code? and google for python obfuscate to find some possible ways of obfuscating Python source and their trade-offs.
The good news with obfuscated source is that it can be used by anybody without any password.
Second way is to encrypt the source. If you use a decent tool, you can assume that it will be impossible to read or execute the file without knowing the key. In that sense, vim
crypto has not the highest possible reputation. In the simplest way (for example with your example vim -x
) you will have to decrypt the file to execute it. Unfortunately, good crypto modules are not shipped in a standard Python installation and must be downloaded from pypi. Well known crypto modules include pycrypto and cryptography.
You can then encrypt the most part of the code, and then at run time ask for the key, decrypt it and execute it. Still a serious job but feasible.
Alternatively, you could build in another language (C/C++) a decryptor that decrypts the remaining of the file and feed it into a python interpretor, but functionally, this is only a variant of the above method.
As per your comment I assume that you want to encrypt the source code and decrypt it (with the password) at run time. The princips are:
- build a Python script that will take another arbitrary Python script, encode it with a secure crypto module and prepend it with some decrypting code.
- at run time, the prepended code will ask for the password, decrypt the encrypted code exec it
The builder could be (this code uses the cryptography module):
import cryptography.fernet
import cryptography.hazmat.primitives.kdf.pbkdf2
import cryptography.hazmat.primitives.hashes
import cryptography.hazmat.backends
import base64
import os
import sys
# the encryption function
def encrypt_source(infile, outfile, passwd):
with open(infile, 'rb') as fdin: # read original file
plain_data = fdin.read()
salt, key = gen_key(passwd) # derive a key from the password
f = cryptography.fernet.Fernet(key)
crypted_data = f.encrypt(plain_data) # encrypt the original code
with open(outfile, "w") as fdout: # prepend a decoding block
fdout.write("""#! /usr/bin/env python
import cryptography.fernet
import cryptography.hazmat.primitives.kdf.pbkdf2
import cryptography.hazmat.primitives.hashes
import cryptography.hazmat.backends
import base64
import os
def gen_key(passwd, salt): # key derivation
kdf = cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC(
algorithm = cryptography.hazmat.primitives.hashes.SHA256(),
length = 32,
salt = salt,
iterations = 100000,
backend = cryptography.hazmat.backends.default_backend()
)
return base64.urlsafe_b64encode(kdf.derive(passwd))
passwd = input("Password:") # ask for the password
salt = base64.decodebytes({})
key = gen_key(passwd.encode(), salt) # derive the key from the password and the original salt
crypted_source = base64.decodebytes( # decode (base64) the crypted source
b'''{}'''
)
f = cryptography.fernet.Fernet(key)
plain_source = f.decrypt(crypted_source) # decrypt it
exec(plain_source) # and exec it
""".format(base64.encodebytes(salt),
base64.encodebytes(crypted_data).decode()))
# derive a key from a password and a random salt
def gen_key(passwd, salt=None):
if salt is None: salt = os.urandom(16)
kdf = cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC(
algorithm = cryptography.hazmat.primitives.hashes.SHA256(),
length = 32,
salt = salt,
iterations = 100000,
backend = cryptography.hazmat.backends.default_backend()
)
return salt, base64.urlsafe_b64encode(kdf.derive(passwd))
if __name__ == '__main__':
if len(sys.argv) != 3:
print("Usage {} infile outfile".format(sys.argv[0]))
sys.exit(1)
passwd = input("Password:").encode() # ask for a password
encrypt_source(sys.argv[1], sys.argv[2], passwd) # and generates an encrypted Python script