The point is obvious. There is more than one way to burn a book. And the world is full of people running about with lit matches. Every minority ... feels it has the will, the right, the duty to douse the kerosene, light the fuse. Every dimwit editor who sees himself as the source of all dreary blanc-mange plain porridge unleavened literature, licks his guillotine and eyes the neck of any author who dares to speak above a whisper or write above a nursery rhyme.”
Ray Bradbury

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.

[pastacode lang=”python” manual=”%23!%2Fusr%2Fbin%2Fpython3%0A%0A%23%20This%20script%20written%20on%20Linux%20Mint%2017.3%20with%20Python%203.4%0A%23%20en%2Fdecrypting%20is%20multithreaded%2C%20srm%20wiping%2Fshredding%20has%20to%20be%20single%20threaded%0A%0Aimport%20sys%0Aimport%20os%0Aimport%20fnmatch%0Afrom%20pathlib%20import%20PurePath%20as%20ppath%0Aimport%20glob%20%23handles%20wildcards%20in%20listings%20better%0Aimport%20apt%0Aimport%20subprocess%0Aimport%20multiprocessing%20as%20mp%0Aimport%20getpass%0A%0Aaptpackages%20%3D%20%5B%22haveged%22%2C%22openssl%22%2C%22python3%22%2C%22secure-delete%22%5D%0Aaction%20%3D%20%22%22%0Aworkinglist%20%3D%20%5B%5D%0A%0A%23cipher%20options%20include%20bf-cbc%20%7C%20des-ede3-cbc%20%7C%20cast5-cbc%20%7C%20aes-256-cbc%20%7C%20camellia-256-cbc%0Acipher%3D%22bf-cbc%22%0A%0A%23%20There%20are%20reasons%20not%20to%20aggressively%20wipe%20the%20source%20file(s)%2C%20such%20as%20if%20you’re%20already%0A%23%20encrypted%20with%20whole-drive%20encryption%2C%20over%20a%20network%20or%20on%20a%20USB%20drive%20that%20doesn’t%0A%23%20have%20read-under%20(in%20which%20case%20as%20for%20all%20SSD’s%2C%20physical%20destruction%20is%20what’s%20really%20needed).%0A%23%20Note%20-%20decrypted%20sources%20are%20always%20wiped%20quickly%20since%20contents%20were%20encrypted%0Aaggwipe%3DFalse%0A%0Amplist%20%3D%20mp.Queue()%0Ampcorelimit%20%3D%200%20%23%200%3Dall%2C%20or%202%2C4%2C6%2C8%2C10%2C12%20.%20Consider%20drive%20I%2FO%2C%208-12%20generally%20okay%20with%20SSD’s%0A%0A%23———————————–%20%20Don’t%20recommend%20changes%20beneath%20this%20line%0A%0Adef%20errout%20(problem)%3A%0A%20%20%20%20print(problem)%0A%20%20%20%20exit(1)%0A%0Adef%20showhelp()%3A%0A%20%20%20%20print(‘crypt.py%20-%20a%20multithreaded%20mass%20encryption%2Fdecryption%20program’)%0A%20%20%20%20print(%22*%20works%20on%20network%20files%2C%20but%20don’t%20get%20too%20aggressive%20(and%20network%20files%20can’t%20be%20remotely%20wiped)%22)%0A%20%20%20%20print(%22*%20Enclosing%20filenameorfilepattern%20in%20quotes%20is%20important%22)%0A%20%20%20%20%23%20because%20without%20quotes%2C%20python%20interprets%20every%20*.doc%20filename%20in%20current%20folder%20as%20a%20parameter%0A%20%20%20%20print(%22%22)%0A%20%20%20%20print(‘Usage%20%3A%20crypt.py%20%5B%20(e%20%7C%20d)%20%7C%20r%20%5D%20%22filenameorfilepattern%22’)%0A%20%20%20%20print(%22e%20-%20encrypt%20%20%20%20d%20-%20decrypt%20%20%20%20%20r%20-%20recurse%20current%20dir%20and%20down%22)%0A%20%20%20%20print(%22%22)%0A%20%20%20%20print(‘Example%20%3A%20%20%20crypt.py%20e%20%22filenumber1.doc%22’)%0A%20%20%20%20print(‘%20%20%20%20%20%20%20%20%20%20%20%20crypt.py%20er%20%22*.doc%22’)%0A%20%20%20%20print(‘%20%20%20%20%20%20%20%20%20%20%20%20crypt.py%20d%20%22*.doc.ssl%22’)%0A%20%20%20%20print(‘%20%20%20%20%20%20%20%20%20%20%20%20crypt.py%20dr%20%22*.xls.ssl%22′)%0A%20%20%20%20print(%22%22)%0A%20%20%20%20print(%22%22)%0A%20%20%20%20print(%22pkg%20requirements%20%3A%20%22%20%2B%20str(aptpackages))%0A%20%20%20%20checkpackages()%0A%20%20%20%20exit(0)%0A%0Adef%20parseparams()%3A%0A%20%20%20%20returnlist%3D%5B%5D%0A%20%20%20%20args%20%3D%20%22%22%0A%20%20%20%20%23%20position%200%3Dself%2C%201%3Dflags%2C%202%3Dfile%20descriptors%0A%20%20%20%20if%20len(sys.argv)%20%3C%203%20or%20sys.argv%5B1%5D.lower()%20%3D%3D%20%22help%22%3A%0A%20%20%20%20%20%20%20%20showhelp()%0A%0A%20%20%20%20args%20%3D%20sys.argv%0A%20%20%20%20args.pop(0)%20%20%20%20%23%20don’t%20need%20position%20zero%20(scriptname)%0A%20%20%20%20action%20%3D%20str(args.pop(0).lower())%0A%20%20%20%20fullpath%20%3D%20os.path.abspath(args%5B0%5D)%0A%0A%20%20%20%20if%20%22e%22%20not%20in%20action%20and%20%22d%22%20not%20in%20action%3A%0A%20%20%20%20%20%20%20%20showhelp()%0A%20%20%20%20%20%20%20%20errout(%22incorrect%20parameters%22)%0A%0A%20%20%20%20if%20%22r%22%20not%20in%20action%3A%0A%20%20%20%20%20%20%20%20blob%20%3D%20glob.glob(fullpath)%20%23interprets%20wildcards%20%26%20abs%20names%20correctly%0A%20%20%20%20%20%20%20%20for%20i%20in%20blob%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20returnlist.append(i)%0A%20%20%20%20%20%20%20%20return(action%2Creturnlist)%0A%0A%20%20%20%20else%3A%20%23process%20recursively%0A%20%20%20%20%20%20%20%20pattern%20%3D%20os.path.basename(fullpath)%0A%20%20%20%20%20%20%20%20dirname%20%3D%20fullpath.rstrip(pattern)%0A%0A%20%20%20%20%20%20%20%20%23%20for%20this%20you%20have%20to%20walk%20for%20all%20files%2C%20then%20find%20those%20that%20match%0A%20%20%20%20%20%20%20%20for%20root%2C%20dirs%2C%20files%20in%20os.walk(os.path.abspath(dirname))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20filename%20in%20fnmatch.filter(files%2Cpattern)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20returnlist.append(os.path.join(root%2C%20filename))%0A%0A%20%20%20%20%20%20%20%20return(action%2Creturnlist)%0A%0Adef%20checkpackages()%3A%0A%20%20%20%20print(%22Checking%20packages…%22)%0A%20%20%20%20havepkg%20%3D%20True%0A%20%20%20%20cache%20%3D%20apt.Cache()%0A%20%20%20%20cache.open()%0A%20%20%20%20for%20i%20in%20aptpackages%3A%0A%20%20%20%20%20%20%20%20if%20not%20cache%5Bi%5D.is_installed%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22*%20%20%20%20%22%20%2B%20str(i)%20%2B%20%22%20is%20not%20installed%2C%20need%20to%20’apt-get%20install%20%22%20%2B%20str(i))%0A%20%20%20%20%20%20%20%20%20%20%20%20havepkg%20%3D%20False%0A%0A%20%20%20%20if%20not%20havepkg%3A%0A%20%20%20%20%20%20%20%20errout(%22packages%20missing%20-%20exiting%22)%0A%0A%20%20%20%20%23%20is%20haveged%20actually%20running%3F%0A%20%20%20%20result%20%3D%20subprocess.check_output(%5B’ps’%2C’-e’%5D)%0A%20%20%20%20if%20%22haveged%22%20not%20in%20str(result).lower()%3A%0A%20%20%20%20%20%20%20%20print(‘haveged%20is%20installed%20but%20it%5C’s%20not%20running.%20you%20need%20to%20%22service%20start%20haveged%22′)%0A%20%20%20%20%20%20%20%20errout(%22haveged%20not%20running%20-%20exiting%22)%0A%0Adef%20decryptitem(infile)%3A%0A%20%20%20%20print(%22.%22%2C%20end%3D”)%0A%20%20%20%20rawname%3Dstr(infile)%0A%20%20%20%20outfile%3Drawname.rsplit(%22.%22%2C%201)%5B0%5D%0A%0A%20%20%20%20process%20%3D%20subprocess.Popen(%5B’openssl’%2C%20’enc’%2C%20′-d’%2C%20′-salt’%2C%20’-‘%20%2B%20cipher%2C%20%5C%0A%20%20%20%20%20%20%20%20′-pass’%2C%20’pass%3A’%2Buserpass1%2C%20′-in’%2C%20infile%2C%20′-out’%2C%20outfile%5D%2C%20%5C%0A%20%20%20%20%20%20%20%20stdout%3Dsubprocess.PIPE%2C%20stderr%3Dsubprocess.PIPE)%0A%0A%20%20%20%20stdout%2C%20stderr%20%3D%20process.communicate()%0A%0A%20%20%20%20if%20str(stderr)%20%3D%3D%20%22b”%22%20or%20%22%22%3A%0A%20%20%20%20%20%20%20%20return(%22%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20return(rawname%20%2B%20%22%3A%20%22%20%2B%20str(stderr).split(%22%5C%5Cn%22%2C1)%5B0%5D)%20%23***%20redundant%20under%20all%20error%20conditions%3F%20Need%20to%20check.%0A%20%20%20%20%20%20%20%20%23return(str(stderr).split(%22%5C%5Cn%22%2C1)%5B0%5D)%0A%0Adef%20cryptitem(infile)%3A%0A%20%20%20%20print(%22.%22%2C%20end%3D”)%0A%20%20%20%20process%20%3D%20subprocess.Popen(%5B’openssl’%2C%20’enc’%2C%20′-e’%2C%20′-salt’%2C%20’-‘%20%2B%20cipher%2C%20%5C%0A%20%20%20%20%20%20%20%20′-pass’%2C%20’pass%3A’%2Buserpass1%2C%20′-in’%2C%20infile%2C%20′-out’%2C%20infile%2B’.ssl’%5D%2C%20%5C%0A%20%20%20%20%20%20%20%20stdout%3Dsubprocess.PIPE%2C%20stderr%3Dsubprocess.PIPE)%0A%0A%20%20%20%20stdout%2C%20stderr%20%3D%20process.communicate()%0A%0A%20%20%20%20rawname%3Dstr(infile)%0A%20%20%20%20if%20str(stderr)%20%3D%3D%20%22b”%22%20or%20%22%22%3A%0A%20%20%20%20%20%20%20%20return(%22%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23return(rawname%20%2B%20%22%3A%20%22%20%2B%20str(stderr))%0A%20%20%20%20%20%20%20%20return(str(stderr).split(%22%5C%5Cn%22%2C1)%5B0%5D)%0A%0Adef%20encrypt(workinglist)%3A%0A%20%20%20%20print(%22Encrypting%20the%20following%20%22%20%2B%20str(len(workinglist))%20%2B%20%22%20files%3A%22)%0A%20%20%20%20for%20i%20in%20workinglist%3A%0A%20%20%20%20%20%20%20%20print(%22%20%20%20%20%22%20%2B%20str(i))%0A%20%20%20%20print(%22%22)%0A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20input(%22Hit%20ENTER%20to%20continue%20or%20CNTL-C%20to%20abort..%22)%0A%20%20%20%20except%3A%0A%20%20%20%20%20%20%20%20errout(%22Aborted%22)%0A%0A%20%20%20%20global%20userpass1%20%23because%20hard%20to%20pass%20to%20mp.cryptitem%20otherwise%0A%20%20%20%20goodpw%20%3D%20False%0A%20%20%20%20while%20not%20goodpw%3A%0A%20%20%20%20%20%20%20%20userpass1%20%3D%20getpass.getpass(prompt%3D%22Password%3A%20%22)%0A%20%20%20%20%20%20%20%20if%20(cipher%20%3D%3D%20%22aes-256-cbc%22%20or%20cipher%20%3D%3D%20%22camellia-256-cbc%22)%20and%20len(userpass1)%20%3C%2032%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22*%20%20%20WARNING%20-%20short%20password%20-%20using%20a%20%22%20%2B%20str(len(userpass1)*8)%20%2B%20%22%20bit%20key%20for%20a%20256%20bit%20cipher%22)%0A%20%20%20%20%20%20%20%20if%20cipher%20%3D%3D%20%22bf-cbc%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22*%20%20%20%20cipher%20strength%3A%20%22%20%2B%20str(len(userpass1)*8)%20%2B%20%22%20bits%20(448%20max).%22)%0A%20%20%20%20%20%20%20%20if%20cipher%20%3D%3D%20%22des-ede3-cbc%22%20and%20len(userpass1)%20%3C%2021%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22*%20%20%20%20WARNING%20-%20short%20password%20-%20using%20a%20%22%20%2B%20str(len(userpass1)*8)%20%2B%20%22%20bit%20key%20for%20a%20168%20bit%20cipher%22)%0A%0A%20%20%20%20%20%20%20%20userpass2%20%3D%20getpass.getpass(prompt%3D%22Again%3A%20%22)%0A%0A%20%20%20%20%20%20%20%20if%20userpass1%20%3D%3D%20userpass2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20goodpw%20%3D%20True%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22*%20passwords%20don’t%20match%2C%20try%20again.%22)%0A%0A%20%20%20%20print(%22Encrypting%22%2C%20end%3D”)%0A%20%20%20%20pool%20%3D%20mp.Pool(processes%3Dmp.cpu_count()%20*%202)%0A%20%20%20%20if%20not%20mpcorelimit%20%3D%3D%200%20and%20pool%20%3E%20mpcorelimit%3A%0A%20%20%20%20%20%20%20%20pool%20%3D%20mpcorelimit%0A%0A%20%20%20%20pool_outputs%20%3D%20pool.map(cryptitem%2C%20workinglist)%0A%20%20%20%20pool.close()%20%23%20no%20more%20tasks%20left%0A%20%20%20%20pool.join()%20%23%20wrap%20up%20tasks%20and%20join%0A%0A%20%20%20%20userpass1%20%3D%20userpass2%20%3D%20%22000000000000000000000000000000000%22%20%23clear%20the%20pw%20from%20memory%0A%20%20%20%20print(%22%22)%0A%20%20%20%20goodrun%3DTrue%0A%20%20%20%20for%20i%20in%20pool_outputs%3A%0A%20%20%20%20%20%20%20%20if%20not%20i%20%3D%3D%20%22%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20goodrun%20%3D%20False%0A%20%20%20%20if%20goodrun%3A%0A%20%20%20%20%20%20%20%20return(True)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(%22*%20%20%20%20—-%20errors%20detected%22)%0A%20%20%20%20%20%20%20%20for%20i%20in%20pool_outputs%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(i)%0A%20%20%20%20%20%20%20%20return(False)%0A%0Adef%20decrypt(workinglist)%3A%0A%20%20%20%20print(%22Decrypting%20the%20following%20%22%20%2B%20str(len(workinglist))%20%2B%20%22%20files%3A%22)%0A%20%20%20%20for%20i%20in%20workinglist%3A%0A%20%20%20%20%20%20%20%20print(%22%20%20%20%20%22%20%2B%20str(i))%0A%20%20%20%20print(%22%22)%0A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20input(%22Hit%20ENTER%20to%20continue%20or%20CNTL-C%20to%20abort..%22)%0A%20%20%20%20except%3A%0A%20%20%20%20%20%20%20%20errout(%22Aborted%22)%0A%0A%20%20%20%20global%20userpass1%20%23because%20hard%20to%20pass%20to%20mp.decryptitem%20otherwise%0A%20%20%20%20userpass1%20%3D%20getpass.getpass(prompt%3D%22Password%3A%20%22)%0A%0A%20%20%20%20print(%22Decrypting%22%2C%20end%3D”)%0A%20%20%20%20pool%20%3D%20mp.Pool(processes%3Dmp.cpu_count()%20*%202)%0A%20%20%20%20if%20not%20mpcorelimit%20%3D%3D%200%20and%20pool%20%3E%20mpcorelimit%3A%0A%20%20%20%20%20%20%20%20pool%20%3D%20mpcorelimit%0A%0A%20%20%20%20pool_outputs%20%3D%20pool.map(decryptitem%2C%20workinglist)%0A%20%20%20%20pool.close()%20%23%20no%20more%20tasks%20left%0A%20%20%20%20pool.join()%20%23%20wrap%20up%20tasks%20and%20join%0A%0A%20%20%20%20userpass1%20%3D%20%22000000000000000000000000000000000%22%20%23clear%20the%20pw%20from%20memory%0A%20%20%20%20print(%22%22)%0A%20%20%20%20goodrun%3DTrue%0A%20%20%20%20for%20i%20in%20pool_outputs%3A%0A%20%20%20%20%20%20%20%20if%20not%20i%20%3D%3D%20%22%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20goodrun%20%3D%20False%0A%0A%20%20%20%20if%20goodrun%3A%0A%20%20%20%20%20%20%20%20return(True)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(%22*%20%20%20%20—-%20errors%20detected%22)%0A%20%20%20%20%20%20%20%20for%20i%20in%20pool_outputs%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(i)%0A%20%20%20%20%20%20%20%20return(False)%0A%0Adef%20shredlist(workinglist)%3A%0A%20%20%20%20%23%20shredding%20has%20to%20be%20single%20threaded%0A%20%20%20%20for%20shredthis%20in%20workinglist%3A%0A%20%20%20%20%20%20%20%20if%20aggwipe%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22Wiping%3A%20%22%20%2B%20str(shredthis))%0A%20%20%20%20%20%20%20%20%20%20%20%20process%20%3D%20subprocess.Popen(%5B’srm’%2C%20′-d’%2C%20shredthis%5D%2C%20%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20stdout%3Dsubprocess.PIPE%2C%20stderr%3Dsubprocess.PIPE)%0A%20%20%20%20%20%20%20%20%23%20if%20decrypting%2C%20wiping%20can%20be%20faster%20since%20original%20is%20encrypted%0A%20%20%20%20%20%20%20%20elif%20%22d%22%20in%20action%20or%20not%20aggwipe%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22QuickWiping%3A%20%22%20%2B%20str(shredthis))%0A%20%20%20%20%20%20%20%20%20%20%20%20process%20%3D%20subprocess.Popen(%5B’srm’%2C%20′-f’%2C%20′-l’%2C%20′-d’%2C%20shredthis%5D%2C%20%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20stdout%3Dsubprocess.PIPE%2C%20stderr%3Dsubprocess.PIPE)%0A%0A%20%20%20%20%20%20%20%20stdout%2C%20stderr%20%3D%20process.communicate()%0A%0A%20%20%20%20%20%20%20%20if%20not%20str(stderr)%20%3D%3D%20%22b”%22%20or%20%22%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(str(stderr))%0A%20%20%20%20%20%20%20%20%20%20%20%20errout(%22shred%20error%22)%0A%20%20%20%20print(%22Flushing%20disk%20cache..%22)%0A%20%20%20%20subprocess.Popen(%5B’sync’%5D)%20%23syncing%20forces%20write%20to%20every%20file%20system%0A%20%20%20%20return(True)%0A%0A%23main()%0Aaction%2C%20workinglist%20%3D%20parseparams()%0A%0Aif%20%22e%22%20in%20action%3A%0A%20%20%20%20goodresult%20%3D%20encrypt(workinglist)%0Aelif%20%22d%22%20in%20action%3A%0A%20%20%20%20goodresult%20%3D%20decrypt(workinglist)%0A%0Aif%20goodresult%3A%0A%20%20%20%20if%20shredlist(workinglist)%3A%20%20%23if%20return%20True%2C%20we’re%20good%0A%20%20%20%20%20%20%20%20print(%22Task%20completed.%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20errout(%22Problems%20encountered%20-%20see%20above%20output%22)%0A%0Aelse%3A%20%23badresult%0A%20%20%20%20print(%22%22)%0A%20%20%20%20print(%22problem%20with%20en%2Fdecryption%20detected%2C%20filewipe%20aborted%22)%0A%20%20%20%20errout(%22manual%20shred%20line%20like%20%3A%20srm%20-d%20filename%22)” message=”crypt.py” highlight=”” provider=”manual”/]
Leave A Reply