A whisper can be stronger, as an atom is stronger, than a whole mountain.”
~Louise Nevelson

crypt.py

Google+ Pinterest LinkedIn Tumblr +

Like crypt.sh but way better, this program will encrypt/decrypt all specified files in the current subdirectory and recursively downwards.  It’s 100 lines shorter than its BASH counterpart, infinitely more reliable and incredibly fast (10,000 files in < 4 seconds!).

This can be valuable before you backup to the cloud, for example.  It’ll blow through your Documents folder fast, so you can encrypt everything, upload for backup and then decrypt again with the upload being by far the slowest part of the process.

crypt.py
#!/usr/bin/python3

# This script written on Linux Mint 17.3 with Python 3.4
# en/decrypting is multithreaded, srm wiping/shredding has to be single threaded

import sys
import os
import fnmatch
from pathlib import PurePath as ppath
import glob #handles wildcards in listings better
import apt
import subprocess
import multiprocessing as mp
import getpass

aptpackages = ["haveged","openssl","python3","secure-delete"]
action = ""
workinglist = []

#cipher options include bf-cbc | des-ede3-cbc | cast5-cbc | aes-256-cbc | camellia-256-cbc
cipher="bf-cbc"

# There are reasons not to aggressively wipe the source file(s), such as if you're already
# encrypted with whole-drive encryption, over a network or on a USB drive that doesn't
# have read-under (in which case as for all SSD's, physical destruction is what's really needed).
# Note - decrypted sources are always wiped quickly since contents were encrypted
aggwipe=False

mplist = mp.Queue()
mpcorelimit = 0 # 0=all, or 2,4,6,8,10,12 . Consider drive I/O, 8-12 generally okay with SSD's

#-----------------------------------  Don't recommend changes beneath this line

def errout (problem):
    print(problem)
    exit(1)

def showhelp():
    print('crypt.py - a multithreaded mass encryption/decryption program')
    print("* works on network files, but don't get too aggressive (and network files can't be remotely wiped)")
    print("* Enclosing filenameorfilepattern in quotes is important")
    # because without quotes, python interprets every *.doc filename in current folder as a parameter
    print("")
    print('Usage : crypt.py [ (e | d) | r ] "filenameorfilepattern"')
    print("e - encrypt    d - decrypt     r - recurse current dir and down")
    print("")
    print('Example :   crypt.py e "filenumber1.doc"')
    print('            crypt.py er "*.doc"')
    print('            crypt.py d "*.doc.ssl"')
    print('            crypt.py dr "*.xls.ssl"')
    print("")
    print("")
    print("pkg requirements : " + str(aptpackages))
    checkpackages()
    exit(0)

def parseparams():
    returnlist=[]
    args = ""
    # position 0=self, 1=flags, 2=file descriptors
    if len(sys.argv) < 3 or sys.argv[1].lower() == "help":
        showhelp()

    args = sys.argv
    args.pop(0)    # don't need position zero (scriptname)
    action = str(args.pop(0).lower())
    fullpath = os.path.abspath(args[0])

    if "e" not in action and "d" not in action:
        showhelp()
        errout("incorrect parameters")

    if "r" not in action:
        blob = glob.glob(fullpath) #interprets wildcards & abs names correctly
        for i in blob:
            returnlist.append(i)
        return(action,returnlist)

    else: #process recursively
        pattern = os.path.basename(fullpath)
        dirname = fullpath.rstrip(pattern)

        # for this you have to walk for all files, then find those that match
        for root, dirs, files in os.walk(os.path.abspath(dirname)):
            for filename in fnmatch.filter(files,pattern):
                returnlist.append(os.path.join(root, filename))

        return(action,returnlist)

def checkpackages():
    print("Checking packages...")
    havepkg = True
    cache = apt.Cache()
    cache.open()
    for i in aptpackages:
        if not cache[i].is_installed:
            print("*    " + str(i) + " is not installed, need to 'apt-get install " + str(i))
            havepkg = False

    if not havepkg:
        errout("packages missing - exiting")

    # is haveged actually running?
    result = subprocess.check_output(['ps','-e'])
    if "haveged" not in str(result).lower():
        print('haveged is installed but it\'s not running. you need to "service start haveged"')
        errout("haveged not running - exiting")

