Project is now properly encrypting the archive, and now I just need to ensure proper decryption is happening. Also need to work on cleaning up clear text versions of the crypt project files. Need to write tests for flushing out edge cases.
175 lines
5.5 KiB
Python
175 lines
5.5 KiB
Python
#!/usr/bin/env python
|
|
|
|
import crochet
|
|
import glob
|
|
import os
|
|
import pappyproxy
|
|
import scrypt
|
|
import shutil
|
|
import twisted
|
|
|
|
from . import compress
|
|
from .util import PappyException
|
|
from base64 import b64encode, b64decode
|
|
from cryptography.fernet import Fernet
|
|
from twisted.internet import reactor, defer
|
|
|
|
class Crypto(object):
|
|
def __init__(self, sessconfig):
|
|
self.config = sessconfig
|
|
self.archive = self.config.archive
|
|
self.compressor = compress.Compress(sessconfig)
|
|
self.key = None
|
|
self.password = None
|
|
self.salt = None
|
|
|
|
def encrypt_project(self):
|
|
"""
|
|
Compress and encrypt the project files, deleting clear-text files afterwards
|
|
"""
|
|
|
|
# Get the password and salt, then derive the key
|
|
self.crypto_ramp_up()
|
|
|
|
# Instantiate the crypto module
|
|
fern = Fernet(self.key)
|
|
|
|
# Create project archive and crypto archive
|
|
self.compressor.compress_project()
|
|
archive_file = open(self.archive, 'rb').read()
|
|
archive_crypt = open(self.config.crypt_file, 'wb')
|
|
|
|
# Encrypt the archive read as a bytestring
|
|
crypt_token = fern.encrypt(archive_file)
|
|
archive_crypt.write(crypt_token)
|
|
|
|
# Delete clear-text files
|
|
# delete_clear_files()
|
|
|
|
# Leave crypto working directory
|
|
os.chdir('../')
|
|
|
|
def decrypt_project(self):
|
|
"""
|
|
Decrypt and decompress the project files
|
|
"""
|
|
|
|
# Get the password and salt, then derive the key
|
|
self.crypto_ramp_up()
|
|
|
|
crypto_path = self.config.crypt_dir
|
|
|
|
if not os.path.isdir(crypto_path):
|
|
os.mkdir(crypto_path)
|
|
|
|
if os.path.isfile(self.config.crypt_file):
|
|
# Derive the key
|
|
key = self.crypto_ramp_up()
|
|
fern = Fernet(key)
|
|
|
|
# Decrypt the project archive
|
|
archive_crypt = open(self.config.crypt_file, 'rb')
|
|
archive = fern.decrypt(archive_crypt)
|
|
|
|
shutil.move(archive, crypto_path)
|
|
os.chdir(crypto_path)
|
|
self.compressor.decompress_project()
|
|
else:
|
|
project_files = self.config.get_project_files()
|
|
for pf in project_files:
|
|
shutil.copy2(pf, crypto_path)
|
|
os.chdir(crypto_path)
|
|
|
|
def crypto_ramp_up(self):
|
|
if not self.password:
|
|
self.get_password()
|
|
self.set_salt()
|
|
self.derive_key()
|
|
|
|
def delete_clear_files(self):
|
|
"""
|
|
Deletes all clear-text files left in the project directory.
|
|
"""
|
|
project_files = self.config.get_project_files()
|
|
for pf in project_files:
|
|
os.remove(pf)
|
|
|
|
def delete_crypt_files(self):
|
|
"""
|
|
Deletes all encrypted-text files in the project directory.
|
|
Forces generation of new salt after opening and closing the project.
|
|
Adds security in the case of a one-time compromise of the system.
|
|
"""
|
|
os.remove(self.config.salt_file)
|
|
os.remove(self.config.crypt_file)
|
|
|
|
def create_salt_file(self):
|
|
salt_file = open(self.config.salt_file, 'wb')
|
|
|
|
if not self.config.salt:
|
|
self.set_salt()
|
|
|
|
salt_file.write(self.config.salt)
|
|
salt_file.close()
|
|
|
|
def set_salt_from_file(self):
|
|
try:
|
|
salt_file = open(self.config.salt_file, 'rb')
|
|
self.salt = salt_file.readline().strip()
|
|
except:
|
|
raise PappyException("Unable to read project.salt")
|
|
|
|
def set_salt(self):
|
|
if os.path.isfile(self.config.salt_file):
|
|
self.set_salt_from_file()
|
|
else:
|
|
self.salt = os.urandom(16)
|
|
|
|
def get_password(self):
|
|
"""
|
|
Retrieve password from the user. Raise an exception if the
|
|
password is not capable of utf-8 encoding.
|
|
"""
|
|
encoded_passwd = ""
|
|
try:
|
|
passwd = raw_input("Enter a password: ")
|
|
self.password = passwd.encode("utf-8")
|
|
except:
|
|
raise PappyException("Invalid password, try again")
|
|
|
|
def derive_key(self):
|
|
"""
|
|
Derive a key sufficient for use as a cryptographic key
|
|
used to encrypt the project (currently: cryptography.Fernet).
|
|
|
|
cryptography.Fernet utilizes AES-CBC-128, requiring a 32-byte key.
|
|
Parameter notes from the py-scrypt source-code:
|
|
https://bitbucket.org/mhallin/py-scrypt/
|
|
|
|
Compute scrypt(password, salt, N, r, p, buflen).
|
|
|
|
The parameters r, p, and buflen must satisfy r * p < 2^30 and
|
|
buflen <= (2^32 - 1) * 32. The parameter N must be a power of 2
|
|
greater than 1. N, r and p must all be positive.
|
|
|
|
Notes for Python 2:
|
|
- `password` and `salt` must be str instances
|
|
- The result will be a str instance
|
|
|
|
Notes for Python 3:
|
|
- `password` and `salt` can be both str and bytes. If they are str
|
|
instances, they wil be encoded with utf-8.
|
|
- The result will be a bytes instance
|
|
|
|
Exceptions raised:
|
|
- TypeError on invalid input
|
|
- scrypt.error if scrypt failed
|
|
"""
|
|
|
|
try:
|
|
if not self.key:
|
|
self.key = b64encode(scrypt.hash(self.password, self.salt, buflen=32))
|
|
except TypeError, e:
|
|
raise PappyException("Scrypt failed with type error: ", e)
|
|
except scrypt.error, e:
|
|
raise PappyException("Scrypt failed with internal error: ", e)
|