def decryptitem(infile):
    print(".", end='')
    rawname=str(infile)
    outfile=rawname.rsplit(".", 1)[0]

    process = subprocess.Popen(['openssl', 'enc', '-d', '-salt', '-' + cipher, \
        '-pass', 'pass:'+userpass1, '-in', infile, '-out', outfile], \
        stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    stdout, stderr = process.communicate()

    if str(stderr) == "b''" or "":
        return("")
    else:
        return(rawname + ": " + str(stderr).split("\\n",1)[0]) #*** redundant under all error conditions? Need to check.
        #return(str(stderr).split("\\n",1)[0])

def cryptitem(infile):
    print(".", end='')
    process = subprocess.Popen(['openssl', 'enc', '-e', '-salt', '-' + cipher, \
        '-pass', 'pass:'+userpass1, '-in', infile, '-out', infile+'.ssl'], \
        stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    stdout, stderr = process.communicate()

    rawname=str(infile)
    if str(stderr) == "b''" or "":
        return("")
    else:
        #return(rawname + ": " + str(stderr))
        return(str(stderr).split("\\n",1)[0])

def encrypt(workinglist):
    print("Encrypting the following " + str(len(workinglist)) + " files:")
    for i in workinglist:
        print("    " + str(i))
    print("")

    try:
        input("Hit ENTER to continue or CNTL-C to abort..")
    except:
        errout("Aborted")

    global userpass1 #because hard to pass to mp.cryptitem otherwise
    goodpw = False
    while not goodpw:
        userpass1 = getpass.getpass(prompt="Password: ")
        if (cipher == "aes-256-cbc" or cipher == "camellia-256-cbc") and len(userpass1) < 32:
            print("*   WARNING - short password - using a " + str(len(userpass1)*8) + " bit key for a 256 bit cipher")
        if cipher == "bf-cbc":
            print("*    cipher strength: " + str(len(userpass1)*8) + " bits (448 max).")
        if cipher == "des-ede3-cbc" and len(userpass1) < 21:
            print("*    WARNING - short password - using a " + str(len(userpass1)*8) + " bit key for a 168 bit cipher")

        userpass2 = getpass.getpass(prompt="Again: ")

        if userpass1 == userpass2:
            goodpw = True
        else:
            print("* passwords don't match, try again.")

    print("Encrypting", end='')
    pool = mp.Pool(processes=mp.cpu_count() * 2)
    if not mpcorelimit == 0 and pool > mpcorelimit:
        pool = mpcorelimit

    pool_outputs = pool.map(cryptitem, workinglist)
    pool.close() # no more tasks left
    pool.join() # wrap up tasks and join

    userpass1 = userpass2 = "000000000000000000000000000000000" #clear the pw from memory
    print("")
    goodrun=True
    for i in pool_outputs:
        if not i == "":
            goodrun = False
    if goodrun:
        return(True)
    else:
        print("*    ---- errors detected")
        for i in pool_outputs:
            print(i)
        return(False)

def decrypt(workinglist):
    print("Decrypting the following " + str(len(workinglist)) + " files:")
    for i in workinglist:
        print("    " + str(i))
    print("")

    try:
        input("Hit ENTER to continue or CNTL-C to abort..")
    except:
        errout("Aborted")

    global userpass1 #because hard to pass to mp.decryptitem otherwise
    userpass1 = getpass.getpass(prompt="Password: ")

    print("Decrypting", end='')
    pool = mp.Pool(processes=mp.cpu_count() * 2)
    if not mpcorelimit == 0 and pool > mpcorelimit:
        pool = mpcorelimit

    pool_outputs = pool.map(decryptitem, workinglist)
    pool.close() # no more tasks left
    pool.join() # wrap up tasks and join

    userpass1 = "000000000000000000000000000000000" #clear the pw from memory
    print("")
    goodrun=True
    for i in pool_outputs:
        if not i == "":
            goodrun = False

    if goodrun:
        return(True)
    else:
        print("*    ---- errors detected")
        for i in pool_outputs:
            print(i)
        return(False)

def shredlist(workinglist):
    # shredding has to be single threaded
    for shredthis in workinglist:
        if aggwipe:
            print("Wiping: " + str(shredthis))
            process = subprocess.Popen(['srm', '-d', shredthis], \
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        # if decrypting, wiping can be faster since original is encrypted
        elif "d" in action or not aggwipe:
            print("QuickWiping: " + str(shredthis))
            process = subprocess.Popen(['srm', '-f', '-l', '-d', shredthis], \
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)

        stdout, stderr = process.communicate()

        if not str(stderr) == "b''" or "":
            print(str(stderr))
            errout("shred error")
    print("Flushing disk cache..")
    subprocess.Popen(['sync']) #syncing forces write to every file system
    return(True)

#main()
action, workinglist = parseparams()

if "e" in action:
    goodresult = encrypt(workinglist)
elif "d" in action:
    goodresult = decrypt(workinglist)

if goodresult:
    if shredlist(workinglist):  #if return True, we're good
        print("Task completed.")
    else:
        errout("Problems encountered - see above output")

else: #badresult
    print("")
    print("problem with en/decryption detected, filewipe aborted")
    errout("manual shred line like : srm -d filename")
Share.

Leave A Reply

Secured By miniOrange