├── lib ├── __init__.py ├── crypto │ ├── __init__.py │ ├── library │ │ ├── __init__.py │ │ ├── hash.py │ │ ├── package.py │ │ └── cryptor.py │ ├── settings.py │ ├── app.py │ └── decryptoapp.py ├── debug_runner.py └── profiler.py ├── tests ├── __init__.py ├── testdir5 │ ├── test4.txt │ ├── test5 │ ├── test2.txt.gpg │ ├── test1.txt.crypt │ ├── test4.txt.crypt │ └── test3.txt.asc ├── testdir9 │ ├── nontar.txt │ ├── tar_dir │ │ └── test.txt │ └── tar_dir_two │ │ └── test.txt ├── testdir1 │ ├── .testfile │ ├── testcrypt.txt.crypt │ ├── test1.txt │ ├── star.png │ ├── banana.gif │ └── tiger.jpg ├── testdir2 │ ├── test1.txt │ ├── test2.txt │ └── testcrypt.txt.crypt ├── testdir4 │ ├── .testfile │ └── testcrypt.txt.crypt ├── testdir6 │ ├── test1.txt │ ├── test2.txt │ ├── test2.txt.gpg │ └── test1.txt.crypt ├── testdir10 │ └── sourcedir │ │ ├── multifile │ │ ├── test.txt │ │ ├── test2.txt │ │ └── test3.txt │ │ ├── singlefile │ │ └── test.txt │ │ ├── subdirs │ │ └── dir1 │ │ │ └── test.txt │ │ ├── nofile.tar.crypt │ │ ├── subdirs.tar.crypt │ │ ├── multifile.tar.crypt │ │ └── singlefile.tar.crypt ├── testdir7 │ ├── uni_test.txt │ └── uni_test2.txt ├── testdir11 │ ├── esc_test.txt │ ├── esc_test2.txt │ └── testtar │ │ ├── esc_test.txt │ │ └── esc_test2.txt ├── testdir8 │ ├── test.py.crypt │ └── test2.py.crypt ├── attributions.txt ├── test.sh ├── test_hash-digests.py ├── test_unicode-passphrase.py ├── test_single-directory.py ├── test_multi-directory.py ├── test_compression-checks.py ├── test_multi-file.py ├── test_single-file.py ├── test_escaping-passphrase.py ├── test_decrypt-multi-file.py ├── test_ascii-armored.py ├── test_decrypt-multi-directory.py ├── test_decrypt-single-directory.py ├── test_tar-archive.py ├── test_decrypt-single-file.py └── test_decrypt-untar.py ├── MANIFEST.in ├── setup.cfg ├── AUTHORS.md ├── docs ├── TODO ├── LICENSE └── README.rst ├── tox.ini ├── scripts └── benchmarks.py ├── .gitignore ├── LICENSE ├── setup.py └── README.md /lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/crypto/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/testdir5/test4.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/crypto/library/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include docs * -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | universal = 1 -------------------------------------------------------------------------------- /tests/testdir9/nontar.txt: -------------------------------------------------------------------------------- 1 | testing file 2 | -------------------------------------------------------------------------------- /tests/testdir1/.testfile: -------------------------------------------------------------------------------- 1 | this is a dotfile 2 | -------------------------------------------------------------------------------- /tests/testdir2/test1.txt: -------------------------------------------------------------------------------- 1 | first text file 2 | -------------------------------------------------------------------------------- /tests/testdir4/.testfile: -------------------------------------------------------------------------------- 1 | this is a dotfile 2 | -------------------------------------------------------------------------------- /tests/testdir2/test2.txt: -------------------------------------------------------------------------------- 1 | another test text file 2 | -------------------------------------------------------------------------------- /tests/testdir9/tar_dir/test.txt: -------------------------------------------------------------------------------- 1 | here is some text 2 | -------------------------------------------------------------------------------- /tests/testdir1/testcrypt.txt.crypt: -------------------------------------------------------------------------------- 1 | test .crypt file 2 | -------------------------------------------------------------------------------- /tests/testdir9/tar_dir_two/test.txt: -------------------------------------------------------------------------------- 1 | this is some text 2 | -------------------------------------------------------------------------------- /tests/testdir6/test1.txt: -------------------------------------------------------------------------------- 1 | single line of text from test1.txt 2 | -------------------------------------------------------------------------------- /tests/testdir6/test2.txt: -------------------------------------------------------------------------------- 1 | single line of text from test2.txt 2 | -------------------------------------------------------------------------------- /tests/testdir10/sourcedir/multifile/test.txt: -------------------------------------------------------------------------------- 1 | testing file 1 2 | 3 | -------------------------------------------------------------------------------- /tests/testdir10/sourcedir/singlefile/test.txt: -------------------------------------------------------------------------------- 1 | testing file 1 2 | -------------------------------------------------------------------------------- /tests/testdir10/sourcedir/multifile/test2.txt: -------------------------------------------------------------------------------- 1 | testing file 2 2 | 3 | -------------------------------------------------------------------------------- /tests/testdir10/sourcedir/multifile/test3.txt: -------------------------------------------------------------------------------- 1 | testing file 3 2 | 3 | -------------------------------------------------------------------------------- /tests/testdir10/sourcedir/subdirs/dir1/test.txt: -------------------------------------------------------------------------------- 1 | this is text 2 | 3 | -------------------------------------------------------------------------------- /tests/testdir2/testcrypt.txt.crypt: -------------------------------------------------------------------------------- 1 | a test .crypt file in testdir2 2 | -------------------------------------------------------------------------------- /tests/testdir4/testcrypt.txt.crypt: -------------------------------------------------------------------------------- 1 | a test .crypt file in testdir4 2 | -------------------------------------------------------------------------------- /tests/testdir7/uni_test.txt: -------------------------------------------------------------------------------- 1 | a test unicode file with unicode ƷȦϺѠ 2 | -------------------------------------------------------------------------------- /tests/testdir1/test1.txt: -------------------------------------------------------------------------------- 1 | this is some text 2 | 3 | here is more 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/testdir7/uni_test2.txt: -------------------------------------------------------------------------------- 1 | another unicode test file with more unicode ƷȦϺѠ 2 | -------------------------------------------------------------------------------- /tests/testdir5/test5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akshay-Vs/crypto/HEAD/tests/testdir5/test5 -------------------------------------------------------------------------------- /tests/testdir1/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akshay-Vs/crypto/HEAD/tests/testdir1/star.png -------------------------------------------------------------------------------- /lib/debug_runner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Test code here 5 | -------------------------------------------------------------------------------- /tests/testdir1/banana.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akshay-Vs/crypto/HEAD/tests/testdir1/banana.gif -------------------------------------------------------------------------------- /tests/testdir1/tiger.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akshay-Vs/crypto/HEAD/tests/testdir1/tiger.jpg -------------------------------------------------------------------------------- /tests/testdir11/esc_test.txt: -------------------------------------------------------------------------------- 1 | an escape unicode file with escaped $@`!\%^&*()_-+=3'A\\\'M'\W<>?,./|[]{} 2 | -------------------------------------------------------------------------------- /tests/testdir5/test2.txt.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akshay-Vs/crypto/HEAD/tests/testdir5/test2.txt.gpg -------------------------------------------------------------------------------- /tests/testdir6/test2.txt.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akshay-Vs/crypto/HEAD/tests/testdir6/test2.txt.gpg -------------------------------------------------------------------------------- /tests/testdir8/test.py.crypt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akshay-Vs/crypto/HEAD/tests/testdir8/test.py.crypt -------------------------------------------------------------------------------- /tests/testdir5/test1.txt.crypt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akshay-Vs/crypto/HEAD/tests/testdir5/test1.txt.crypt -------------------------------------------------------------------------------- /tests/testdir5/test4.txt.crypt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akshay-Vs/crypto/HEAD/tests/testdir5/test4.txt.crypt -------------------------------------------------------------------------------- /tests/testdir6/test1.txt.crypt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akshay-Vs/crypto/HEAD/tests/testdir6/test1.txt.crypt -------------------------------------------------------------------------------- /tests/testdir8/test2.py.crypt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akshay-Vs/crypto/HEAD/tests/testdir8/test2.py.crypt -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | crypto is written and maintained by Christopher Simpkins. 2 | 3 | ## Contributors 4 | 5 | Christoph Russ -------------------------------------------------------------------------------- /tests/testdir11/esc_test2.txt: -------------------------------------------------------------------------------- 1 | another escape test file with more escapes $@`!\%^&*()_-+=3'A\\\'M'\W<>?,./|[]{} 2 | -------------------------------------------------------------------------------- /tests/testdir11/testtar/esc_test.txt: -------------------------------------------------------------------------------- 1 | an escape unicode file with escaped $@`!\%^&*()_-+=3'A\\\'M'\W<>?,./|[]{} 2 | -------------------------------------------------------------------------------- /tests/testdir11/testtar/esc_test2.txt: -------------------------------------------------------------------------------- 1 | another escape test file with more escapes $@`!\%^&*()_-+=3'A\\\'M'\W<>?,./|[]{} 2 | -------------------------------------------------------------------------------- /tests/testdir10/sourcedir/nofile.tar.crypt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akshay-Vs/crypto/HEAD/tests/testdir10/sourcedir/nofile.tar.crypt -------------------------------------------------------------------------------- /tests/testdir10/sourcedir/subdirs.tar.crypt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akshay-Vs/crypto/HEAD/tests/testdir10/sourcedir/subdirs.tar.crypt -------------------------------------------------------------------------------- /tests/testdir10/sourcedir/multifile.tar.crypt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akshay-Vs/crypto/HEAD/tests/testdir10/sourcedir/multifile.tar.crypt -------------------------------------------------------------------------------- /tests/testdir10/sourcedir/singlefile.tar.crypt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akshay-Vs/crypto/HEAD/tests/testdir10/sourcedir/singlefile.tar.crypt -------------------------------------------------------------------------------- /docs/TODO: -------------------------------------------------------------------------------- 1 | TODO: 2 | 3 | - provide option to eliminate the file mime type suffix string on the encrypted file 4 | 5 | - 2.0.0 : support multi-part encrypted files with local encrypted compiler (.shard) 6 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py35,pypy 3 | 4 | [testenv] 5 | deps= 6 | nose 7 | pexpect 8 | Naked 9 | commands=nosetests \ 10 | "--where=tests" 11 | 12 | [testenv:report] 13 | basepython = python3.5 14 | 15 | [testenv:check] 16 | basepython = python3.5 17 | -------------------------------------------------------------------------------- /scripts/benchmarks.py: -------------------------------------------------------------------------------- 1 | """gtime --verbose gpg -z 7 --batch --force-mdc --cipher-algo AES256 -o test.txt.crypt --passphrase test --symmetric lorem.docx""" 2 | 3 | """gtime --verbose gpg --batch --force-mdc --cipher-algo AES256 -o test.txt.crypt --passphrase test --symmetric pdfcomp.pdf""" 4 | -------------------------------------------------------------------------------- /tests/attributions.txt: -------------------------------------------------------------------------------- 1 | ATTRIBUTIONS 2 | 3 | testdir1/star.png : http://ellatutorials.deviantart.com/art/star-PNG-347446794 4 | testdir1/tiger.jpg : http://rs581.pbsrc.com/albums/ss252/sarge0946/Big%20Cats/tiger_181.jpg~c200 5 | testdir1/banana.gif : http://i.imgur.com/mZ9rekY.jpg 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/testdir5/test3.txt.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP MESSAGE----- 2 | Version: GnuPG/MacGPG2 v2.0.22 (Darwin) 3 | 4 | jA0ECQMClsl5WFabQITT0lgBHABX/10+cX5dJQFsMVDrV/v0qDJhccG1VDTuoZKa 5 | 5p6utzrD+1u1GWEIgbcf1oagjKh5wL0FY1yra+H9WxThhjeP0X7RC1rO6N0gliAE 6 | P1w8JZmQVgSc 7 | =koxE 8 | -----END PGP MESSAGE----- 9 | -------------------------------------------------------------------------------- /lib/crypto/library/hash.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import hashlib 5 | from Naked.toolshed.file import FileReader 6 | 7 | # ------------------------------------------------------------------------------ 8 | # PUBLIC 9 | # ------------------------------------------------------------------------------ 10 | def generate_hash(filepath): 11 | """Public function that reads a local file and generates a SHA256 hash digest for it""" 12 | fr = FileReader(filepath) 13 | data = fr.read_bin() 14 | return _calculate_sha256(data) 15 | 16 | 17 | # ------------------------------------------------------------------------------ 18 | # PRIVATE 19 | # ------------------------------------------------------------------------------ 20 | def _calculate_sha256(binary_string): 21 | """Private function that calculates a SHA256 hash digest for a binary string argument""" 22 | return hashlib.sha256(binary_string).hexdigest() 23 | 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib64/ 17 | parts/ 18 | sdist/ 19 | var/ 20 | *.egg-info/ 21 | .installed.cfg 22 | *.egg 23 | 24 | # PyInstaller 25 | # Usually these files are written by a python script from a template 26 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 27 | *.manifest 28 | *.spec 29 | 30 | # Installer logs 31 | pip-log.txt 32 | pip-delete-this-directory.txt 33 | 34 | # Unit test / coverage reports 35 | htmlcov/ 36 | .tox/ 37 | .coverage 38 | .cache 39 | nosetests.xml 40 | coverage.xml 41 | 42 | # Translations 43 | *.mo 44 | *.pot 45 | 46 | # Django stuff: 47 | *.log 48 | 49 | # Sphinx documentation 50 | docs/_build/ 51 | 52 | # PyBuilder 53 | target/ 54 | 55 | # Wing IDE local files (SHOULD commit .wpr files) 56 | *.wpu 57 | 58 | # PyCharm settings files 59 | .idea 60 | workspace.xml 61 | -------------------------------------------------------------------------------- /docs/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Christopher Simpkins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Chris Simpkins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /lib/crypto/library/package.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import tarfile 6 | from Naked.toolshed.system import stderr, dir_exists, file_exists 7 | 8 | # ------------------------------------------------------------------------------ 9 | # PUBLIC 10 | # ------------------------------------------------------------------------------ 11 | 12 | 13 | def generate_tar_files(directory_list): 14 | """Public function that reads a list of local directories and generates tar archives from them""" 15 | 16 | tar_file_list = [] 17 | 18 | for directory in directory_list: 19 | if dir_exists(directory): 20 | _generate_tar(directory) # create the tar archive 21 | tar_file_list.append(directory + '.tar') # append the tar archive filename to the returned tar_file_list list 22 | else: 23 | stderr("The directory '" + directory + "' does not exist and a tar archive could not be created from it.", exit=1) 24 | 25 | return tar_file_list 26 | 27 | 28 | def remove_tar_files(file_list): 29 | """Public function that removes temporary tar archive files in a local directory""" 30 | for f in file_list: 31 | if file_exists(f) and f.endswith('.tar'): 32 | os.remove(f) # remove any tar files in the list, if it does not appear to be a tar file, leave it alone 33 | 34 | # ------------------------------------------------------------------------------ 35 | # PRIVATE 36 | # ------------------------------------------------------------------------------ 37 | 38 | 39 | def _generate_tar(dir_path): 40 | """Private function that reads a local directory and generates a tar archive from it""" 41 | try: 42 | with tarfile.open(dir_path + '.tar', 'w') as tar: 43 | tar.add(dir_path) 44 | except tarfile.TarError as e: 45 | stderr("Error: tar archive creation failed [" + str(e) + "]", exit=1) 46 | -------------------------------------------------------------------------------- /tests/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | NOSE_FLAGS="--verbosity=2" 4 | TEST_COMMAND="nosetests" 5 | 6 | # Testing scripts 7 | ASCII_FILE="test_ascii-armored.py" 8 | COMPRESS_CHECK="test_compression-checks.py" 9 | DECRYPT_SINGLE_FILE="test_decrypt-single-file.py" 10 | DECRYPT_MULTI_FILE="test_decrypt-multi-file.py" 11 | DECRYPT_SINGLE_DIR="test_decrypt-single-directory.py" 12 | DECRYPT_MULTI_DIR="test_decrypt-multi-directory.py" 13 | HASH="test_hash-digests.py" 14 | MULTI_FILE="test_multi-file.py" 15 | MULTI_DIR="test_multi-directory.py" 16 | SINGLE_FILE="test_single-file.py" 17 | SINGLE_DIR="test_single-directory.py" 18 | UNICODE_PP="test_unicode-passphrase.py" 19 | ESCAPE_PP="test_escaping-passphrase.py" 20 | 21 | if [ "$1" = "all" ];then 22 | "$TEST_COMMAND" "$NOSE_FLAGS" "$SINGLE_FILE" "$SINGLE_DIR" 23 | elif [ "$1" = "ascii" ];then 24 | "$TEST_COMMAND" "$NOSE_FLAGS" "$ASCII_FILE" 25 | elif [ "$1" = "compress" ];then 26 | "$TEST_COMMAND" "$NOSE_FLAGS" "$COMPRESS_CHECK" 27 | elif [ "$1" = "decrypt-dir" ];then 28 | "$TEST_COMMAND" "$NOSE_FLAGS" "$DECRYPT_SINGLE_DIR" 29 | elif [ "$1" = "decrypt-dirs" ];then 30 | "$TEST_COMMAND" "$NOSE_FLAGS" "$DECRYPT_MULTI_DIR" 31 | elif [ "$1" = "decrypt-file" ];then 32 | "$TEST_COMMAND" "$NOSE_FLAGS" "$DECRYPT_SINGLE_FILE" 33 | elif [ "$1" = "decrypt-files" ];then 34 | "$TEST_COMMAND" "$NOSE_FLAGS" "$DECRYPT_MULTI_FILE" 35 | elif [ "$1" = "hash" ];then 36 | "$TEST_COMMAND" "$NOSE_FLAGS" "$HASH" 37 | elif [ "$1" = "multi-file" ];then 38 | "$TEST_COMMAND" "$NOSE_FLAGS" "$MULTI_FILE" 39 | elif [ "$1" = "multi-dir" ];then 40 | "$TEST_COMMAND" "$NOSE_FLAGS" "$MULTI_DIR" 41 | elif [ "$1" = "single-file" ];then 42 | "$TEST_COMMAND" "$NOSE_FLAGS" "$SINGLE_FILE" 43 | elif [ "$1" = "single-dir" ];then 44 | "$TEST_COMMAND" "$NOSE_FLAGS" "$SINGLE_DIR" 45 | elif [ "$1" = "unicode" ];then 46 | "$TEST_COMMAND" "$NOSE_FLAGS" "$UNICODE_PP" 47 | elif [ "$1" = "escape" ];then 48 | "$TEST_COMMAND" "$NOSE_FLAGS" "$ESCAPE_PP" 49 | else 50 | echo "Enter 'all' or a command suite to test." 51 | exit 1 52 | fi 53 | -------------------------------------------------------------------------------- /tests/test_hash-digests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import sys 5 | import os 6 | import unittest 7 | import pexpect 8 | from Naked.toolshed.shell import execute 9 | from Naked.toolshed.system import file_exists, make_path 10 | from crypto.library import hash 11 | 12 | class CryptoHashDigestTest(unittest.TestCase): 13 | 14 | def setUp(self): 15 | self.test_py_crypt_hash = "c386b46760da6416a9584e9809929f54c357362d00f8e1698447a9af6c4e548c" # value from shasum command 16 | self.test_py_crypt_path = "testdir8/test.py.crypt" 17 | self.test_py2_crypt_hash = "b58bd8e5a3c1eb1a97625dd6205579369d9551410e3bed291db7271e1cb0018c" #value from shasum command 18 | self.test_py2_crypt_path = "testdir8/test2.py.crypt" 19 | 20 | def tearDown(self): 21 | pass 22 | 23 | def submit_same_passphrase(self, system_command): 24 | child = pexpect.spawn(system_command) 25 | child.expect("Please enter your passphrase: ") 26 | child.sendline("test") 27 | child.expect("Please enter your passphrase again: ") 28 | child.sendline("test") 29 | child.interact() 30 | return child 31 | 32 | # confirm that the hash.generate_hash(path) function creates an accurate, expected SHA256 hash value 33 | def test_unit_hash_expected_value(self): 34 | generated_hash = hash.generate_hash(self.test_py_crypt_path) 35 | self.assertEqual(generated_hash, self.test_py_crypt_hash) 36 | 37 | # confirm that a modification to the original file alters the generated hash digest value 38 | def test_unit_hash_unexpected_value(self): 39 | generated_hash = hash.generate_hash(self.test_py2_crypt_path) # hash generated from slightly modified file 40 | self.assertEqual(generated_hash, self.test_py2_crypt_hash) # confirm that the appropriate hash value was generated for modified file 41 | self.assertNotEqual(generated_hash, self.test_py_crypt_hash) # compare with hash generated from the original expected file 42 | 43 | -------------------------------------------------------------------------------- /lib/profiler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import cProfile, pstats, StringIO 5 | 6 | def profile(): 7 | # ------------------------------------------------------------------------------ 8 | # Setup a profile 9 | # ------------------------------------------------------------------------------ 10 | pr = cProfile.Profile() 11 | # ------------------------------------------------------------------------------ 12 | # Enter setup code below 13 | # ------------------------------------------------------------------------------ 14 | # Optional: include setup code here 15 | 16 | # import os 17 | # from crypto.library.cryptor import Cryptor 18 | # from Naked.toolshed.system import list_all_files, make_path 19 | # c = Cryptor("test") 20 | # test_dir = make_path(os.path.expanduser('~'), 'Desktop', 'profiletests') 21 | # the_file_list = list_all_files(test_dir) 22 | # x = 0 23 | # for i in the_file_list: 24 | # the_file_list[x] = make_path(test_dir, i) 25 | # x += 1 26 | 27 | # ------------------------------------------------------------------------------ 28 | # Start profiler 29 | # ------------------------------------------------------------------------------ 30 | pr.enable() 31 | 32 | # ------------------------------------------------------------------------------ 33 | # BEGIN profiled code block 34 | # ------------------------------------------------------------------------------ 35 | # include profiled code here 36 | 37 | # c.encrypt_files(the_file_list) 38 | # ------------------------------------------------------------------------------ 39 | # END profiled code block 40 | # ------------------------------------------------------------------------------ 41 | pr.disable() 42 | s = StringIO.StringIO() 43 | sortby = 'cumulative' 44 | ps = pstats.Stats(pr, stream=s).sort_stats(sortby) 45 | ps.strip_dirs().sort_stats("time").print_stats() 46 | print(s.getvalue()) 47 | 48 | if __name__ == '__main__': 49 | profile() -------------------------------------------------------------------------------- /tests/test_unicode-passphrase.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import sys 5 | import os 6 | import unittest 7 | import pexpect 8 | from Naked.toolshed.shell import execute 9 | from Naked.toolshed.system import file_exists, make_path 10 | 11 | class CryptoUnicodePassphraseTest(unittest.TestCase): 12 | 13 | def setUp(self): 14 | pass 15 | 16 | def tearDown(self): 17 | pass 18 | 19 | def submit_same_uni_passphrase(self, system_command): 20 | child = pexpect.spawn(system_command) 21 | #child.logfile = sys.stdout 22 | child.expect("Please enter your passphrase: ") 23 | child.sendline("ƷȦϺѠ") 24 | child.expect("Please enter your passphrase again: ") 25 | child.sendline("ƷȦϺѠ") 26 | child.interact() 27 | return child 28 | 29 | def test_unicode_passphrase_single_file_encrypt(self): 30 | command = "crypto testdir7/uni_test.txt" 31 | child = self.submit_same_uni_passphrase(command) 32 | self.assertTrue(file_exists(make_path("testdir7", "uni_test.txt.crypt"))) #test that new encrypted file exists 33 | child.close() 34 | 35 | # cleanup 36 | os.remove(make_path("testdir7","uni_test.txt.crypt")) 37 | 38 | def test_unicode_passphrase_multi_file_encrypt(self): 39 | command = "crypto testdir7/uni_test.txt testdir7/uni_test2.txt" 40 | child = self.submit_same_uni_passphrase(command) 41 | self.assertTrue(file_exists(make_path("testdir7", "uni_test.txt.crypt"))) #test that new encrypted file exists 42 | self.assertTrue(file_exists(make_path("testdir7", "uni_test2.txt.crypt"))) #test that new encrypted file exists 43 | child.close() 44 | 45 | # cleanup 46 | os.remove(make_path("testdir7","uni_test.txt.crypt")) 47 | os.remove(make_path("testdir7","uni_test2.txt.crypt")) 48 | 49 | def test_unicode_directory_encrypt(self): 50 | command = "crypto testdir7" 51 | child = self.submit_same_uni_passphrase(command) 52 | self.assertTrue(file_exists(make_path("testdir7", "uni_test.txt.crypt"))) #test that new encrypted file exists 53 | self.assertTrue(file_exists(make_path("testdir7", "uni_test2.txt.crypt"))) #test that new encrypted file exists 54 | child.close() 55 | 56 | # cleanup 57 | os.remove(make_path("testdir7","uni_test.txt.crypt")) 58 | os.remove(make_path("testdir7","uni_test2.txt.crypt")) 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from setuptools import setup, find_packages 4 | 5 | 6 | def docs_read(fname): 7 | return open(os.path.join(os.path.dirname(__file__), 'docs', fname)).read() 8 | 9 | def version_read(): 10 | settings_file = open(os.path.join(os.path.dirname(__file__), 'lib', 'crypto', 'settings.py')).read() 11 | major_regex = """major_version\s*?=\s*?["']{1}(\d+)["']{1}""" 12 | minor_regex = """minor_version\s*?=\s*?["']{1}(\d+)["']{1}""" 13 | patch_regex = """patch_version\s*?=\s*?["']{1}(\d+)["']{1}""" 14 | major_match = re.search(major_regex, settings_file) 15 | minor_match = re.search(minor_regex, settings_file) 16 | patch_match = re.search(patch_regex, settings_file) 17 | major_version = major_match.group(1) 18 | minor_version = minor_match.group(1) 19 | patch_version = patch_match.group(1) 20 | if len(major_version) == 0: 21 | major_version = 0 22 | if len(minor_version) == 0: 23 | minor_version = 0 24 | if len(patch_version) == 0: 25 | patch_version = 0 26 | return major_version + "." + minor_version + "." + patch_version 27 | 28 | 29 | setup( 30 | name='crypto', 31 | version=version_read(), 32 | description='Simple symmetric GPG file encryption and decryption', 33 | long_description=(docs_read('README.rst')), 34 | url='https://github.com/chrissimpkins/crypto', 35 | license='MIT license', 36 | author='Christopher Simpkins', 37 | author_email='git.simpkins@gmail.com', 38 | platforms=['any'], 39 | entry_points = { 40 | 'console_scripts': [ 41 | 'crypto = crypto.app:main', 42 | 'decrypto = crypto.decryptoapp:main' 43 | ], 44 | }, 45 | packages=find_packages("lib"), 46 | package_dir={'': 'lib'}, 47 | install_requires=['Naked', 'shellescape'], 48 | keywords='encryption,decryption,gpg,pgp,openpgp,cipher,AES256,crypto,cryptography,security,privacy', 49 | include_package_data=True, 50 | classifiers=[ 51 | 'Intended Audience :: End Users/Desktop', 52 | 'Topic :: Security :: Cryptography', 53 | 'Topic :: Security', 54 | 'Development Status :: 5 - Production/Stable', 55 | 'Natural Language :: English', 56 | 'License :: OSI Approved :: MIT License', 57 | 'Operating System :: MacOS :: MacOS X', 58 | 'Operating System :: POSIX', 59 | 'Operating System :: Unix', 60 | 'Programming Language :: Python', 61 | 'Programming Language :: Python :: 2', 62 | 'Programming Language :: Python :: 3' 63 | ], 64 | ) 65 | -------------------------------------------------------------------------------- /docs/README.rst: -------------------------------------------------------------------------------- 1 | Documentation: http://chrissimpkins.github.io/crypto/ 2 | 3 | Description 4 | ------------- 5 | 6 | crypto provides a simple interface to symmetric Gnu Privacy Guard (gpg) encryption and decryption for one or more files on Unix and Linux platforms. It runs on top of gpg and requires a gpg install on your system. Encryption is performed with the AES256 cipher algorithm. `Benchmarks relative to default gpg settings are available for text and binary file mime types `_. 7 | 8 | crypto provides a number of options including automated tar archives of multiple files prior to encryption, portable ASCII armored encryption formatting, and SHA256 hash digest generation for your encrypted files. You can view all available options in the `usage documentation `_ or with the ``--help`` option. 9 | 10 | Tested in cPython 2.7.x, 3.4.x, and pypy 2.4.x (Python version 2.7.9) 11 | 12 | 13 | Install 14 | --------- 15 | 16 | Install with ``pip`` using the command: 17 | 18 | .. code-block:: bash 19 | 20 | $ pip install crypto 21 | 22 | or `download the source repository `_, unpack it, and navigate to the top level of the repository. Then enter: 23 | 24 | .. code-block:: bash 25 | 26 | $ python setup.py install 27 | 28 | 29 | Upgrade 30 | ----------- 31 | 32 | You can upgrade your crypto version with the command: 33 | 34 | .. code-block:: bash 35 | 36 | $ pip install --upgrade crypto 37 | 38 | 39 | Usage 40 | --------- 41 | 42 | Encryption (crypto) 43 | ^^^^^^^^^^^^^^^^^^^^^ 44 | 45 | .. code-block:: bash 46 | 47 | $ crypto [file path] 48 | 49 | .. code-block:: bash 50 | 51 | $ crypto [directory path] 52 | 53 | 54 | Decryption (decrypto) 55 | ^^^^^^^^^^^^^^^^^^^^^^^ 56 | 57 | .. code-block:: bash 58 | 59 | $ decrypto [file path] 60 | 61 | .. code-block:: bash 62 | 63 | $ decrypto [directory path] 64 | 65 | 66 | You can find all available options in the `documentation `_ or by using one of the following commands: 67 | 68 | .. code-block:: bash 69 | 70 | $ crypto --help 71 | $ decrypto --help 72 | 73 | 74 | Frequently Asked Questions 75 | ------------------------------- 76 | 77 | `FAQ link `_ 78 | 79 | 80 | Issue Reporting 81 | ------------------- 82 | 83 | Issue reporting is available on the `GitHub repository `_ 84 | 85 | 86 | Changelog 87 | ------------ 88 | 89 | `Changelog link `_ 90 | -------------------------------------------------------------------------------- /tests/test_single-directory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import sys 5 | import os 6 | import unittest 7 | import pexpect 8 | from Naked.toolshed.shell import execute 9 | from Naked.toolshed.system import file_exists, make_path 10 | 11 | class CryptoDirectoryEncryptTest(unittest.TestCase): 12 | 13 | def setUp(self): 14 | pass 15 | 16 | def tearDown(self): 17 | pass 18 | 19 | def submit_same_passphrase(self, system_command): 20 | child = pexpect.spawn(system_command) 21 | #child.logfile = sys.stdout 22 | child.expect("Please enter your passphrase: ") 23 | child.sendline("test") 24 | child.expect("Please enter your passphrase again: ") 25 | child.sendline("test") 26 | child.interact() 27 | return child 28 | 29 | 30 | # EMPTY DIRECTORY TEST 31 | def test_singledir_empty_directory(self): 32 | command = "crypto testdir3" 33 | child = pexpect.spawn(command) 34 | child.expect("There are no unencrypted files in the directory.") 35 | child.close() 36 | self.assertEqual(child.exitstatus, 1) 37 | 38 | # ONLY DOTFILE AND CRYPT FILE TEST 39 | def test_singledir_dotcryptfiles_only(self): 40 | command = "crypto testdir4" 41 | child = pexpect.spawn(command) 42 | child.expect("There are no unencrypted files in the directory.") 43 | child.close() 44 | self.assertEqual(child.exitstatus, 1) 45 | 46 | # MISSING DIRECTORY TEST 47 | def test_singledir_missing_directory(self): 48 | command = "crypto completelybogusdir" 49 | child = pexpect.spawn(command) 50 | child.expect("The path that you entered does not appear to be an existing file or directory. Please try again.") 51 | child.close() 52 | self.assertEqual(child.exitstatus, 1) 53 | 54 | # NON-MATCH ON PASSPHRASE 55 | def test_singledir_bad_passphrase(self): 56 | command = "crypto testdir1" 57 | child = pexpect.spawn(command) 58 | child.expect("Please enter your passphrase: ") 59 | child.sendline("test") 60 | child.expect("Please enter your passphrase again: ") 61 | child.sendline("bogus") 62 | child.expect("The passphrases did not match. Please enter your command again.") 63 | child.close() 64 | self.assertEqual(child.exitstatus, 1) 65 | 66 | # fail on blank passphrase entry 67 | def test_singledir_blank_passphrase(self): 68 | command = "crypto testdir1" 69 | child = pexpect.spawn(command) 70 | child.expect("Please enter your passphrase: ") 71 | child.sendline("") 72 | child.expect("You did not enter a passphrase. Please repeat your command and try again.") 73 | child.close() 74 | self.assertEqual(child.exitstatus, 1) 75 | 76 | # DIRECTORY FILE ENCRYPTION TESTS 77 | def test_singledir_file_encrypt(self): 78 | command = "crypto testdir2" 79 | child = self.submit_same_passphrase(command) 80 | child.close() 81 | self.assertTrue(file_exists(make_path("testdir2", "test1.txt.crypt"))) 82 | self.assertTrue(file_exists(make_path("testdir2", "test2.txt.crypt"))) 83 | self.assertFalse(file_exists(make_path("testdir2", "testcrypt.txt.crypt.crypt"))) 84 | 85 | os.remove(make_path("testdir2","test1.txt.crypt")) 86 | os.remove(make_path("testdir2","test2.txt.crypt")) 87 | -------------------------------------------------------------------------------- /tests/test_multi-directory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import sys 5 | import os 6 | import unittest 7 | import pexpect 8 | from Naked.toolshed.shell import execute 9 | from Naked.toolshed.system import file_exists, make_path 10 | 11 | class CryptoMultiDirectoryEncryptTest(unittest.TestCase): 12 | 13 | def setUp(self): 14 | pass 15 | 16 | def tearDown(self): 17 | pass 18 | 19 | def submit_same_passphrase(self, system_command): 20 | child = pexpect.spawn(system_command) 21 | child.expect("Please enter your passphrase: ") 22 | child.sendline("test") 23 | child.expect("Please enter your passphrase again: ") 24 | child.sendline("test") 25 | child.interact() 26 | return child 27 | 28 | # multiple directories including some previously encrypted files and dotfiles 29 | def test_multdir_encrypt_files(self): 30 | command = "crypto testdir1 testdir2" 31 | child = self.submit_same_passphrase(command) 32 | self.assertTrue(file_exists(make_path("testdir1", "test1.txt.crypt"))) 33 | self.assertTrue(file_exists(make_path("testdir1", "banana.gif.crypt"))) 34 | self.assertTrue(file_exists(make_path("testdir1", "star.png.crypt"))) 35 | self.assertTrue(file_exists(make_path("testdir1", "tiger.jpg.crypt"))) 36 | 37 | self.assertFalse(file_exists(make_path("testdir1", ".testfile.crypt"))) # does not encrypt dotfiles when not explicit 38 | self.assertFalse(file_exists(make_path("testdir1", "testcrypt.txt.crypt.crypt"))) # does not encrypt previously encrypted files 39 | 40 | self.assertTrue(file_exists(make_path("testdir2", "test1.txt.crypt"))) 41 | self.assertTrue(file_exists(make_path("testdir2", "test2.txt.crypt"))) 42 | 43 | self.assertFalse(file_exists(make_path("testdir2", "testcrypt.txt.crypt.crypt"))) 44 | 45 | # cleanup 46 | os.remove(make_path("testdir1","test1.txt.crypt")) 47 | os.remove(make_path("testdir1","banana.gif.crypt")) 48 | os.remove(make_path("testdir1","star.png.crypt")) 49 | os.remove(make_path("testdir1","tiger.jpg.crypt")) 50 | os.remove(make_path("testdir2","test1.txt.crypt")) 51 | os.remove(make_path("testdir2","test2.txt.crypt")) 52 | 53 | # ignores single bad directory 54 | def test_multidir_bad_single_dir_path(self): 55 | command = "crypto bogusdir1 testdir2" 56 | child = self.submit_same_passphrase(command) 57 | 58 | self.assertTrue(file_exists(make_path("testdir2", "test1.txt.crypt"))) 59 | self.assertTrue(file_exists(make_path("testdir2", "test2.txt.crypt"))) 60 | 61 | self.assertFalse(file_exists(make_path("testdir2", "testcrypt.txt.crypt.crypt"))) 62 | 63 | # cleanup 64 | os.remove(make_path("testdir2","test1.txt.crypt")) 65 | os.remove(make_path("testdir2","test2.txt.crypt")) 66 | 67 | # notifies user if all bad directory paths 68 | def test_multidir_bad_all_dir_paths(self): 69 | command = "crypto bogusdir1 bogusdir2" 70 | child = pexpect.spawn(command) 71 | child.expect("Unable to identify files for encryption") 72 | child.close() 73 | self.assertEqual(child.exitstatus, 1) 74 | 75 | # fail on non-matched passphrases 76 | def test_multidir_diff_passphrases(self): 77 | command = "crypto testdir1 testdir2" 78 | child = pexpect.spawn(command) 79 | child.expect("Please enter your passphrase: ") 80 | child.sendline("test") 81 | child.expect("Please enter your passphrase again: ") 82 | child.sendline("bogus") 83 | child.expect("The passphrases did not match. Please enter your command again.") 84 | child.close() 85 | self.assertEqual(child.exitstatus, 1) 86 | 87 | # confirm that fails on blank passphrase entry 88 | def test_multidir_blank_passphrase(self): 89 | command = "crypto testdir1 testdir2" 90 | child = pexpect.spawn(command) 91 | child.expect("Please enter your passphrase: ") 92 | child.sendline("") 93 | child.expect("You did not enter a passphrase. Please repeat your command and try again.") 94 | child.close() 95 | self.assertEqual(child.exitstatus, 1) 96 | -------------------------------------------------------------------------------- /tests/test_compression-checks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import sys 5 | import os 6 | import unittest 7 | from crypto.library.cryptor import Cryptor 8 | 9 | class CryptoCompressionCheckTest(unittest.TestCase): 10 | 11 | def setUp(self): 12 | self.pdf_file = "test.pdf" 13 | self.mp3_file = "test.mp3" 14 | self.gz_file = "test.tar.gz" 15 | self.png_file = "test.png" 16 | self.jpg_file = "test.jpg" 17 | self.flac_file = "test.flac" 18 | self.txt_file = "test.txt" 19 | self.c_file = "test.c" 20 | self.py_file = "test.py" 21 | self.yaml_file = "test.yaml" 22 | self.nosuffix_file = "test" 23 | 24 | def tearDown(self): 25 | pass 26 | 27 | # binary file checks 28 | 29 | def test_binary_pdf_check(self): 30 | c = Cryptor("passphrase") 31 | response_bin = c._is_common_binary(self.pdf_file) 32 | response_txt = c._is_common_text(self.pdf_file) 33 | self.assertTrue(response_bin) 34 | self.assertFalse(response_txt) 35 | 36 | def test_binary_mp3_check(self): 37 | c = Cryptor("passphrase") 38 | response_bin = c._is_common_binary(self.mp3_file) 39 | response_txt = c._is_common_text(self.mp3_file) 40 | self.assertTrue(response_bin) 41 | self.assertFalse(response_txt) 42 | 43 | def test_binary_gz_check(self): 44 | c = Cryptor("passphrase") 45 | response_bin = c._is_common_binary(self.gz_file) 46 | response_txt = c._is_common_text(self.gz_file) 47 | self.assertTrue(response_bin) 48 | self.assertFalse(response_txt) 49 | 50 | def test_binary_png_check(self): 51 | c = Cryptor("passphrase") 52 | response_bin = c._is_common_binary(self.png_file) 53 | response_txt = c._is_common_text(self.png_file) 54 | self.assertTrue(response_bin) 55 | self.assertFalse(response_txt) 56 | 57 | def test_binary_jpg_check(self): 58 | c = Cryptor("passphrase") 59 | response_bin = c._is_common_binary(self.jpg_file) 60 | response_txt = c._is_common_text(self.jpg_file) 61 | self.assertTrue(response_bin) 62 | self.assertFalse(response_txt) 63 | 64 | def test_binary_flac_check(self): 65 | c = Cryptor("passphrase") 66 | response_bin = c._is_common_binary(self.flac_file) 67 | response_txt = c._is_common_text(self.flac_file) 68 | self.assertTrue(response_bin) 69 | self.assertFalse(response_txt) 70 | 71 | # text file checks 72 | 73 | def test_text_yaml_check(self): 74 | c = Cryptor("passphrase") 75 | response_bin = c._is_common_binary(self.yaml_file) 76 | response_txt = c._is_common_text(self.yaml_file) 77 | self.assertFalse(response_bin) 78 | self.assertTrue(response_txt) 79 | 80 | def test_text_txt_check(self): 81 | c = Cryptor("passphrase") 82 | response_bin = c._is_common_binary(self.txt_file) 83 | response_txt = c._is_common_text(self.txt_file) 84 | self.assertFalse(response_bin) 85 | self.assertTrue(response_txt) 86 | 87 | def test_text_py_check(self): 88 | c = Cryptor("passphrase") 89 | response_bin = c._is_common_binary(self.py_file) 90 | response_txt = c._is_common_text(self.py_file) 91 | self.assertFalse(response_bin) 92 | self.assertTrue(response_txt) 93 | 94 | def test_text_c_check(self): 95 | c = Cryptor("passphrase") 96 | response_bin = c._is_common_binary(self.c_file) 97 | response_txt = c._is_common_text(self.c_file) 98 | self.assertFalse(response_bin) 99 | self.assertTrue(response_txt) 100 | 101 | 102 | # test is_common_binary & _is_common_text function with file without file type suffix in the file name 103 | 104 | def test_binary_nosuffix_check(self): 105 | c = Cryptor("passphrase") 106 | response_bin = c._is_common_binary(self.nosuffix_file) 107 | self.assertFalse(response_bin) 108 | 109 | def test_text_nosuffix_check(self): 110 | c = Cryptor("passphrase") 111 | response_txt = c._is_common_text(self.nosuffix_file) 112 | self.assertFalse(response_txt) 113 | 114 | 115 | -------------------------------------------------------------------------------- /lib/crypto/settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # ------------------------------------------------------------------------------ 5 | # Application Name 6 | # ------------------------------------------------------------------------------ 7 | app_name = 'crypto' 8 | 9 | # ------------------------------------------------------------------------------ 10 | # Version Number 11 | # ------------------------------------------------------------------------------ 12 | major_version = "1" 13 | minor_version = "4" 14 | patch_version = "1" 15 | 16 | # ------------------------------------------------------------------------------ 17 | # Debug Flag (switch to False for production release code) 18 | # ------------------------------------------------------------------------------ 19 | debug = False 20 | 21 | # ------------------------------------------------------------------------------ 22 | # Usage String 23 | # ------------------------------------------------------------------------------ 24 | usage = """ 25 | Encrypt by explicit file path: 26 | ------------------------------ 27 | crypto [file path] 28 | 29 | 30 | Encrypt all top level files in directory: 31 | ----------------------------------------- 32 | crypto [directory path] 33 | 34 | 35 | Create a tar archive from directory and encrypt the archive: 36 | ----------------------------------------------------------- 37 | crypto --tar [directory path] 38 | 39 | 40 | Decrypt by explicit file path: 41 | ------------------------------ 42 | decrypto [file path] 43 | 44 | 45 | Decrypt all top level encrypted files in directory: 46 | --------------------------------------------------- 47 | decrypto [directory path] 48 | 49 | 50 | Enter `crypto --help` or `decrypto --help` to view the available options. 51 | 52 | """ 53 | 54 | # ------------------------------------------------------------------------------ 55 | # Help String 56 | # ------------------------------------------------------------------------------ 57 | help = """ 58 | ------------------------------------------------- 59 | crypto 60 | Simple symmetric GPG file encryption 61 | Copyright 2015 Christopher Simpkins 62 | MIT license 63 | Source: https://github.com/chrissimpkins/crypto 64 | Docs: https://chrissimpkins.github.io/crypto/ 65 | ------------------------------------------------- 66 | 67 | ABOUT 68 | crypto provides a simple interface to symmetric Gnu Privacy Guard (gpg) encryption and decryption for one or more files. gpg must be installed on your system in order to use the crypto and decrypto executables. 69 | 70 | USAGE 71 | ENCRYPTION 72 | crypto [file path] 73 | crypto [directory path] 74 | 75 | DECRYPTION 76 | decrypto [file path] 77 | decrypto [directory path] 78 | 79 | CRYPTO OPTIONS 80 | --armor | -a Use a portable ASCII armored encryption format 81 | --hash Generate SHA256 hash digest of encrypted file(s) 82 | --space Favor reduced file size over encryption speed 83 | --speed Favor encryption speed over reduced file size 84 | --tar Create tar archive of directory of files before encryption 85 | 86 | DECRYPTO OPTIONS 87 | --nountar Do not automatically unpack decrypted tar archives 88 | --overwrite | -o Overwrite an existing file with the decrypted file 89 | --stdout | -s Print file contents to the standard output stream 90 | 91 | OTHER OPTIONS 92 | --help | -h Display crypto and decrypto help 93 | --usage Display crypto and decrypto usage 94 | --version | -v Display version number 95 | 96 | DESCRIPTION 97 | Use one or more explicit file path arguments to encrypt or decrypt the file(s). crypto and decrypto will attempt to encrypt or decrypt (respectively) any explicit filepaths that you include irrespective of the file type. Encrypted files are generated on the path '.crypt'. The original file is not modified or removed by crypto. 98 | 99 | Use one or more directory arguments with the crypto executable to encrypt all files in the top level of each directory with the same passphrase. Previously encrypted files with a '.crypt' file type will not be generated again in a directory. Remove them before you run the command if you intend to repeat encryption with a file. 100 | 101 | Use one or more directory arguments with decrypto to decrypt all .crypt, .gpg, .asc, and .pgp files in the top level of each directory. decrypto automatically unpacks decrypted tar archives. 102 | 103 | Encryption is performed with the AES256 cipher algorithm. Decryption will take place with any cipher algorithm that your version of gpg supports. 104 | """ 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # crypto 2 | 3 | ## Simple symmetric GPG file encryption and decryption 4 | 5 | ## About 6 | crypto provides a simple interface to symmetric Gnu Privacy Guard (gpg) encryption and decryption for one or more files on Unix and Linux platforms. It runs on top of gpg and requires a gpg install on your system. Encryption is performed with the AES256 cipher algorithm. 7 | 8 | Encryption benchmarks vs. default gpg encryption are available [here](http://chrissimpkins.github.io/crypto/benchmarks.html) with additional details for [text](http://chrissimpkins.github.io/crypto/text-benchmarks.html), [pdf](http://chrissimpkins.github.io/crypto/pdf-benchmarks.html), [mp3](http://chrissimpkins.github.io/crypto/mp3-benchmarks.html), and [png](http://chrissimpkins.github.io/crypto/png-benchmarks.html) mime types. 9 | 10 | crypto provides a number of options including automated tar archives of multiple files prior to encryption, portable ASCII armored encryption formatting, and SHA256 hash digest generation for your encrypted files. 11 | 12 | ## Documentation 13 | 14 | Detailed documentation is available [here](http://chrissimpkins.github.io/crypto/index.html). 15 | 16 | ## Quickstart 17 | 18 | #### Encrypt a File 19 | ``` 20 | $ crypto sometext.txt 21 | ``` 22 | 23 | #### Encrypt with Portable ASCII Armored Format 24 | ``` 25 | $ crypto --armor sometext.txt 26 | ``` 27 | 28 | #### Encrypt Multiple Files with Same Passphrase 29 | ``` 30 | $ crypto sometext.txt anotherimage.jpg 31 | ``` 32 | 33 | #### Encrypt Multiple Files with Wildcard Expansion 34 | ``` 35 | $ crypto *.txt 36 | ``` 37 | 38 | #### Encrypt and Generate SHA256 Hash Digest of the Encrypted File 39 | ``` 40 | $ crypto --hash sometext.txt 41 | ``` 42 | 43 | #### Encrypt All Top Level Files in Multiple Directories with Same Passphrase 44 | ``` 45 | $ crypto imagedir privatedir 46 | ``` 47 | 48 | #### Pack Multiple Files in a Tar Archive, Then Encrypt the Archive 49 | ``` 50 | $ crypto --tar privatedir 51 | ``` 52 | 53 | #### Decrypt a File 54 | ``` 55 | $ decrypto sometext.txt.crypt 56 | ``` 57 | 58 | #### Decrypt All Encrypted Files in Top Level of Directory 59 | ``` 60 | $ decrypto privatedir 61 | ``` 62 | 63 | #### Decrypt Text to Standard Output Stream 64 | ``` 65 | $ decrypto --stdout sometext.txt.gpg 66 | ``` 67 | 68 | 69 | ## Install 70 | 71 | ### 1) Install GPG 72 | 73 | #### Mac OSX Users 74 | Mac OSX users can install gpg from [source](https://www.gnupg.org/download/index.html), with [Homebrew](http://brew.sh/), or by installing the [Mac GPG Tools Suite](https://gpgtools.org/gpgsuite.html). 75 | 76 | The Homebrew install command is: 77 | 78 | ``` 79 | brew install gpg 80 | ``` 81 | 82 | Please refer to the detailed documentation on the Gnu Privacy Guard and Mac GPG Tools suite sites for more information if you choose the source or GPG Tools approaches. 83 | 84 | #### Linux Users 85 | If gpg is not installed on your Linux distro, you can use your package manager to install it or compile and install it from the [source](https://www.gnupg.org/download/index.html). 86 | 87 | ### 2) Install crypto 88 | You can install crypto with [pip](https://pypi.python.org/pypi/pip/): 89 | 90 | ``` 91 | pip install crypto 92 | ``` 93 | 94 | or download the [crypto source](https://github.com/chrissimpkins/crypto/archive/master.zip), unpack it, navigate to the top level directory, and install with the command: 95 | 96 | ``` 97 | python setup.py install 98 | ``` 99 | 100 | ## Options 101 | 102 | ### crypto Options 103 | 104 | #### `--armor | -a` 105 | 106 | Encrypt in a portable ASCII armored format 107 | 108 | #### `--hash` 109 | 110 | Generate SHA256 hash digest of encrypted file(s) 111 | 112 | #### `--space` 113 | 114 | Favor reduced file size over encryption speed 115 | 116 | #### `--speed` 117 | 118 | Favor encryption speed over reduced file size 119 | 120 | #### `--tar` 121 | 122 | Create tar archives from directories of files, then encrypt 123 | 124 | ### decrypto Options 125 | 126 | #### `--nountar` 127 | 128 | Do not automatically unpack tar archives after decryption 129 | 130 | #### `--overwrite | -o` 131 | 132 | Overwrite an existing file with the new decrypted file 133 | 134 | #### `--stdout | -s` 135 | 136 | Push the decrypted data to the standard output stream instead of generating a new file 137 | 138 | ### Other Options 139 | 140 | #### `--help | -h` 141 | 142 | View the help documentation 143 | 144 | #### `--usage` 145 | 146 | View the usage documentation 147 | 148 | #### `--version | -v` 149 | 150 | View the crypto version number 151 | 152 | 153 | ## Issues 154 | 155 | Please submit a [new issue report on the GitHub repository](https://github.com/chrissimpkins/crypto/issueshttps://github.com/chrissimpkins/crypto/issues) with a detailed overview of the problem that you are having. 156 | 157 | 158 | ## Project Contributors 159 | 160 | - [Christoph Russ](https://github.com/christophruss) (@christophruss) 161 | 162 | --- 163 | [MIT License](https://github.com/chrissimpkins/crypto/blob/master/docs/LICENSE) | Built with the [Naked Framework](https://pypi.python.org/pypi/Naked) 164 | -------------------------------------------------------------------------------- /tests/test_multi-file.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import sys 5 | import os 6 | import unittest 7 | import pexpect 8 | from Naked.toolshed.shell import execute 9 | from Naked.toolshed.system import file_exists, make_path 10 | 11 | class CryptoMultiFileEncryptTest(unittest.TestCase): 12 | 13 | def setUp(self): 14 | pass 15 | 16 | def tearDown(self): 17 | pass 18 | 19 | def submit_same_passphrase(self, system_command): 20 | child = pexpect.spawn(system_command) 21 | child.expect("Please enter your passphrase: ") 22 | child.sendline("test") 23 | child.expect("Please enter your passphrase again: ") 24 | child.sendline("test") 25 | child.interact() 26 | return child 27 | 28 | 29 | # text files 30 | def test_multifile_encrypt_txt(self): 31 | command = "crypto testdir1/test1.txt testdir2/test1.txt" 32 | child = self.submit_same_passphrase(command) 33 | self.assertTrue(file_exists(make_path("testdir1", "test1.txt.crypt"))) #test that new encrypted file exists 34 | self.assertTrue(file_exists(make_path("testdir2", "test1.txt.crypt"))) 35 | child.close() 36 | 37 | # cleanup 38 | os.remove(make_path("testdir1","test1.txt.crypt")) 39 | os.remove(make_path("testdir2","test1.txt.crypt")) 40 | 41 | # image files 42 | def test_multifile_encrypt_image(self): 43 | command = "crypto testdir1/star.png testdir1/tiger.jpg" 44 | child = self.submit_same_passphrase(command) 45 | self.assertTrue(file_exists(make_path("testdir1", "star.png.crypt"))) 46 | self.assertTrue(file_exists(make_path("testdir1", "tiger.jpg.crypt"))) 47 | child.close() 48 | 49 | # cleanup 50 | os.remove(make_path("testdir1", "star.png.crypt")) 51 | os.remove(make_path("testdir1", "tiger.jpg.crypt")) 52 | 53 | # multiple files with included encrypted file 54 | def test_multifile_encrypt_withencryptedfile(self): 55 | command = "crypto testdir2/test1.txt testdir2/test2.txt testdir2/testcrypt.txt.crypt" 56 | child = self.submit_same_passphrase(command) 57 | self.assertTrue(file_exists(make_path("testdir2", "test1.txt.crypt"))) 58 | self.assertTrue(file_exists(make_path("testdir2", "test2.txt.crypt"))) 59 | self.assertFalse(file_exists(make_path("testdir2", "testcrypt.txt.crypt.crypt"))) # assert that did not encrypt previously encrypted file 60 | child.close() 61 | 62 | # cleanup 63 | os.remove(make_path("testdir2", "test1.txt.crypt")) 64 | os.remove(make_path("testdir2", "test2.txt.crypt")) 65 | 66 | # multiple files with included dotfile 67 | def test_multifile_encrypt_withdotfile(self): 68 | command = "crypto testdir1/test1.txt testdir1/.testfile" 69 | child = self.submit_same_passphrase(command) 70 | self.assertTrue(file_exists(make_path("testdir1", "test1.txt.crypt"))) 71 | self.assertTrue(file_exists(make_path("testdir1", ".testfile.crypt"))) #should encrypt an explicitly included dotfile 72 | child.close() 73 | 74 | # cleanup 75 | os.remove(make_path("testdir1", "test1.txt.crypt")) 76 | os.remove(make_path("testdir1", ".testfile.crypt")) 77 | 78 | # multiple files with combination of good and bad filepaths 79 | def test_multifile_goodbad_filepath(self): 80 | command = "crypto testdir1/test1.txt testdir1/bogusfile.txt" 81 | child = self.submit_same_passphrase(command) 82 | self.assertTrue(file_exists(make_path("testdir1", "test1.txt.crypt"))) 83 | self.assertFalse(file_exists(make_path("testdir1", "bogusfile.txt.crypt"))) #should not be present when original does not exist 84 | child.close() 85 | 86 | # cleanup 87 | os.remove(make_path("testdir1", "test1.txt.crypt")) 88 | 89 | # multiple files with all bad filepaths 90 | def test_multifile_bad_filepaths(self): 91 | command = "crypto testdir1/bogus.txt testdir1/anotherbogus.txt" 92 | child = pexpect.spawn(command) 93 | child.expect("Unable to identify files for encryption") 94 | child.close() 95 | self.assertEqual(child.exitstatus, 1) 96 | 97 | # fail on non-matched passphrase 98 | def test_multifile_diff_passphrase(self): 99 | command = "crypto testdir1/test1.txt testdir1/test2.txt" 100 | child = pexpect.spawn(command) 101 | child.expect("Please enter your passphrase: ") 102 | child.sendline("test") 103 | child.expect("Please enter your passphrase again: ") 104 | child.sendline("bogus") 105 | child.expect("The passphrases did not match. Please enter your command again.") 106 | child.close() 107 | self.assertEqual(child.exitstatus, 1) 108 | 109 | # confirm that fails on blank passphrase entry 110 | def test_multifile_blank_passphrase(self): 111 | command = "crypto testdir1/test1.txt testdir1/test2.txt" 112 | child = pexpect.spawn(command) 113 | child.expect("Please enter your passphrase: ") 114 | child.sendline("") 115 | child.expect("You did not enter a passphrase. Please repeat your command and try again.") 116 | child.close() 117 | self.assertEqual(child.exitstatus, 1) 118 | 119 | 120 | -------------------------------------------------------------------------------- /tests/test_single-file.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import sys 5 | import os 6 | import unittest 7 | import pexpect 8 | from Naked.toolshed.shell import execute 9 | from Naked.toolshed.system import file_exists, make_path 10 | 11 | class CryptoSingleFileEncryptTest(unittest.TestCase): 12 | 13 | def setUp(self): 14 | pass 15 | 16 | def tearDown(self): 17 | pass 18 | 19 | def submit_same_passphrase(self, system_command): 20 | child = pexpect.spawn(system_command) 21 | #child.logfile = sys.stdout 22 | child.expect("Please enter your passphrase: ") 23 | child.sendline("test") 24 | child.expect("Please enter your passphrase again: ") 25 | child.sendline("test") 26 | child.interact() 27 | return child 28 | 29 | def submit_different_passphrase(self, system_command): 30 | child = pexpect.spawn(system_command) 31 | #child.logfile = sys.stdout 32 | child.expect("Please enter your passphrase: ") 33 | child.sendline("test") 34 | child.expect("Please enter your passphrase again: ") 35 | child.sendline("bogus") 36 | child.interact() 37 | return child 38 | 39 | 40 | 41 | # TESTS FOR EXISTING FILES 42 | 43 | # text file 44 | def test_singlefile_encrypt_txt(self): 45 | command = "crypto testdir1/test1.txt" 46 | child = self.submit_same_passphrase(command) 47 | #stdout_string = child.logfile.getvalue() 48 | self.assertTrue(file_exists(make_path("testdir1", "test1.txt.crypt"))) #test that new encrypted file exists 49 | child.close() 50 | 51 | # cleanup 52 | os.remove(make_path("testdir1","test1.txt.crypt")) 53 | 54 | 55 | # image file types 56 | def test_singlefile_encrypt_png(self): 57 | command = "crypto testdir1/star.png" 58 | child = self.submit_same_passphrase(command) 59 | self.assertTrue(file_exists(make_path("testdir1", "star.png.crypt"))) 60 | child.close() 61 | 62 | # cleanup 63 | os.remove(make_path("testdir1", "star.png.crypt")) 64 | 65 | def test_singlefile_encrypt_jpg(self): 66 | command = "crypto testdir1/tiger.jpg" 67 | child = self.submit_same_passphrase(command) 68 | self.assertTrue(file_exists(make_path("testdir1", "tiger.jpg.crypt"))) 69 | child.close() 70 | 71 | # cleanup 72 | os.remove(make_path("testdir1", "tiger.jpg.crypt")) 73 | 74 | def test_singlefile_encrypt_gif(self): 75 | command = "crypto testdir1/banana.gif" 76 | child = self.submit_same_passphrase(command) 77 | self.assertTrue(file_exists(make_path("testdir1", "banana.gif.crypt"))) 78 | child.close() 79 | 80 | # cleanup 81 | os.remove(make_path("testdir1", "banana.gif.crypt")) 82 | 83 | 84 | 85 | # private files (should succeed on this explicit call) 86 | def test_singlefile_encrypt_dotfile(self): 87 | command = "crypto testdir1/.testfile" 88 | child = self.submit_same_passphrase(command) 89 | child.close() 90 | self.assertTrue(file_exists(make_path("testdir1", ".testfile.crypt"))) 91 | 92 | # cleanup 93 | os.remove(make_path("testdir1", ".testfile.crypt")) 94 | 95 | 96 | # previously encrypted file (should fail) 97 | def test_singlefile_encrypt_cryptfile(self): 98 | command = "crypto testdir1/testcrypt.txt.crypt" 99 | 100 | # confirm error message and non-zero exit status code 101 | child = pexpect.spawn(command) 102 | child.expect("You are attempting to encrypt an encrypted file. Please delete the .crypt file and repeat encryption with the original file if this is your intent.") 103 | child.close() 104 | self.assertEqual(child.exitstatus, 1) 105 | 106 | # TESTS FOR NON-EXISTENT FILES 107 | 108 | def test_singlefile_encrypt_missing_file(self): 109 | command = "crypto testdir1/bogusfile.txt" 110 | 111 | # confirm error message and non-zero exit status code 112 | child = pexpect.spawn(command) 113 | child.expect("The path that you entered does not appear to be an existing file or directory. Please try again.") 114 | child.close() 115 | self.assertEqual(child.exitstatus, 1) 116 | 117 | 118 | # TESTS FOR PASSPHRASE 119 | 120 | def test_singlefile_encrypt_bad_passphrase(self): 121 | command = "crypto testdir1/test1.txt" 122 | 123 | # confirm non-zero exit status 124 | child = pexpect.spawn(command) 125 | child.expect("Please enter your passphrase: ") 126 | child.sendline("test") 127 | child.expect("Please enter your passphrase again: ") 128 | child.sendline("bogus") 129 | child.expect("The passphrases did not match. Please enter your command again.") 130 | child.close() 131 | self.assertEqual(child.exitstatus, 1) 132 | 133 | # confirm that fails on blank passphrase entry 134 | def test_singlefile_encrypt_blank_passphrase(self): 135 | command = "crypto testdir1/test1.txt" 136 | child = pexpect.spawn(command) 137 | child.expect("Please enter your passphrase: ") 138 | child.sendline("") 139 | child.expect("You did not enter a passphrase. Please repeat your command and try again.") 140 | child.close() 141 | self.assertEqual(child.exitstatus, 1) 142 | 143 | 144 | -------------------------------------------------------------------------------- /tests/test_escaping-passphrase.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | import os 6 | import unittest 7 | import pexpect 8 | import shutil 9 | from Naked.toolshed.shell import execute 10 | from Naked.toolshed.system import file_exists, make_path, dir_exists 11 | 12 | class CryptoEscapePassphraseTest(unittest.TestCase): 13 | 14 | def setUp(self): 15 | self.cwd = os.getcwd() 16 | 17 | def tearDown(self): 18 | pass 19 | 20 | def submit_same_esc_passphrase(self, system_command): 21 | child = pexpect.spawn(system_command) 22 | # child.logfile = sys.stdout 23 | child.expect("Please enter your passphrase: ") 24 | child.sendline("$@`!\%^&*()_-+=3'A\\\'M'\W<>?,./|[]{}") 25 | child.expect("Please enter your passphrase again: ") 26 | child.sendline("$@`!\%^&*()_-+=3'A\\\'M'\W<>?,./|[]{}") 27 | child.interact() 28 | return child 29 | 30 | def test_escape_passphrase_single_file_encrypt_decrypt(self): 31 | command = "crypto testdir11/esc_test.txt" 32 | child = self.submit_same_esc_passphrase(command) 33 | self.assertTrue(file_exists(make_path("testdir11", "esc_test.txt.crypt"))) # test new encrypted file exists 34 | child.close() 35 | 36 | os.rename("testdir11/esc_test.txt.crypt", "testdir11/SUBTEST.txt.crypt") 37 | 38 | decrypt_command = "decrypto testdir11/SUBTEST.txt.crypt" 39 | child = self.submit_same_esc_passphrase(decrypt_command) 40 | self.assertTrue(file_exists(make_path("testdir11", "SUBTEST.txt"))) # test decrypted file exists 41 | child.close() 42 | 43 | # cleanup 44 | os.remove(make_path("testdir11", "SUBTEST.txt")) 45 | os.remove(make_path("testdir11", "SUBTEST.txt.crypt")) 46 | 47 | def test_escape_passphrase_multi_file_encrypt_decrypt(self): 48 | command = "crypto testdir11/esc_test.txt testdir11/esc_test2.txt" 49 | child = self.submit_same_esc_passphrase(command) 50 | self.assertTrue(file_exists(make_path("testdir11", "esc_test.txt.crypt"))) # test new encrypted file exists 51 | self.assertTrue(file_exists(make_path("testdir11", "esc_test2.txt.crypt"))) # test new encrypted file exists 52 | child.close() 53 | 54 | os.rename("testdir11/esc_test.txt.crypt", "testdir11/SUBTEST1.txt.crypt") 55 | os.rename("testdir11/esc_test2.txt.crypt", "testdir11/SUBTEST2.txt.crypt") 56 | 57 | decrypt_command = "decrypto testdir11/SUBTEST1.txt.crypt testdir11/SUBTEST2.txt.crypt" 58 | child = self.submit_same_esc_passphrase(decrypt_command) 59 | self.assertTrue(file_exists(make_path("testdir11", "SUBTEST1.txt"))) # test decrypted file exists 60 | self.assertTrue(file_exists(make_path("testdir11", "SUBTEST2.txt"))) # test decrypted file exists 61 | child.close() 62 | 63 | # cleanup 64 | os.remove(make_path("testdir11", "SUBTEST1.txt")) 65 | os.remove(make_path("testdir11", "SUBTEST1.txt.crypt")) 66 | os.remove(make_path("testdir11", "SUBTEST2.txt")) 67 | os.remove(make_path("testdir11", "SUBTEST2.txt.crypt")) 68 | 69 | def test_escape_directory_encrypt_decrypt(self): 70 | command = "crypto testdir11" 71 | child = self.submit_same_esc_passphrase(command) 72 | self.assertTrue(file_exists(make_path("testdir11", "esc_test.txt.crypt"))) # test new encrypted file exists 73 | self.assertTrue(file_exists(make_path("testdir11", "esc_test2.txt.crypt"))) # test new encrypted file exists 74 | child.close() 75 | 76 | os.rename("testdir11/esc_test.txt.crypt", "testdir11/SUBTEST1.txt.crypt") 77 | os.rename("testdir11/esc_test2.txt.crypt", "testdir11/SUBTEST2.txt.crypt") 78 | 79 | decrypt_command = "decrypto testdir11" 80 | child = self.submit_same_esc_passphrase(decrypt_command) 81 | self.assertTrue(file_exists(make_path("testdir11", "SUBTEST1.txt"))) # test decrypted file exists 82 | self.assertTrue(file_exists(make_path("testdir11", "SUBTEST2.txt"))) # test decrypted file exists 83 | child.close() 84 | 85 | # cleanup 86 | os.remove(make_path("testdir11", "SUBTEST1.txt")) 87 | os.remove(make_path("testdir11", "SUBTEST1.txt.crypt")) 88 | os.remove(make_path("testdir11", "SUBTEST2.txt")) 89 | os.remove(make_path("testdir11", "SUBTEST2.txt.crypt")) 90 | 91 | def test_escape_tar_encrypt(self): 92 | try: 93 | # change to the sub test directory for tar tests 94 | os.chdir("testdir11") 95 | command = "crypto --tar testtar" 96 | child = self.submit_same_esc_passphrase(command) 97 | self.assertTrue(file_exists("testtar.tar.crypt")) # test new encrypted archive exists 98 | child.close() 99 | 100 | shutil.move('testtar', 'testtar_temp') 101 | 102 | decrypt_command = "decrypto testtar.tar.crypt" 103 | child = self.submit_same_esc_passphrase(decrypt_command) 104 | self.assertTrue(dir_exists(make_path("testtar"))) # test decrypted tar archive exists 105 | self.assertTrue(file_exists(make_path("testtar", "esc_test.txt"))) 106 | self.assertTrue(file_exists(make_path("testtar", "esc_test2.txt"))) 107 | child.close() 108 | 109 | # cleanup 110 | os.remove(make_path("testtar.tar.crypt")) # remove encrypted archive 111 | shutil.rmtree(make_path("testtar")) # remove the decrypted, unpacked directory 112 | shutil.move('testtar_temp', 'testtar') # move the original tar testing dir back to original path 113 | except Exception as e: 114 | # return to top level testing directory 115 | os.chdir(self.cwd) 116 | raise e 117 | finally: 118 | # return to top level testing directory 119 | os.chdir(self.cwd) 120 | 121 | 122 | def test_escape_passphrase_with_spaces(self): 123 | command = "crypto testdir11/esc_test.txt" 124 | child = pexpect.spawn(command) 125 | child.expect("Please enter your passphrase: ") 126 | child.sendline("$@`!\%^&*()_ -+=3'A\\\ 'M'\W<>?,./|[]{}") 127 | child.expect("Please enter your passphrase again: ") 128 | child.sendline("$@`!\%^&*()_ -+=3'A\\\ 'M'\W<>?,./|[]{}") 129 | child.interact() 130 | self.assertTrue(file_exists(make_path("testdir11", "esc_test.txt.crypt"))) # test new encrypted file exists 131 | child.close() 132 | 133 | # cleanup 134 | os.remove(make_path("testdir11", "esc_test.txt.crypt")) 135 | 136 | def test_escape_passphrase_with_unicode_chars_and_spaces(self): 137 | command = "crypto testdir11/esc_test.txt" 138 | child = pexpect.spawn(command) 139 | child.expect("Please enter your passphrase: ") 140 | child.sendline("$@`!\%^&*()_ -+=3'A\\\ 'M'\W<>?,./|[]{}œœ") 141 | child.expect("Please enter your passphrase again: ") 142 | child.sendline("$@`!\%^&*()_ -+=3'A\\\ 'M'\W<>?,./|[]{}œœ") 143 | child.interact() 144 | self.assertTrue(file_exists(make_path("testdir11", "esc_test.txt.crypt"))) # test new encrypted file exists 145 | child.close() 146 | 147 | # cleanup 148 | os.remove(make_path("testdir11", "esc_test.txt.crypt")) 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /tests/test_decrypt-multi-file.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import sys 5 | import os 6 | import unittest 7 | import pexpect 8 | from Naked.toolshed.shell import execute 9 | from Naked.toolshed.system import file_exists, make_path 10 | 11 | class CryptoMultiFileDecryptTest(unittest.TestCase): 12 | 13 | def setUp(self): 14 | pass 15 | 16 | def tearDown(self): 17 | pass 18 | 19 | def submit_same_passphrase(self, system_command): 20 | child = pexpect.spawn(system_command) 21 | child.expect("Please enter your passphrase: ") 22 | child.sendline("test") 23 | child.expect("Please enter your passphrase again: ") 24 | child.sendline("test") 25 | child.interact() 26 | return child 27 | 28 | # multiple files of different filetypes directory 29 | def test_decrypt_multifile_same_directory(self): 30 | command = "decrypto testdir5/test1.txt.crypt testdir5/test2.txt.gpg testdir5/test3.txt.asc" 31 | child = self.submit_same_passphrase(command) 32 | self.assertTrue(file_exists(make_path("testdir5", "test1.txt"))) 33 | self.assertTrue(file_exists(make_path("testdir5", "test2.txt"))) 34 | self.assertTrue(file_exists(make_path("testdir5", "test3.txt"))) 35 | 36 | child.close() 37 | 38 | #cleanup 39 | os.remove(make_path("testdir5", "test1.txt")) 40 | os.remove(make_path("testdir5", "test2.txt")) 41 | os.remove(make_path("testdir5", "test3.txt")) 42 | 43 | # multiple files with one file that would lead to overwrite - confirm that overwrite fails 44 | def test_decrypt_multifile_overwrite_file(self): 45 | command = "decrypto testdir5/test1.txt.crypt testdir6/test1.txt.crypt" 46 | child = pexpect.spawn(command) 47 | child.expect("Please enter your passphrase: ") 48 | child.sendline("test") 49 | child.expect("Please enter your passphrase again: ") 50 | child.sendline("test") 51 | child.expect("The file path 'testdir6/test1.txt' already exists. This file was not decrypted") 52 | child.close() 53 | self.assertTrue(file_exists(make_path("testdir5", "test1.txt"))) 54 | self.assertTrue(file_exists(make_path("testdir6", "test1.txt"))) 55 | self.assertFalse(file_exists(make_path("testdir6", "test1.txt.tmp"))) # confirm that there is no .tmp file 56 | 57 | #cleanup 58 | os.remove(make_path("testdir5", "test1.txt")) 59 | ## do not remove the test1.txt in the testdir6, needed for other tests 60 | 61 | # confirm that overwrite succeeds when user passes the --overwrite long option 62 | def test_decrypt_multifile_overwrite_longflag(self): 63 | command = "decrypto --overwrite testdir5/test1.txt.crypt testdir6/test1.txt.crypt" 64 | child = pexpect.spawn(command) 65 | child.expect("Please enter your passphrase: ") 66 | child.sendline("test") 67 | child.expect("Please enter your passphrase again: ") 68 | child.sendline("test") 69 | child.interact() 70 | child.close() 71 | self.assertTrue(file_exists(make_path("testdir5", "test1.txt"))) 72 | self.assertTrue(file_exists(make_path("testdir6", "test1.txt"))) 73 | self.assertFalse(file_exists(make_path("testdir6", "test1.txt.tmp"))) # confirm that there is no .tmp file 74 | 75 | #cleanup 76 | os.remove(make_path("testdir5", "test1.txt")) 77 | ## do not remove the test1.txt in the testdir6, needed for other tests 78 | 79 | # confirm that overwrite succeeds when user passes the -o short option 80 | def test_decrypt_multifile_overwrite_shortflag(self): 81 | command = "decrypto -o testdir5/test1.txt.crypt testdir6/test1.txt.crypt" 82 | child = pexpect.spawn(command) 83 | child.expect("Please enter your passphrase: ") 84 | child.sendline("test") 85 | child.expect("Please enter your passphrase again: ") 86 | child.sendline("test") 87 | child.interact() 88 | child.close() 89 | self.assertTrue(file_exists(make_path("testdir5", "test1.txt"))) 90 | self.assertTrue(file_exists(make_path("testdir6", "test1.txt"))) 91 | self.assertFalse(file_exists(make_path("testdir6", "test1.txt.tmp"))) # confirm that there is no .tmp file 92 | 93 | #cleanup 94 | os.remove(make_path("testdir5", "test1.txt")) 95 | ## do not remove the test1.txt in the testdir6, needed for other tests 96 | 97 | # stdout long flag with multiple files 98 | def test_decrypt_multifile_long_stdout(self): 99 | command = "decrypto --stdout testdir6/test1.txt.crypt testdir6/test2.txt.gpg" 100 | child = pexpect.spawn(command) 101 | child.expect("Please enter your passphrase: ") 102 | child.sendline("test") 103 | child.expect("Please enter your passphrase again: ") 104 | child.sendline("test") 105 | child.expect("single line of text from test1.txt") 106 | child.expect("single line of text from test2.txt") 107 | child.close() 108 | 109 | # stdout short flag with multiple files 110 | def test_decrypt_multifile_short_stdout(self): 111 | command = "decrypto -s testdir6/test1.txt.crypt testdir6/test2.txt.gpg" 112 | child = pexpect.spawn(command) 113 | child.expect("Please enter your passphrase: ") 114 | child.sendline("test") 115 | child.expect("Please enter your passphrase again: ") 116 | child.sendline("test") 117 | child.expect("single line of text from test1.txt") 118 | child.expect("single line of text from test2.txt") 119 | child.close() 120 | 121 | # multiple files with one bad file path 122 | def test_decrypt_multifile_bad_filepath(self): 123 | command = "decrypto testdir5/bogusfile.txt.crypt testdir5/test1.txt.crypt" 124 | child = pexpect.spawn(command) 125 | child.expect("'testdir5/bogusfile.txt.crypt' does not appear to be an existing file or directory. Aborting decryption attempt for this request.") 126 | child.expect("Please enter your passphrase: ") 127 | child.sendline("test") 128 | child.expect("Please enter your passphrase again: ") 129 | child.sendline("test") 130 | child.interact() 131 | child.close() 132 | self.assertTrue(file_exists(make_path("testdir5", "test1.txt"))) 133 | 134 | # cleanup 135 | os.remove(make_path("testdir5", "test1.txt")) 136 | 137 | # passphrase mismatch 138 | def test_decrypt_multifile_diff_passphrase(self): 139 | command = "decrypto testdir5/test1.txt.crypt testdir5/test2.txt.gpg" 140 | child = pexpect.spawn(command) 141 | child.expect("Please enter your passphrase: ") 142 | child.sendline("test") 143 | child.expect("Please enter your passphrase again: ") 144 | child.sendline("bogus") 145 | child.expect("The passphrases did not match. Please enter your command again.") 146 | child.close() 147 | self.assertEqual(child.exitstatus, 1) 148 | 149 | # test that fails on blank passphrase (i.e. user hit return without entering a passphrase) 150 | def test_decrypt_multifile_blank_passphrase(self): 151 | command = "decrypto testdir5/test1.txt.crypt testdir5/test2.txt.gpg" 152 | child = pexpect.spawn(command) 153 | child.expect("Please enter your passphrase: ") 154 | child.sendline("") 155 | child.expect("You did not enter a passphrase. Please repeat your command and try again.") 156 | child.close() 157 | self.assertEqual(child.exitstatus, 1) 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /tests/test_ascii-armored.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import os 5 | import unittest 6 | import pexpect 7 | from Naked.toolshed.system import file_exists, make_path 8 | 9 | class CryptoASCIIFileEncryptTest(unittest.TestCase): 10 | 11 | def setUp(self): 12 | pass 13 | 14 | def tearDown(self): 15 | pass 16 | 17 | def submit_same_passphrase(self, system_command): 18 | child = pexpect.spawn(system_command) 19 | #child.logfile = sys.stdout 20 | child.expect("Please enter your passphrase: ") 21 | child.sendline("test") 22 | child.expect("Please enter your passphrase again: ") 23 | child.sendline("test") 24 | child.interact() 25 | return child 26 | 27 | # text file 28 | def test_asciifile_encrypt_txt(self): 29 | command = "crypto --armor testdir1/test1.txt" 30 | child = self.submit_same_passphrase(command) 31 | #stdout_string = child.logfile.getvalue() 32 | self.assertTrue(file_exists(make_path("testdir1", "test1.txt.crypt"))) #test that new encrypted file exists 33 | child.close() 34 | 35 | # cleanup 36 | os.remove(make_path("testdir1","test1.txt.crypt")) 37 | 38 | 39 | # image file types 40 | def test_asciifile_encrypt_png(self): 41 | command = "crypto -a testdir1/star.png" 42 | child = self.submit_same_passphrase(command) 43 | self.assertTrue(file_exists(make_path("testdir1", "star.png.crypt"))) 44 | child.close() 45 | 46 | # cleanup 47 | os.remove(make_path("testdir1", "star.png.crypt")) 48 | 49 | def test_asciifile_encrypt_jpg(self): 50 | command = "crypto --armor testdir1/tiger.jpg" 51 | child = self.submit_same_passphrase(command) 52 | self.assertTrue(file_exists(make_path("testdir1", "tiger.jpg.crypt"))) 53 | child.close() 54 | 55 | # cleanup 56 | os.remove(make_path("testdir1", "tiger.jpg.crypt")) 57 | 58 | def test_asciifile_encrypt_gif(self): 59 | command = "crypto -a testdir1/banana.gif" 60 | child = self.submit_same_passphrase(command) 61 | self.assertTrue(file_exists(make_path("testdir1", "banana.gif.crypt"))) 62 | child.close() 63 | 64 | # cleanup 65 | os.remove(make_path("testdir1", "banana.gif.crypt")) 66 | 67 | 68 | # previously encrypted file (.crypt suffix) - should fail 69 | def test_asciifile_encrypt_cryptfile(self): 70 | command = "crypto -a testdir1/testcrypt.txt.crypt" 71 | child = pexpect.spawn(command) 72 | child.expect("There were no files identified for encryption. crypto does not encrypt dot files or previously encrypted '.crypt' files.") 73 | child.close() 74 | self.assertEqual(child.exitstatus, 1) 75 | 76 | # dotfile - should pass when explicit request 77 | def test_asciifile_encrypt_dotfile(self): 78 | command = "crypto --armor testdir1/.testfile" 79 | child = self.submit_same_passphrase(command) 80 | self.assertTrue(file_exists(make_path("testdir1", ".testfile.crypt"))) 81 | child.close() 82 | 83 | # cleanup 84 | os.remove(make_path("testdir1", ".testfile.crypt")) 85 | 86 | # multiple files explicitly passed on CL 87 | def test_asciifile_encrypt_multiple_files(self): 88 | command = "crypto --armor testdir1/.testfile testdir1/test1.txt" 89 | child = self.submit_same_passphrase(command) 90 | self.assertTrue(file_exists(make_path("testdir1", ".testfile.crypt"))) 91 | self.assertTrue(file_exists(make_path("testdir1", "test1.txt.crypt"))) 92 | child.close() 93 | 94 | # cleanup 95 | os.remove(make_path("testdir1", ".testfile.crypt")) 96 | os.remove(make_path("testdir1", "test1.txt.crypt")) 97 | 98 | # single directory test 99 | def test_asciifile_single_directory(self): 100 | command = "crypto --armor testdir2" 101 | child = self.submit_same_passphrase(command) 102 | self.assertTrue(file_exists(make_path("testdir2", "test1.txt.crypt"))) 103 | self.assertTrue(file_exists(make_path("testdir2", "test2.txt.crypt"))) 104 | self.assertFalse(file_exists(make_path("testdir2", "testcrypt.txt.crypt.crypt"))) # test that did not encrypt previously encrypted file 105 | child.close() 106 | 107 | # cleanup 108 | os.remove(make_path("testdir2", "test1.txt.crypt")) 109 | os.remove(make_path("testdir2", "test2.txt.crypt")) 110 | 111 | # multiple directories test 112 | def test_asciifile_multiple_directory(self): 113 | command = "crypto --armor testdir1 testdir2" 114 | child = self.submit_same_passphrase(command) 115 | self.assertTrue(file_exists(make_path("testdir1", "banana.gif.crypt"))) 116 | self.assertTrue(file_exists(make_path("testdir1", "star.png.crypt"))) 117 | self.assertTrue(file_exists(make_path("testdir1", "test1.txt.crypt"))) 118 | self.assertTrue(file_exists(make_path("testdir1", "tiger.jpg.crypt"))) 119 | self.assertTrue(file_exists(make_path("testdir2", "test1.txt.crypt"))) 120 | self.assertTrue(file_exists(make_path("testdir2", "test2.txt.crypt"))) 121 | self.assertFalse(file_exists(make_path("testdir1", "testcrypt.txt.crypt.crypt"))) 122 | self.assertFalse(file_exists(make_path("testdir2", ".testfile.crypt"))) 123 | self.assertFalse(file_exists(make_path("testdir2", "testcrypt.txt.crypt.crypt"))) # test that did not encrypt previously encrypted file 124 | child.close() 125 | 126 | # cleanup 127 | os.remove(make_path("testdir1", "banana.gif.crypt")) 128 | os.remove(make_path("testdir1", "star.png.crypt")) 129 | os.remove(make_path("testdir1", "test1.txt.crypt")) 130 | os.remove(make_path("testdir1", "tiger.jpg.crypt")) 131 | os.remove(make_path("testdir2", "test1.txt.crypt")) 132 | os.remove(make_path("testdir2", "test2.txt.crypt")) 133 | 134 | # empty directory test 135 | def test_asciifile_empty_directory(self): 136 | command = "crypto -a testdir3" 137 | child = pexpect.spawn(command) 138 | child.expect("Unable to identify files for encryption") 139 | child.close() 140 | self.assertEqual(child.exitstatus, 1) 141 | 142 | # single directory with only dotfiles and already encrypted files 143 | def test_asciifile_dotcrypt_directory(self): 144 | command = "crypto --armor testdir4" 145 | child = pexpect.spawn(command) 146 | child.expect("There were no files identified for encryption. crypto does not encrypt dot files or previously encrypted '.crypt' files.") 147 | child.close() 148 | self.assertEqual(child.exitstatus, 1) 149 | 150 | # fail on non-matched passphrase 151 | def test_asciifile_diff_passphrase(self): 152 | command = "crypto --armor testdir1" 153 | child = pexpect.spawn(command) 154 | child.expect("Please enter your passphrase: ") 155 | child.sendline("test") 156 | child.expect("Please enter your passphrase again: ") 157 | child.sendline("bogus") 158 | child.expect("The passphrases did not match. Please enter your command again.") 159 | child.close() 160 | self.assertEqual(child.exitstatus, 1) 161 | 162 | # fail on blank passphrase (i.e. hit enter without typing a passphrase) 163 | def test_asciifile_blank_passphrase(self): 164 | command = "crypto --armor testdir1" 165 | child = pexpect.spawn(command) 166 | child.expect("Please enter your passphrase: ") 167 | child.sendline("") 168 | child.expect("You did not enter a passphrase. Please repeat your command and try again.") 169 | child.close() 170 | self.assertEqual(child.exitstatus, 1) 171 | 172 | 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /tests/test_decrypt-multi-directory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import sys 5 | import os 6 | import unittest 7 | import pexpect 8 | from Naked.toolshed.shell import execute 9 | from Naked.toolshed.system import file_exists, make_path 10 | 11 | class CryptoMultiDirectoryDecryptTest(unittest.TestCase): 12 | 13 | def setUp(self): 14 | pass 15 | 16 | def tearDown(self): 17 | pass 18 | 19 | # test multi-directory without overwrite switch 20 | def test_decrypt_multidir_blockoverwrite(self): 21 | command = "decrypto testdir5 testdir6" 22 | child = pexpect.spawn(command) 23 | child.expect("Please enter your passphrase: ") 24 | child.sendline("test") 25 | child.expect("Please enter your passphrase again: ") 26 | child.sendline("test") 27 | child.expect("The file path 'testdir5/test4.txt' already exists. This file was not decrypted") 28 | child.expect("The file path 'testdir6/test1.txt' already exists. This file was not decrypted") 29 | child.expect("The file path 'testdir6/test2.txt' already exists. This file was not decrypted") 30 | child.close() 31 | self.assertTrue(file_exists(make_path("testdir5", "test1.txt"))) 32 | self.assertTrue(file_exists(make_path("testdir5", "test2.txt"))) 33 | self.assertTrue(file_exists(make_path("testdir5", "test3.txt"))) 34 | self.assertFalse(file_exists(make_path("testdir5", "test5.decrypt"))) 35 | self.assertTrue(file_exists(make_path("testdir6", "test1.txt"))) 36 | self.assertFalse(file_exists(make_path("testdir6", "test1.txt.tmp"))) # confirm that there is no .tmp file 37 | self.assertTrue(file_exists(make_path("testdir6", "test2.txt"))) 38 | self.assertFalse(file_exists(make_path("testdir6", "test2.txt.tmp"))) # confirm that there is no .tmp file 39 | 40 | # cleanup 41 | os.remove(make_path("testdir5", "test1.txt")) 42 | os.remove(make_path("testdir5", "test2.txt")) 43 | os.remove(make_path("testdir5", "test3.txt")) 44 | 45 | 46 | # test multi-directory with long overwrite switch 47 | def test_decrypt_multidir_longoverwrite(self): 48 | command = "decrypto --overwrite testdir5 testdir6" 49 | child = pexpect.spawn(command) 50 | child.expect("Please enter your passphrase: ") 51 | child.sendline("test") 52 | child.expect("Please enter your passphrase again: ") 53 | child.sendline("test") 54 | child.interact() 55 | child.close() 56 | self.assertTrue(file_exists(make_path("testdir5", "test1.txt"))) 57 | self.assertTrue(file_exists(make_path("testdir5", "test2.txt"))) 58 | self.assertTrue(file_exists(make_path("testdir5", "test3.txt"))) 59 | self.assertFalse(file_exists(make_path("testdir5", "test5.decrypt"))) 60 | self.assertTrue(file_exists(make_path("testdir6", "test1.txt"))) 61 | self.assertFalse(file_exists(make_path("testdir6", "test1.txt.tmp"))) # confirm that there is no .tmp file 62 | self.assertTrue(file_exists(make_path("testdir6", "test2.txt"))) 63 | self.assertFalse(file_exists(make_path("testdir6", "test2.txt.tmp"))) # confirm that there is no .tmp file 64 | 65 | # cleanup 66 | os.remove(make_path("testdir5", "test1.txt")) 67 | os.remove(make_path("testdir5", "test2.txt")) 68 | os.remove(make_path("testdir5", "test3.txt")) 69 | 70 | 71 | # test multi-directory with short overwrite switch 72 | def test_decrypt_multidir_shortoverwrite(self): 73 | command = "decrypto -o testdir5 testdir6" 74 | child = pexpect.spawn(command) 75 | child.expect("Please enter your passphrase: ") 76 | child.sendline("test") 77 | child.expect("Please enter your passphrase again: ") 78 | child.sendline("test") 79 | child.interact() 80 | child.close() 81 | self.assertTrue(file_exists(make_path("testdir5", "test1.txt"))) 82 | self.assertTrue(file_exists(make_path("testdir5", "test2.txt"))) 83 | self.assertTrue(file_exists(make_path("testdir5", "test3.txt"))) 84 | self.assertFalse(file_exists(make_path("testdir5", "test5.decrypt"))) 85 | self.assertTrue(file_exists(make_path("testdir6", "test1.txt"))) 86 | self.assertFalse(file_exists(make_path("testdir6", "test1.txt.tmp"))) # confirm that there is no .tmp file 87 | self.assertTrue(file_exists(make_path("testdir6", "test2.txt"))) 88 | self.assertFalse(file_exists(make_path("testdir6", "test2.txt.tmp"))) # confirm that there is no .tmp file 89 | 90 | # cleanup 91 | os.remove(make_path("testdir5", "test1.txt")) 92 | os.remove(make_path("testdir5", "test2.txt")) 93 | os.remove(make_path("testdir5", "test3.txt")) 94 | 95 | # test multi-directory stdout long switch 96 | def test_decrypt_multidir_longstdout(self): 97 | command = "decrypto --stdout testdir5 testdir6" 98 | child = pexpect.spawn(command) 99 | child.expect("Please enter your passphrase: ") 100 | child.sendline("test") 101 | child.expect("Please enter your passphrase again: ") 102 | child.sendline("test") 103 | child.interact() 104 | child.close() 105 | self.assertEqual(child.exitstatus, 0) 106 | 107 | # test multi-directory stdout short switch 108 | def test_decrypt_multidir_shortstdout(self): 109 | command = "decrypto -s testdir5 testdir6" 110 | child = pexpect.spawn(command) 111 | child.expect("Please enter your passphrase: ") 112 | child.sendline("test") 113 | child.expect("Please enter your passphrase again: ") 114 | child.sendline("test") 115 | child.interact() 116 | child.close() 117 | self.assertEqual(child.exitstatus, 0) 118 | 119 | # test multi-directory with one bad directory path 120 | def test_decrypt_multidir_bad_dirpath(self): 121 | command = "decrypto -s testdir6 bogusdir" 122 | child = pexpect.spawn(command) 123 | child.expect("'bogusdir' does not appear to be an existing file or directory. Aborting decryption attempt for this request.") 124 | child.expect("Please enter your passphrase: ") 125 | child.sendline("test") 126 | child.expect("Please enter your passphrase again: ") 127 | child.sendline("test") 128 | child.expect("single line of text from test1.txt") 129 | child.expect("single line of text from test2.txt") 130 | child.close() 131 | self.assertEqual(child.exitstatus, 0) 132 | 133 | # test multi-directory with two bad directory paths 134 | def test_decrypt_multidir_two_bad_dirpath(self): 135 | command = "decrypto -s bogusdir bogus2dir" 136 | child = pexpect.spawn(command) 137 | child.expect("'bogusdir' does not appear to be an existing file or directory. Aborting decryption attempt for this request.") 138 | child.expect("'bogus2dir' does not appear to be an existing file or directory. Aborting decryption attempt for this request.") 139 | child.expect("Could not identify files for decryption") 140 | child.close() 141 | self.assertEqual(child.exitstatus, 1) 142 | 143 | # test multi-directory with an empty directory included 144 | def test_decrypt_multidir_empty_directory(self): 145 | command = "decrypto -s testdir6 testdir3" 146 | child = pexpect.spawn(command) 147 | child.expect("Please enter your passphrase: ") 148 | child.sendline("test") 149 | child.expect("Please enter your passphrase again: ") 150 | child.sendline("test") 151 | child.expect("single line of text from test1.txt") 152 | child.expect("single line of text from test2.txt") 153 | child.close() 154 | self.assertEqual(child.exitstatus, 0) 155 | 156 | # test non-matching passphrases on multi-directory command 157 | def test_decrypt_multidir_diff_passphrase(self): 158 | command = "decrypto -s testdir6 testdir3" 159 | child = pexpect.spawn(command) 160 | child.expect("Please enter your passphrase: ") 161 | child.sendline("test") 162 | child.expect("Please enter your passphrase again: ") 163 | child.sendline("bogus") 164 | child.expect("The passphrases did not match. Please enter your command again.") 165 | child.close() 166 | 167 | # test fails on blank passphrase (i.e. user hit enter without typing passphrase) 168 | def test_decrypt_multidir_blank_passphrase(self): 169 | command = "decrypto testdir6 testdir3" 170 | child = pexpect.spawn(command) 171 | child.expect("Please enter your passphrase: ") 172 | child.sendline("") 173 | child.expect("You did not enter a passphrase. Please repeat your command and try again.") 174 | child.close() 175 | self.assertEqual(child.exitstatus, 1) 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /tests/test_decrypt-single-directory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import sys 5 | import os 6 | import unittest 7 | import pexpect 8 | from Naked.toolshed.shell import execute 9 | from Naked.toolshed.system import file_exists, make_path 10 | 11 | class CryptoSingleDirectoryDecryptTest(unittest.TestCase): 12 | 13 | def setUp(self): 14 | pass 15 | 16 | def tearDown(self): 17 | pass 18 | 19 | 20 | # SINGLE ARGUMENT TESTS 21 | # does not decrypt a file if overwrite of existing file is going to occur 22 | # does not implicitly decrypt a file without a .crypt, .gpg, .asc, or .pgp suffix 23 | def test_decrypt_singledir_multiplefiles(self): 24 | command = "decrypto testdir5" 25 | child = pexpect.spawn(command) 26 | child.expect("Please enter your passphrase: ") 27 | child.sendline("test") 28 | child.expect("Please enter your passphrase again: ") 29 | child.sendline("test") 30 | child.expect("'testdir5/test1.txt.crypt' decrypted to 'testdir5/test1.txt'") 31 | child.expect("'testdir5/test2.txt.gpg' decrypted to 'testdir5/test2.txt'") 32 | child.expect("'testdir5/test3.txt.asc' decrypted to 'testdir5/test3.txt'") 33 | child.expect("The file path 'testdir5/test4.txt' already exists. This file was not decrypted.") 34 | child.interact() 35 | self.assertTrue(file_exists(make_path("testdir5", "test1.txt"))) 36 | self.assertTrue(file_exists(make_path("testdir5", "test2.txt"))) 37 | self.assertTrue(file_exists(make_path("testdir5", "test3.txt"))) 38 | self.assertFalse(file_exists(make_path("testdir5", "test5.decrypt"))) # should not decrypt this file 39 | child.close() 40 | 41 | #cleanup 42 | os.remove(make_path("testdir5", "test1.txt")) 43 | os.remove(make_path("testdir5", "test2.txt")) 44 | os.remove(make_path("testdir5", "test3.txt")) 45 | 46 | # empty directory test 47 | def test_decrypt_singledir_emptydir(self): 48 | command = "decrypto testdir3" 49 | child = pexpect.spawn(command) 50 | child.expect("There are no encrypted files in the directory") 51 | child.close() 52 | self.assertEqual(child.exitstatus, 1) 53 | 54 | # non-matched passphrases 55 | def test_decrypt_singledir_diff_passphrase(self): 56 | command = "decrypto testdir5" 57 | child = pexpect.spawn(command) 58 | child.expect("Please enter your passphrase: ") 59 | child.sendline("test") 60 | child.expect("Please enter your passphrase again: ") 61 | child.sendline("bogus") 62 | child.expect("The passphrases did not match. Please enter your command again.") 63 | child.close() 64 | self.assertEqual(child.exitstatus, 1) 65 | 66 | # test that fails on blank passphrase entry 67 | def test_decrypt_singledir_blank_passphrase(self): 68 | command = "decrypto testdir5" 69 | child = pexpect.spawn(command) 70 | child.expect("Please enter your passphrase: ") 71 | child.sendline("") 72 | child.expect("You did not enter a passphrase. Please repeat your command and try again.") 73 | child.close() 74 | self.assertEqual(child.exitstatus, 1) 75 | 76 | # missing directory test 77 | def test_decrypt_singledir_missing_dir(self): 78 | command = "decrypto fakedir" 79 | child = pexpect.spawn(command) 80 | child.expect("The path that you entered does not appear to be an existing file or directory. Please try again.") 81 | child.close() 82 | self.assertEqual(child.exitstatus, 1) 83 | 84 | # MULTIPLE ARGUMENT TESTS 85 | 86 | # test single directory with overwrite of existing .crypt and .gpg files using long option 87 | def test_decrypt_singledir_overwrite_longflag(self): 88 | command = "decrypto --overwrite testdir6" 89 | child = pexpect.spawn(command) 90 | child.expect("Please enter your passphrase: ") 91 | child.sendline("test") 92 | child.expect("Please enter your passphrase again: ") 93 | child.sendline("test") 94 | child.expect("'testdir6/test1.txt.crypt' decrypted to 'testdir6/test1.txt'") 95 | child.expect("'testdir6/test2.txt.gpg' decrypted to 'testdir6/test2.txt'") 96 | child.close() 97 | self.assertTrue(file_exists(make_path("testdir6", "test1.txt"))) # confirm decrypted file present 98 | self.assertFalse(file_exists(make_path("testdir6", "test1.txt.tmp"))) # confirm tmp file erased 99 | self.assertTrue(file_exists(make_path("testdir6", "test2.txt"))) # confirm decrypted file present 100 | self.assertFalse(file_exists(make_path("testdir6", "test2.txt.tmp"))) # confirm tmp file erased 101 | 102 | # test single directory with overwrite of existing .crypt and .gpg files using short option 103 | def test_decrypt_singledir_overwrite_shortflag(self): 104 | command = "decrypto -o testdir6" 105 | child = pexpect.spawn(command) 106 | child.expect("Please enter your passphrase: ") 107 | child.sendline("test") 108 | child.expect("Please enter your passphrase again: ") 109 | child.sendline("test") 110 | child.expect("'testdir6/test1.txt.crypt' decrypted to 'testdir6/test1.txt'") 111 | child.expect("'testdir6/test2.txt.gpg' decrypted to 'testdir6/test2.txt'") 112 | child.close() 113 | self.assertTrue(file_exists(make_path("testdir6", "test1.txt"))) # confirm decrypted file present 114 | self.assertFalse(file_exists(make_path("testdir6", "test1.txt.tmp"))) # confirm tmp file erased 115 | self.assertTrue(file_exists(make_path("testdir6", "test2.txt"))) # confirm decrypted file present 116 | self.assertFalse(file_exists(make_path("testdir6", "test2.txt.tmp"))) # confirm tmp file erased 117 | 118 | # test print to stdout from multiple encrypted files contained in a single directory using long option 119 | def test_decrypt_singledir_stdout_longflag(self): 120 | command = "decrypto --stdout testdir6" 121 | child = pexpect.spawn(command) 122 | child.expect("Please enter your passphrase: ") 123 | child.sendline("test") 124 | child.expect("Please enter your passphrase again: ") 125 | child.sendline("test") 126 | child.expect("single line of text from test1.txt") 127 | child.expect("single line of text from test2.txt") 128 | child.close() 129 | self.assertEqual(child.exitstatus, 0) 130 | 131 | # test print to stdout from multiple encrypted files contained in a single directory using short option 132 | def test_decrypt_singledir_stdout_shortflag(self): 133 | command = "decrypto -s testdir6" 134 | child = pexpect.spawn(command) 135 | child.expect("Please enter your passphrase: ") 136 | child.sendline("test") 137 | child.expect("Please enter your passphrase again: ") 138 | child.sendline("test") 139 | child.expect("single line of text from test1.txt") 140 | child.expect("single line of text from test2.txt") 141 | child.close() 142 | self.assertEqual(child.exitstatus, 0) 143 | 144 | # mismatched passphrase test 145 | def test_decrypt_singledir_stdout_badpassphrase(self): 146 | command = "decrypto -s testdir6" 147 | child = pexpect.spawn(command) 148 | child.expect("Please enter your passphrase: ") 149 | child.sendline("test") 150 | child.expect("Please enter your passphrase again: ") 151 | child.sendline("bogus") 152 | child.expect("The passphrases did not match. Please enter your command again.") 153 | child.close() 154 | self.assertEqual(child.exitstatus, 1) 155 | 156 | # test that fails on blank passphrase entry 157 | def test_decrypt_singledir_stdout_blank_passphrase(self): 158 | command = "decrypto -s testdir6" 159 | child = pexpect.spawn(command) 160 | child.expect("Please enter your passphrase: ") 161 | child.sendline("") 162 | child.expect("You did not enter a passphrase. Please repeat your command and try again.") 163 | child.close() 164 | self.assertEqual(child.exitstatus, 1) 165 | 166 | # test missing requested directory 167 | def test_decrypt_singledir_stdout_missingdir(self): 168 | command = "decrypto -s bogusdir" 169 | child = pexpect.spawn(command) 170 | child.expect("'bogusdir' does not appear to be an existing file or directory. Aborting decryption attempt for this request.") 171 | child.expect("Could not identify files for decryption") 172 | child.close() 173 | self.assertEqual(child.exitstatus, 1) 174 | 175 | # test empty directory 176 | def test_decrypt_singledir_stdout_emptydir(self): 177 | command = "decrypto -s testdir3" 178 | child = pexpect.spawn(command) 179 | child.expect("Could not identify files for decryption") 180 | child.close() 181 | self.assertEqual(child.exitstatus, 1) 182 | 183 | -------------------------------------------------------------------------------- /lib/crypto/library/cryptor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | from Naked.toolshed.shell import muterun 6 | from Naked.toolshed.system import file_size, stdout, stderr 7 | 8 | from shellescape import quote 9 | 10 | # ------------------------------------------------------------------------------ 11 | # Cryptor class 12 | # performs gpg encryption of one or more files 13 | # ------------------------------------------------------------------------------ 14 | 15 | 16 | class Cryptor(object): 17 | """performs gpg encryption of one or more files""" 18 | def __init__(self, passphrase): 19 | self.command_default = "gpg -z 1 --batch --force-mdc --cipher-algo AES256 -o " 20 | self.command_nocompress = "gpg -z 0 --batch --force-mdc --cipher-algo AES256 -o " 21 | self.command_maxcompress = "gpg -z 7 --batch --force-mdc --cipher-algo AES256 -o " 22 | self.command_default_armored = "gpg -z 1 --armor --batch --force-mdc --cipher-algo AES256 -o " 23 | self.command_nocompress_armored = "gpg -z 0 --armor --batch --force-mdc --cipher-algo AES256 -o " 24 | self.command_maxcompress_armored = "gpg -z 7 --armor --batch --force-mdc --cipher-algo AES256 -o " 25 | self.passphrase = passphrase 26 | self.common_binaries = set(['.7z', '.gz', '.aac', '.app', '.avi', '.azw', '.bz2', '.deb', '.doc', '.dmg', '.exe', '.flv', '.gif', '.jar', '.jpg', '.mov', '.mp3', '.mp4', '.odt', '.oga', '.ogg', '.ogm', '.pdf', '.pkg', '.png', '.ppt', '.pps', '.psd', '.rar', '.rpm', '.tar', '.tif', '.wav', '.wma', '.wmv', '.xls', '.zip', '.aiff', '.docx', '.epub', '.flac', '.mpeg', '.jpeg', '.pptx', '.xlsx']) 27 | self.common_text = set(['.c', '.h', '.m', '.cc', '.js', '.pl', '.py', '.rb', '.sh', '.cpp', '.css', '.csv', '.php', '.rss', '.txt', '.xml', '.yml', '.java', '.json', '.html', '.yaml']) 28 | 29 | # ------------------------------------------------------------------------------ 30 | # PUBLIC methods 31 | # ------------------------------------------------------------------------------ 32 | 33 | # ------------------------------------------------------------------------------ 34 | # encrypt_file : file encryption method 35 | # ------------------------------------------------------------------------------ 36 | def encrypt_file(self, inpath, force_nocompress=False, force_compress=False, armored=False, checksum=False): 37 | """public method for single file encryption with optional compression, ASCII armored formatting, and file hash digest generation""" 38 | if armored: 39 | if force_compress: 40 | command_stub = self.command_maxcompress_armored 41 | elif force_nocompress: 42 | command_stub = self.command_nocompress_armored 43 | else: 44 | if self._is_compress_filetype(inpath): 45 | command_stub = self.command_default_armored 46 | else: 47 | command_stub = self.command_nocompress_armored 48 | else: 49 | if force_compress: 50 | command_stub = self.command_maxcompress 51 | elif force_nocompress: 52 | command_stub = self.command_nocompress 53 | else: 54 | if self._is_compress_filetype(inpath): 55 | command_stub = self.command_default 56 | else: 57 | command_stub = self.command_nocompress 58 | 59 | encrypted_outpath = self._create_outfilepath(inpath) 60 | system_command = command_stub + encrypted_outpath + " --passphrase " + quote(self.passphrase) + " --symmetric " + quote(inpath) 61 | 62 | try: 63 | response = muterun(system_command) 64 | # check returned status code 65 | if response.exitcode == 0: 66 | stdout(encrypted_outpath + " was generated from " + inpath) 67 | if checksum: # add a SHA256 hash digest of the encrypted file - requested by user --hash flag in command 68 | from crypto.library import hash 69 | encrypted_file_hash = hash.generate_hash(encrypted_outpath) 70 | if len(encrypted_file_hash) == 64: 71 | stdout("SHA256 hash digest for " + encrypted_outpath + " :") 72 | stdout(encrypted_file_hash) 73 | else: 74 | stdout("Unable to generate a SHA256 hash digest for the file " + encrypted_outpath) 75 | else: 76 | stderr(response.stderr, 0) 77 | stderr("Encryption failed") 78 | sys.exit(1) 79 | except Exception as e: 80 | stderr("There was a problem with the execution of gpg. Encryption failed. Error: [" + str(e) + "]") 81 | sys.exit(1) 82 | 83 | # ------------------------------------------------------------------------------ 84 | # encrypt_files : multiple file encryption 85 | # ------------------------------------------------------------------------------ 86 | def encrypt_files(self, file_list, force_nocompress=False, force_compress=False, armored=False, checksum=False): 87 | """public method for multiple file encryption with optional compression, ASCII armored formatting, and file hash digest generation""" 88 | for the_file in file_list: 89 | self.encrypt_file(the_file, force_nocompress, force_compress, armored, checksum) 90 | 91 | # ------------------------------------------------------------------------------ 92 | # cleanup : overwrite the passphrase in memory 93 | # ------------------------------------------------------------------------------ 94 | def cleanup(self): 95 | """public method that overwrites user passphrase in memory""" 96 | self.passphrase = "" 97 | 98 | # ------------------------------------------------------------------------------ 99 | # PRIVATE methods 100 | # ------------------------------------------------------------------------------ 101 | 102 | def _create_outfilepath(self, inpath): 103 | """private method that generates the crypto saved file path string with a .crypt file type""" 104 | return inpath + '.crypt' 105 | 106 | def _is_compress_filetype(self, inpath): 107 | """private method that performs magic number and size check on file to determine whether to compress the file""" 108 | # check for common file type suffixes in order to avoid the need for file reads to check magic number for binary vs. text file 109 | if self._is_common_binary(inpath): 110 | return False 111 | elif self._is_common_text(inpath): 112 | return True 113 | else: 114 | # files > 10kB get checked for compression (arbitrary decision to skip compression on small files) 115 | the_file_size = file_size(inpath) 116 | if the_file_size > 10240: 117 | if the_file_size > 512000: # seems to be a break point at ~ 500kb where file compression offset by additional file read, so limit tests to files > 500kB 118 | try: 119 | system_command = "file --mime-type -b " + quote(inpath) 120 | response = muterun(system_command) 121 | if response.stdout[0:5] == "text/": # check for a text file mime type 122 | return True # appropriate size, appropriate file mime type 123 | else: 124 | return False # appropriate size, inappropriate file mime type 125 | except Exception: 126 | return False 127 | else: 128 | return True # if file size is < 500kB, skip the additional file read and just go with compression 129 | else: 130 | return False # below minimum size to consider compression, do not compress 131 | 132 | def _is_common_binary(self, inpath): 133 | """private method to compare file path mime type to common binary file types""" 134 | # make local variables for the available char numbers in the suffix types to be tested 135 | two_suffix = inpath[-3:] 136 | three_suffix = inpath[-4:] 137 | four_suffix = inpath[-5:] 138 | 139 | # test for inclusion in the instance variable common_binaries (defined in __init__) 140 | if two_suffix in self.common_binaries: 141 | return True 142 | elif three_suffix in self.common_binaries: 143 | return True 144 | elif four_suffix in self.common_binaries: 145 | return True 146 | else: 147 | return False 148 | 149 | def _is_common_text(self, inpath): 150 | """private method to compare file path mime type to common text file types""" 151 | # make local variables for the available char numbers in the suffix types to be tested 152 | one_suffix = inpath[-2:] 153 | two_suffix = inpath[-3:] 154 | three_suffix = inpath[-4:] 155 | four_suffix = inpath[-5:] 156 | 157 | # test for inclusion in the instance variable common_text (defined in __init__) 158 | if one_suffix in self.common_text: 159 | return True 160 | elif two_suffix in self.common_text: 161 | return True 162 | elif three_suffix in self.common_text: 163 | return True 164 | elif four_suffix in self.common_text: 165 | return True 166 | else: 167 | return False 168 | -------------------------------------------------------------------------------- /tests/test_tar-archive.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import unittest 5 | import os 6 | import pexpect 7 | from Naked.toolshed.system import file_exists, dir_exists, stderr, make_path 8 | from crypto.library.package import generate_tar_files, remove_tar_files 9 | 10 | 11 | class CryptoTarArchiveTest(unittest.TestCase): 12 | 13 | def setUp(self): 14 | self.pre_tardir_path = "testdir9/tar_dir" 15 | self.post_tardir_path = "testdir9/tar_dir.tar" 16 | self.pre_tardir2_path = "testdir9/tar_dir_two" 17 | self.post_tardir2_path = "testdir9/tar_dir_two.tar" 18 | self.pre_tardir_file_path = "testdir9/tar_dir/test.txt" 19 | self.pre_tardir2_file_path = "testdir9/tar_dir_two/test.txt" 20 | self.testdir_good_list = [self.pre_tardir_path] 21 | self.testdir_good_multidir_list = [self.pre_tardir_path, self.pre_tardir2_path] 22 | self.delete_files = [ 23 | 'testdir9/tar_dir.tar.crypt', 24 | 'testdir9/tar_dir_two.tar.crypt', 25 | 'testdir9/nontar.txt.crypt' 26 | ] 27 | 28 | if not dir_exists(self.pre_tardir_path): 29 | stderr("missing test directory for the CryptoTarArchiveTest in test_tar-archive.py test module", exit=1) 30 | 31 | if not file_exists(self.pre_tardir_file_path): 32 | stderr("missing test file for the CryptoTarArchiveTest in the test_tar-archive.py test module", exit=1) 33 | 34 | if not dir_exists(self.pre_tardir2_path): 35 | stderr("missing test directory for the CryptoTarArchiveTest in test_tar-archive.py test module", exit=1) 36 | 37 | if not file_exists(self.pre_tardir2_file_path): 38 | stderr("missing test file for the CryptoTarArchiveTest in the test_tar-archive.py test module", exit=1) 39 | 40 | # cleanup files from old tests if still around 41 | if file_exists(self.post_tardir_path): 42 | os.remove(self.post_tardir_path) 43 | 44 | if file_exists(self.post_tardir2_path): 45 | os.remove(self.post_tardir2_path) 46 | 47 | if file_exists(self.post_tardir_path) or file_exists(self.post_tardir2_path): 48 | stderr("unable to delete testfile in setup for CryptoTarArchiveTest in the test_tar-archive.py test module", exit=1) 49 | 50 | # remove previously generated encrypted files if still around 51 | for the_encypted_file in self.delete_files: 52 | if file_exists(the_encypted_file): 53 | os.remove(the_encypted_file) 54 | 55 | # Tar archive file creation unit tests 56 | 57 | def test_crypto_tar_creation(self): 58 | # execute generate_tar_file function with directory that contains a file 59 | tar_list = generate_tar_files(self.testdir_good_list) 60 | self.assertTrue(file_exists(self.post_tardir_path)) # generates tar from existing directory 61 | self.assertEqual([self.post_tardir_path], tar_list) # confirm the list returned by function 62 | 63 | # cleanup 64 | os.remove(self.post_tardir_path) 65 | 66 | def test_crypto_tar_remove(self): 67 | # execute generate_tar_file function with directory that contains a file 68 | generate_tar_files(self.testdir_good_list) 69 | self.assertTrue(file_exists(self.post_tardir_path)) # generates tar from existing directory 70 | 71 | remove_tar_files([self.post_tardir_path]) # use the module function to remove the tar file 72 | self.assertFalse(file_exists(self.post_tardir_path)) 73 | 74 | def test_crypto_tar_multidirectory(self): 75 | tar_list = generate_tar_files(self.testdir_good_multidir_list) 76 | self.assertTrue(file_exists(self.post_tardir_path)) 77 | self.assertTrue(file_exists(self.post_tardir2_path)) 78 | self.assertEqual([self.post_tardir_path, self.post_tardir2_path], tar_list) # confirm list returned by the function 79 | 80 | # cleanup 81 | os.remove(self.post_tardir_path) 82 | os.remove(self.post_tardir2_path) 83 | 84 | def test_crypto_tar_multidirectory_remove(self): 85 | generate_tar_files(self.testdir_good_multidir_list) 86 | self.assertTrue(file_exists(self.post_tardir_path)) 87 | self.assertTrue(file_exists(self.post_tardir2_path)) 88 | 89 | remove_tar_files([self.post_tardir_path, self.post_tardir2_path]) 90 | self.assertFalse(file_exists(self.post_tardir_path)) 91 | self.assertFalse(file_exists(self.post_tardir2_path)) 92 | 93 | # Command line tests with the tar archive flag 94 | 95 | def test_crypto_tar_commandline_tararchive(self): 96 | command = "crypto --tar testdir9/tar_dir" 97 | child = pexpect.spawn(command) 98 | child.expect("Please enter your passphrase: ") 99 | child.sendline("test") 100 | child.expect("Please enter your passphrase again: ") 101 | child.sendline("test") 102 | child.expect("\r\ntestdir9/tar_dir.tar.crypt was generated from testdir9/tar_dir.tar\r\n") 103 | self.assertTrue(file_exists(make_path('testdir9', 'tar_dir.tar.crypt'))) # confirm that the encrypted tar file is there 104 | self.assertFalse(file_exists(make_path('testdir9', 'tar_dir.tar'))) # confirm that the tar file is removed 105 | self.assertTrue(dir_exists(make_path('testdir9', 'tar_dir'))) # confirm that the test directory is not removed 106 | child.close() 107 | 108 | os.remove('testdir9/tar_dir.tar.crypt') 109 | 110 | def test_crypto_tar_commandline_multidir_tararchive(self): 111 | command = "crypto --tar testdir9/tar_dir testdir9/tar_dir_two" 112 | child = pexpect.spawn(command) 113 | child.expect("Please enter your passphrase: ") 114 | child.sendline("test") 115 | child.expect("Please enter your passphrase again: ") 116 | child.sendline("test") 117 | child.expect("\r\ntestdir9/tar_dir.tar.crypt was generated from testdir9/tar_dir.tar\r\n") 118 | child.expect("testdir9/tar_dir_two.tar.crypt was generated from testdir9/tar_dir_two.tar\r\n") 119 | 120 | self.assertTrue(file_exists(make_path('testdir9', 'tar_dir.tar.crypt'))) # confirm that the encrypted tar file is there 121 | self.assertFalse(file_exists(make_path('testdir9', 'tar_dir.tar'))) # confirm that the tar file is removed 122 | self.assertTrue(dir_exists(make_path('testdir9', 'tar_dir'))) # confirm that the test directory is not removed 123 | 124 | self.assertTrue(file_exists(make_path('testdir9', 'tar_dir_two.tar.crypt'))) # confirm that the encrypted tar file is there 125 | self.assertFalse(file_exists(make_path('testdir9', 'tar_dir_two.tar'))) # confirm that the tar file is removed 126 | self.assertTrue(dir_exists(make_path('testdir9', 'tar_dir_two'))) # confirm that the test directory is not removed 127 | 128 | child.close() 129 | 130 | os.remove('testdir9/tar_dir.tar.crypt') 131 | os.remove('testdir9/tar_dir_two.tar.crypt') 132 | 133 | def test_crypto_tar_commandline_multidir_and_file(self): 134 | command = "crypto --tar testdir9/tar_dir testdir9/tar_dir_two testdir9/nontar.txt" 135 | child = pexpect.spawn(command) 136 | child.expect("Please enter your passphrase: ") 137 | child.sendline("test") 138 | child.expect("Please enter your passphrase again: ") 139 | child.sendline("test") 140 | child.expect("\r\ntestdir9/nontar.txt.crypt was generated from testdir9/nontar.txt\r\n") 141 | child.expect("testdir9/tar_dir.tar.crypt was generated from testdir9/tar_dir.tar\r\n") 142 | child.expect("testdir9/tar_dir_two.tar.crypt was generated from testdir9/tar_dir_two.tar\r\n") 143 | 144 | self.assertTrue( 145 | file_exists(make_path('testdir9', 'tar_dir.tar.crypt'))) # confirm that the encrypted tar file is there 146 | self.assertFalse(file_exists(make_path('testdir9', 'tar_dir.tar'))) # confirm that the tar file is removed 147 | self.assertTrue(dir_exists(make_path('testdir9', 'tar_dir'))) # confirm that the test directory is not removed 148 | 149 | self.assertTrue( 150 | file_exists(make_path('testdir9', 'tar_dir_two.tar.crypt'))) # confirm that the encrypted tar file is there 151 | self.assertFalse(file_exists(make_path('testdir9', 'tar_dir_two.tar'))) # confirm that the tar file is removed 152 | self.assertTrue( 153 | dir_exists(make_path('testdir9', 'tar_dir_two'))) # confirm that the test directory is not removed 154 | 155 | self.assertTrue(file_exists(make_path('testdir9', 'nontar.txt.crypt'))) 156 | self.assertTrue(file_exists(make_path('testdir9', 'nontar.txt'))) # confirm that the file was not removed 157 | 158 | child.close() 159 | 160 | os.remove('testdir9/tar_dir.tar.crypt') 161 | os.remove('testdir9/tar_dir_two.tar.crypt') 162 | os.remove('testdir9/nontar.txt.crypt') 163 | 164 | # Error tests 165 | 166 | def test_crypto_tar_fails_with_missingdir(self): 167 | bogus_dir_list = ["testdir9/bogusdir"] 168 | with self.assertRaises(SystemExit): 169 | generate_tar_files(bogus_dir_list) 170 | 171 | def test_crypto_tar_fails_with_file_instead_dir(self): 172 | file_list = [self.pre_tardir_file_path] 173 | with self.assertRaises(SystemExit): 174 | generate_tar_files(file_list) 175 | 176 | def test_crypto_tar_noerror_when_remove_nofile(self): 177 | # attempt to remove a file that does not exist 178 | # should not raise exception 179 | remove_tar_files(['testdir9/abogusfile.tar']) 180 | 181 | def test_crypto_tar_do_not_remove_nontar_file(self): 182 | # should not remove files without .tar file extension 183 | nontar_file = "testdir9/nontar.txt" 184 | self.assertTrue(file_exists(nontar_file)) 185 | remove_tar_files([nontar_file]) 186 | self.assertTrue(file_exists(nontar_file)) # should still be present after the function executed b/c not tar file 187 | -------------------------------------------------------------------------------- /tests/test_decrypt-single-file.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import sys 5 | import os 6 | import unittest 7 | import pexpect 8 | from Naked.toolshed.shell import execute 9 | from Naked.toolshed.system import file_exists, make_path 10 | 11 | class CryptoSingleFileDecryptTest(unittest.TestCase): 12 | 13 | def setUp(self): 14 | pass 15 | 16 | def tearDown(self): 17 | pass 18 | 19 | def submit_same_passphrase(self, system_command): 20 | child = pexpect.spawn(system_command) 21 | child.expect("Please enter your passphrase: ") 22 | child.sendline("test") 23 | child.expect("Please enter your passphrase again: ") 24 | child.sendline("test") 25 | child.interact() 26 | return child 27 | 28 | 29 | ## SINGLE ARGUMENT CODE BLOCK TESTS 30 | # .crypt file decryption test 31 | def test_decrypt_singlefile_cryptfile(self): 32 | command = "decrypto testdir5/test1.txt.crypt" 33 | child = self.submit_same_passphrase(command) 34 | self.assertTrue(file_exists(make_path("testdir5", "test1.txt"))) 35 | child.close() 36 | 37 | # cleanup 38 | os.remove(make_path("testdir5", "test1.txt")) 39 | 40 | # .gpg file decryption test 41 | def test_decrypt_singlefile_gpgfile(self): 42 | command = "decrypto testdir5/test2.txt.gpg" 43 | child = self.submit_same_passphrase(command) 44 | self.assertTrue(file_exists(make_path("testdir5", "test2.txt"))) 45 | child.close() 46 | 47 | # cleanup 48 | os.remove(make_path("testdir5", "test2.txt")) 49 | 50 | # .asc file decryption test 51 | def test_decrypt_singlefile_ascfile(self): 52 | command = "decrypto testdir5/test3.txt.asc" 53 | child = self.submit_same_passphrase(command) 54 | self.assertTrue(file_exists(make_path("testdir5", "test3.txt"))) 55 | child.close() 56 | 57 | # cleanup 58 | os.remove(make_path("testdir5", "test3.txt")) 59 | 60 | # encrypted file without a file suffix test 61 | def test_decrypt_singlefile_nosuffix(self): 62 | command = "decrypto testdir5/test5" 63 | child = pexpect.spawn(command) 64 | child.expect("Could not confirm that the requested file is encrypted based upon the file type. Attempting decryption. Keep your fingers crossed...") 65 | child.expect("Please enter your passphrase: ") 66 | child.sendline("test") 67 | child.expect("Please enter your passphrase again: ") 68 | child.sendline("test") 69 | child.interact() 70 | self.assertTrue(file_exists(make_path("testdir5", "test5.decrypt"))) 71 | child.close() 72 | # cleanup 73 | os.remove(make_path("testdir5", "test5.decrypt")) 74 | 75 | # incorrect file path (non-existent file) 76 | def test_decrypt_singlefile_missingfile(self): 77 | command = "decrypto testdir5/bogus.txt.crypt" 78 | child = pexpect.spawn(command) 79 | child.expect("The path that you entered does not appear to be an existing file or directory. Please try again.") 80 | child.close() 81 | self.assertEqual(child.exitstatus, 1) 82 | 83 | # confirm existing file overwrite fails 84 | def test_decrypt_singlefile_overwrite(self): 85 | command = "decrypto testdir5/test4.txt.crypt" 86 | child = pexpect.spawn(command) 87 | child.expect("Your file will be decrypted to 'testdir5/test4.txt' and this file path already exists. Please move the file or use the --overwrite option with your command if you intend to replace the current file.") 88 | child.close() 89 | self.assertEqual(child.exitstatus, 1) 90 | 91 | # passphrases do not match 92 | def test_decrypt_singlefile_diff_passphrase(self): 93 | command = "decrypto testdir5/test1.txt.crypt" 94 | child = pexpect.spawn(command) 95 | child.expect("Please enter your passphrase: ") 96 | child.sendline("test") 97 | child.expect("Please enter your passphrase again: ") 98 | child.sendline("bogus") 99 | child.expect("The passphrases did not match. Please enter your command again.") 100 | child.close() 101 | self.assertEqual(child.exitstatus, 1) 102 | 103 | # test that fails on blank passphrase entry 104 | def test_decrypt_singlefile_blank_passphrase(self): 105 | command = "decrypto testdir5/test1.txt.crypt" 106 | child = pexpect.spawn(command) 107 | child.expect("Please enter your passphrase: ") 108 | child.sendline("") 109 | child.expect("You did not enter a passphrase. Please repeat your command and try again.") 110 | child.close() 111 | self.assertEqual(child.exitstatus, 1) 112 | 113 | 114 | ## MULTIPLE ARGUMENT CODEBLOCK TESTS 115 | # tests for single file decryption that includes command line options 116 | 117 | # file overwrite .crypt file long flag test 118 | def test_decrypt_singlecryptfile_overwrite_longflag(self): 119 | command = "decrypto --overwrite testdir6/test1.txt.crypt" 120 | child = pexpect.spawn(command) 121 | child.expect("Please enter your passphrase: ") 122 | child.sendline("test") 123 | child.expect("Please enter your passphrase again: ") 124 | child.sendline("test") 125 | child.expect("'testdir6/test1.txt.crypt' decrypted to 'testdir6/test1.txt'") 126 | child.close() 127 | self.assertTrue(file_exists(make_path("testdir6", "test1.txt"))) # confirm decrypted file present 128 | self.assertFalse(file_exists(make_path("testdir6", "test1.txt.tmp"))) # confirm tmp file erased 129 | 130 | # file overwrite .gpg file long flag test 131 | def test_decrypt_singlegpgfile_overwrite_longflag(self): 132 | command = "decrypto --overwrite testdir6/test2.txt.gpg" 133 | child = pexpect.spawn(command) 134 | child.expect("Please enter your passphrase: ") 135 | child.sendline("test") 136 | child.expect("Please enter your passphrase again: ") 137 | child.sendline("test") 138 | child.expect("'testdir6/test2.txt.gpg' decrypted to 'testdir6/test2.txt'") 139 | child.close() 140 | self.assertTrue(file_exists(make_path("testdir6", "test2.txt"))) # confirm decrypted file present 141 | self.assertFalse(file_exists(make_path("testdir6", "test2.txt.tmp"))) # confirm tmp file erased 142 | 143 | # file overwrite .crypt file short flag test 144 | def test_decrypt_singlecryptfile_overwrite_shortflag(self): 145 | command = "decrypto -o testdir6/test1.txt.crypt" 146 | child = pexpect.spawn(command) 147 | child.expect("Please enter your passphrase: ") 148 | child.sendline("test") 149 | child.expect("Please enter your passphrase again: ") 150 | child.sendline("test") 151 | child.expect("'testdir6/test1.txt.crypt' decrypted to 'testdir6/test1.txt'") 152 | child.close() 153 | self.assertTrue(file_exists(make_path("testdir6", "test1.txt"))) # confirm decrypted file present 154 | self.assertFalse(file_exists(make_path("testdir6", "test1.txt.tmp"))) # confirm tmp file erased 155 | 156 | # file overwrite .gpg file short flag test 157 | def test_decrypt_singlegpgfile_overwrite_shortflag(self): 158 | command = "decrypto -o testdir6/test2.txt.gpg" 159 | child = pexpect.spawn(command) 160 | child.expect("Please enter your passphrase: ") 161 | child.sendline("test") 162 | child.expect("Please enter your passphrase again: ") 163 | child.sendline("test") 164 | child.expect("'testdir6/test2.txt.gpg' decrypted to 'testdir6/test2.txt'") 165 | child.close() 166 | self.assertTrue(file_exists(make_path("testdir6", "test2.txt"))) # confirm decrypted file present 167 | self.assertFalse(file_exists(make_path("testdir6", "test2.txt.tmp"))) # confirm tmp file erased 168 | 169 | # stdout long flag test with .crypt file 170 | def test_decrypt_singlefile_stdoutcrypt_longflag(self): 171 | command = "decrypto --stdout testdir6/test1.txt.crypt" 172 | child = pexpect.spawn(command) 173 | child.expect("Please enter your passphrase: ") 174 | child.sendline("test") 175 | child.expect("Please enter your passphrase again: ") 176 | child.sendline("test") 177 | child.expect("single line of text from test1.txt") 178 | child.close() 179 | 180 | # stdout long flag test with .gpg file 181 | def test_decrypt_singlefile_stdoutgpg_longflag(self): 182 | command = "decrypto --stdout testdir6/test2.txt.gpg" 183 | child = pexpect.spawn(command) 184 | child.expect("Please enter your passphrase: ") 185 | child.sendline("test") 186 | child.expect("Please enter your passphrase again: ") 187 | child.sendline("test") 188 | child.expect("single line of text from test2.txt") 189 | child.close() 190 | 191 | # stdout short flag test with .crypt file 192 | def test_decrypt_singlefile_stdoutcrypt_longflag(self): 193 | command = "decrypto -s testdir6/test1.txt.crypt" 194 | child = pexpect.spawn(command) 195 | child.expect("Please enter your passphrase: ") 196 | child.sendline("test") 197 | child.expect("Please enter your passphrase again: ") 198 | child.sendline("test") 199 | child.expect("single line of text from test1.txt") 200 | child.close() 201 | 202 | # stdout short flag test with .gpg file 203 | def test_decrypt_singlefile_stdoutgpg_shortflag(self): 204 | command = "decrypto -s testdir6/test2.txt.gpg" 205 | child = pexpect.spawn(command) 206 | child.expect("Please enter your passphrase: ") 207 | child.sendline("test") 208 | child.expect("Please enter your passphrase again: ") 209 | child.sendline("test") 210 | child.expect("single line of text from test2.txt") 211 | child.close() 212 | 213 | # mismatched passphrase test 214 | def test_decrypt_singlefile_stdoutgpg_badpassphrase(self): 215 | command = "decrypto -s testdir6/test2.txt.gpg" 216 | child = pexpect.spawn(command) 217 | child.expect("Please enter your passphrase: ") 218 | child.sendline("test") 219 | child.expect("Please enter your passphrase again: ") 220 | child.sendline("bogus") 221 | child.expect("The passphrases did not match. Please enter your command again.") 222 | child.close() 223 | self.assertEqual(child.exitstatus, 1) 224 | 225 | # test that fails on blank passphrase entry 226 | def test_decrypt_singlefile_stdoutgpg_blank_passphrase(self): 227 | command = "decrypto -s testdir6/test2.txt.gpg" 228 | child = pexpect.spawn(command) 229 | child.expect("Please enter your passphrase: ") 230 | child.sendline("") 231 | child.expect("You did not enter a passphrase. Please repeat your command and try again.") 232 | child.close() 233 | self.assertEqual(child.exitstatus, 1) 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | -------------------------------------------------------------------------------- /lib/crypto/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # ------------------------------------------------------------------------------ 5 | # crypto 6 | # Copyright 2015 Christopher Simpkins 7 | # MIT license 8 | # ------------------------------------------------------------------------------ 9 | 10 | # Application start 11 | 12 | 13 | def main(): 14 | import sys 15 | import getpass 16 | from Naked.commandline import Command 17 | from Naked.toolshed.system import dir_exists, file_exists, list_all_files, make_path, stderr 18 | 19 | # ------------------------------------------------------------------------------------------ 20 | # [ Instantiate command line object ] 21 | # used for all subsequent conditional logic in the CLI application 22 | # ------------------------------------------------------------------------------------------ 23 | c = Command(sys.argv[0], sys.argv[1:]) 24 | # ------------------------------------------------------------------------------------------ 25 | # [ VALIDATION LOGIC ] - early validation of appropriate command syntax 26 | # Test that user entered at least one argument to the executable, print usage if not 27 | # ------------------------------------------------------------------------------------------ 28 | if not c.command_suite_validates(): 29 | from crypto.settings import usage as crypto_usage 30 | print(crypto_usage) 31 | sys.exit(1) 32 | # ------------------------------------------------------------------------------------------ 33 | # [ HELP, VERSION, USAGE LOGIC ] 34 | # Naked framework provides default help, usage, and version commands for all applications 35 | # --> settings for user messages are assigned in the lib/crypto/settings.py file 36 | # ------------------------------------------------------------------------------------------ 37 | if c.help(): # User requested crypto help information 38 | from crypto.settings import help as crypto_help 39 | print(crypto_help) 40 | sys.exit(0) 41 | elif c.usage(): # User requested crypto usage information 42 | from crypto.settings import usage as crypto_usage 43 | print(crypto_usage) 44 | sys.exit(0) 45 | elif c.version(): # User requested crypto version information 46 | from crypto.settings import app_name, major_version, minor_version, patch_version 47 | version_display_string = app_name + ' ' + major_version + '.' + minor_version + '.' + patch_version 48 | print(version_display_string) 49 | sys.exit(0) 50 | # ------------------------------------------------------------------------------------------ 51 | # [ APPLICATION LOGIC ] 52 | # 53 | # ------------------------------------------------------------------------------------------ 54 | elif c.argc > 1: 55 | # code for multi-file processing and commands that include options 56 | # ASCII ARMOR SWITCH 57 | ascii_armored = False 58 | if c.option('--armor') or c.option('-a'): 59 | ascii_armored = True 60 | 61 | # MAX COMPRESS / COMPRESS ALL SWITCH 62 | max_compress = False 63 | if c.option('--space'): 64 | max_compress = True 65 | 66 | # NO COMPRESSION SWITCH 67 | no_compress = False 68 | if c.option('--speed'): 69 | no_compress = True 70 | 71 | # SECURE HASH DIGEST REPORT SWITCH 72 | report_checksum = False 73 | if c.option('--hash'): 74 | report_checksum = True 75 | 76 | # TAR FOLDERS SWITCH 77 | tar_folders = False 78 | if c.option('--tar'): 79 | tar_folders = True 80 | 81 | directory_list = [] # directory paths included in the user entered paths from the command line 82 | tar_directory_list = [] # directories, which need to be packaged as tar archives 83 | file_list = [] # file paths included in the user entered paths from the command line (and inside directories entered) 84 | 85 | # dot and .crypt file flags for exclusion testing 86 | contained_dot_file = False 87 | contained_crypt_file = False 88 | 89 | # determine if argument is an existing file or directory 90 | for argument in c.argv: 91 | if file_exists(argument): 92 | if argument.endswith('.crypt'): # do not include previously encrypted files 93 | contained_crypt_file = True 94 | else: 95 | file_list.append(argument) # add appropriate file paths to the file_list 96 | elif dir_exists(argument): 97 | directory_list.append(argument) # if it is a directory, add path to the directory_list 98 | 99 | # add all file paths from user specified directories to the file_list 100 | if len(directory_list) > 0: 101 | if not tar_folders: 102 | for directory in directory_list: 103 | directory_file_list = list_all_files(directory) 104 | for contained_file in directory_file_list: 105 | if contained_file[0] == ".": 106 | contained_dot_file = True # change the flag + is not included in file_list intentionally (no dot files) 107 | elif contained_file.endswith('.crypt'): 108 | contained_crypt_file = True # change the flag + is not included in file_list intentionally (no previously encrypted files) 109 | else: 110 | # otherwise add to the list for encryption 111 | contained_file_path = make_path(directory, contained_file) 112 | file_list.append(contained_file_path) 113 | else: 114 | # create (uncompressed) tar archive for every targeted folder and add the resulting archive to the file_list 115 | # do not start tar file creation, yet (!) - it is more convenient for the user to first enter the passphrase then start processing 116 | for directory in directory_list: 117 | directory_file_path = directory + '.tar' 118 | tar_directory_list.append(directory) 119 | file_list.append(directory_file_path) 120 | 121 | # confirm that there are files to be encrypted, if not warn user 122 | if len(file_list) == 0: 123 | if contained_dot_file is True or contained_crypt_file is True: 124 | stderr("There were no files identified for encryption. crypto does not encrypt dot files or previously encrypted '.crypt' files.") 125 | sys.exit(1) 126 | else: 127 | stderr("Unable to identify files for encryption") 128 | sys.exit(1) 129 | else: 130 | # file_list should contain all filepaths from either user specified file paths or contained in top level of directory, encrypt them 131 | passphrase = getpass.getpass("Please enter your passphrase: ") 132 | if len(passphrase) == 0: # confirm that user entered a passphrase 133 | stderr("You did not enter a passphrase. Please repeat your command and try again.") 134 | sys.exit(1) 135 | passphrase_confirm = getpass.getpass("Please enter your passphrase again: ") 136 | 137 | if passphrase == passphrase_confirm: 138 | 139 | # create temporary tar-files 140 | tar_list = [] 141 | if len(tar_directory_list) > 0: 142 | from crypto.library import package 143 | tar_list = package.generate_tar_files(tar_directory_list) 144 | for t in tar_list: 145 | if t not in file_list: # check to confirm that the tar archive is in the list of files to encrypt 146 | if file_exists(t): 147 | # append the tarfile to the file_list for encryption if it was not included in the file list for encryption 148 | file_list.append(t) 149 | else: 150 | stderr("There was an error with the tar archive creation. Please try again.", exit=1) 151 | 152 | from crypto.library.cryptor import Cryptor 153 | the_cryptor = Cryptor(passphrase) 154 | 155 | # run encryption based upon any passed switches 156 | if ascii_armored: 157 | if max_compress: 158 | the_cryptor.encrypt_files(file_list, force_nocompress=False, force_compress=True, armored=True, checksum=report_checksum) 159 | elif no_compress: 160 | the_cryptor.encrypt_files(file_list, force_nocompress=True, force_compress=False, armored=True, checksum=report_checksum) 161 | else: 162 | the_cryptor.encrypt_files(file_list, force_nocompress=False, force_compress=False, armored=True, checksum=report_checksum) 163 | else: 164 | if max_compress: 165 | the_cryptor.encrypt_files(file_list, force_nocompress=False, force_compress=True, armored=False, checksum=report_checksum) 166 | elif no_compress: 167 | the_cryptor.encrypt_files(file_list, force_nocompress=True, force_compress=False, armored=False, checksum=report_checksum) 168 | else: 169 | the_cryptor.encrypt_files(file_list, force_nocompress=False, force_compress=False, armored=False, checksum=report_checksum) 170 | 171 | # overwrite user entered passphrases 172 | passphrase = "" 173 | passphrase_confirm = "" 174 | the_cryptor.cleanup() 175 | 176 | # tmp tar file removal (generated with package.generate_tar_files function above) 177 | if len(tar_list) > 0: 178 | from crypto.library import package 179 | package.remove_tar_files(tar_list) 180 | else: 181 | # passphrases did not match, report to user and abort 182 | # overwrite user entered passphrases 183 | passphrase = "" 184 | passphrase_confirm = "" 185 | stderr("The passphrases did not match. Please enter your command again.") 186 | sys.exit(1) 187 | 188 | elif c.argc == 1: 189 | # simple single file or directory processing with default settings 190 | path = c.arg0 191 | if file_exists(path): 192 | # it is a file, encrypt the single file with default settings 193 | # confirm that it is not already encrypted, abort if so 194 | if path.endswith('.crypt'): 195 | stderr("You are attempting to encrypt an encrypted file. Please delete the .crypt file and repeat encryption with the original file if this is your intent.") 196 | sys.exit(1) 197 | # if passes test above, obtain passphrase from the user 198 | passphrase = getpass.getpass("Please enter your passphrase: ") 199 | if len(passphrase) == 0: # confirm that user entered a passphrase 200 | stderr("You did not enter a passphrase. Please repeat your command and try again.") 201 | sys.exit(1) 202 | passphrase_confirm = getpass.getpass("Please enter your passphrase again: ") 203 | 204 | if passphrase == passphrase_confirm: 205 | from crypto.library.cryptor import Cryptor 206 | the_cryptor = Cryptor(passphrase) 207 | the_cryptor.encrypt_file(path) 208 | the_cryptor.cleanup() 209 | else: 210 | stderr("The passphrases did not match. Please enter your command again.") 211 | sys.exit(1) 212 | elif dir_exists(path): 213 | # it is a directory, encrypt all top level files with default settings 214 | dirty_directory_file_list = list_all_files(path) 215 | # remove dot files and previously encrypted files (with .crypt suffix) from the list of directory files 216 | clean_directory_file_list = [x for x in dirty_directory_file_list if x[0] != "." and x.endswith(".crypt") is False] # remove dotfiles and .crypt files 217 | 218 | # confirm that there are still files in the list after the dot files and encrypted files are removed 219 | if len(clean_directory_file_list) == 0: 220 | stderr("There are no unencrypted files in the directory.") 221 | sys.exit(1) 222 | 223 | # create relative file paths for each file in the clean_directory_file_list 224 | clean_directory_file_list_relpaths = [] 225 | for clean_file in clean_directory_file_list: 226 | new_file_path = make_path(path, clean_file) 227 | clean_directory_file_list_relpaths.append(new_file_path) 228 | 229 | # prompt for the passphrase 230 | passphrase = getpass.getpass("Please enter your passphrase: ") 231 | if len(passphrase) == 0: # confirm that user entered a passphrase 232 | stderr("You did not enter a passphrase. Please repeat your command and try again.") 233 | sys.exit(1) 234 | passphrase_confirm = getpass.getpass("Please enter your passphrase again: ") 235 | 236 | if passphrase == passphrase_confirm: 237 | from crypto.library.cryptor import Cryptor 238 | the_cryptor = Cryptor(passphrase) 239 | the_cryptor.encrypt_files(clean_directory_file_list_relpaths) # encrypt the list of directory files 240 | the_cryptor.cleanup() 241 | else: 242 | # passphrases do not match 243 | # overwrite user entered passphrases 244 | passphrase = "" 245 | passphrase_confirm = "" 246 | stderr("The passphrases did not match. Please enter your command again.") 247 | sys.exit(1) 248 | else: 249 | # error message, not a file or directory. user entry error 250 | stderr("The path that you entered does not appear to be an existing file or directory. Please try again.") 251 | sys.exit(1) 252 | 253 | # ------------------------------------------------------------------------------------------ 254 | # [ DEFAULT MESSAGE FOR MATCH FAILURE ] 255 | # Message to provide to the user when all above conditional logic fails to meet a true condition 256 | # ------------------------------------------------------------------------------------------ 257 | else: 258 | print("Could not complete your request. Please try again.") 259 | sys.exit(1) 260 | 261 | if __name__ == '__main__': 262 | main() 263 | -------------------------------------------------------------------------------- /tests/test_decrypt-untar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import unittest 5 | import shutil 6 | import os 7 | import pexpect 8 | from Naked.toolshed.system import file_exists, dir_exists 9 | 10 | 11 | class CryptoUntarArchiveTest(unittest.TestCase): 12 | 13 | def setUp(self): 14 | self.cwd = os.getcwd() 15 | self.testdir = 'testdir10' 16 | self.sourcedir = os.path.join(self.testdir, 'sourcedir') 17 | self.nofile_encrypted_archive_sourcepath = os.path.join(self.sourcedir, 'nofile.tar.crypt') 18 | self.nofile_encrypted_archive_destpath = os.path.join(self.testdir, 'nofile.tar.crypt') 19 | self.singlefile_encrypted_archive_sourcepath = os.path.join(self.sourcedir, 'singlefile.tar.crypt') 20 | self.singlefile_encrypted_archive_destpath = os.path.join(self.testdir, 'singlefile.tar.crypt') 21 | self.multifile_encrypted_archive_sourcepath = os.path.join(self.sourcedir, 'multifile.tar.crypt') 22 | self.multifile_encrypted_archive_destpath = os.path.join(self.testdir, 'multifile.tar.crypt') 23 | self.subdirs_encrypted_archive_sourcepath = os.path.join(self.sourcedir, 'subdirs.tar.crypt') 24 | self.subdirs_encrypted_archive_destpath = os.path.join(self.testdir, 'subdirs.tar.crypt') 25 | 26 | # cleanup old test files if they are present 27 | if file_exists(self.nofile_encrypted_archive_destpath): 28 | os.remove(self.nofile_encrypted_archive_destpath) 29 | 30 | if file_exists(self.singlefile_encrypted_archive_destpath): 31 | os.remove(self.singlefile_encrypted_archive_destpath) 32 | 33 | if file_exists(self.multifile_encrypted_archive_destpath): 34 | os.remove(self.multifile_encrypted_archive_destpath) 35 | 36 | if file_exists(self.subdirs_encrypted_archive_destpath): 37 | os.remove(self.subdirs_encrypted_archive_destpath) 38 | 39 | if file_exists(os.path.join(self.testdir, 'nofile.tar')): 40 | os.remove(os.path.join(self.testdir, 'nofile.tar')) 41 | 42 | if file_exists(os.path.join(self.testdir, 'nofile.tar.crypt')): 43 | os.remove(os.path.join(self.testdir, 'nofile.tar.crypt')) 44 | 45 | if dir_exists(os.path.join(self.testdir, 'nofile')): 46 | shutil.rmtree(os.path.join(self.testdir, 'nofile')) 47 | 48 | if file_exists(os.path.join(self.testdir, 'singlefile.tar')): 49 | os.remove(os.path.join(self.testdir, 'singlefile.tar')) 50 | 51 | if file_exists(os.path.join(self.testdir, 'singlefile.tar.crypt')): 52 | os.remove(os.path.join(self.testdir, 'singlefile.tar.crypt')) 53 | 54 | if dir_exists(os.path.join(self.testdir, 'singlefile')): 55 | shutil.rmtree(os.path.join(self.testdir, 'singlefile')) 56 | 57 | if file_exists(os.path.join(self.testdir, 'multifile.tar')): 58 | os.remove(os.path.join(self.testdir, 'multifile.tar')) 59 | 60 | if file_exists(os.path.join(self.testdir, 'multifile.tar.crypt')): 61 | os.remove(os.path.join(self.testdir, 'multifile.tar.crypt')) 62 | 63 | if dir_exists(os.path.join(self.testdir, 'multifile')): 64 | shutil.rmtree(os.path.join(self.testdir, 'multifile')) 65 | 66 | if file_exists(os.path.join(self.testdir, 'subdirs.tar')): 67 | os.remove(os.path.join(self.testdir, 'subdirs.tar')) 68 | 69 | if file_exists(os.path.join(self.testdir, 'subdirs.tar.crypt')): 70 | os.remove(os.path.join(self.testdir, 'subdirs.tar.crypt')) 71 | 72 | if dir_exists(os.path.join(self.testdir, 'subdirs')): 73 | shutil.rmtree(os.path.join(self.testdir, 'subdirs')) 74 | 75 | def submit_same_passphrase(self, system_command): 76 | child = pexpect.spawn(system_command) 77 | child.expect("Please enter your passphrase: ") 78 | child.sendline("test") 79 | child.expect("Please enter your passphrase again: ") 80 | child.sendline("test") 81 | child.interact() 82 | return child 83 | 84 | def test_crypto_untar_no_file_archive_cwd(self): 85 | shutil.copyfile(self.nofile_encrypted_archive_sourcepath, self.nofile_encrypted_archive_destpath) 86 | # execute with testdir as the working directory 87 | try: 88 | os.chdir(self.testdir) 89 | command = "decrypto nofile.tar.crypt" 90 | child = self.submit_same_passphrase(command) 91 | # directory write occurs in the proper spot 92 | self.assertTrue(dir_exists('nofile')) 93 | # the tar archive is deleted, encrypted file is not 94 | self.assertFalse(file_exists('nofile.tar')) 95 | self.assertTrue(file_exists('nofile.tar.crypt')) 96 | child.close() 97 | 98 | # cleanup 99 | shutil.rmtree('nofile') 100 | os.remove('nofile.tar.crypt') 101 | os.chdir(self.cwd) 102 | except Exception as e: 103 | os.chdir(self.cwd) 104 | raise e 105 | 106 | def test_crypto_untar_nofile_archive_notcwd(self): 107 | shutil.copyfile(self.nofile_encrypted_archive_sourcepath, self.nofile_encrypted_archive_destpath) 108 | # execute with testdir not working directory 109 | command = "decrypto testdir10/nofile.tar.crypt" 110 | child = self.submit_same_passphrase(command) 111 | # directory write occurs in the proper spot 112 | self.assertTrue(dir_exists('testdir10/nofile')) 113 | # the tar archive is deleted, encrypted file is not 114 | self.assertFalse(file_exists('testdir10/nofile.tar')) 115 | self.assertTrue(file_exists('testdir10/nofile.tar.crypt')) 116 | # there is no directory written to the current working directory 117 | self.assertFalse(dir_exists('nofile')) 118 | child.close() 119 | 120 | # cleanup 121 | shutil.rmtree('testdir10/nofile') 122 | os.remove(os.path.join('testdir10', 'nofile.tar.crypt')) 123 | 124 | def test_crypto_untar_singlefile_archive_cwd(self): 125 | shutil.copyfile(self.singlefile_encrypted_archive_sourcepath, self.singlefile_encrypted_archive_destpath) 126 | # execute with testdir as the working directory 127 | try: 128 | os.chdir(self.testdir) 129 | command = "decrypto singlefile.tar.crypt" 130 | child = self.submit_same_passphrase(command) 131 | # directory write occurs in the proper spot 132 | self.assertTrue(dir_exists('singlefile')) 133 | # unpacked decrypted directory contains single file 134 | self.assertTrue(file_exists(os.path.join('singlefile', 'test.txt'))) 135 | # the tar archive is deleted, encrypted file is not 136 | self.assertFalse(file_exists('singlefile.tar')) 137 | self.assertTrue(file_exists('singlefile.tar.crypt')) 138 | child.close() 139 | 140 | # cleanup 141 | shutil.rmtree('singlefile') 142 | os.remove('singlefile.tar.crypt') 143 | os.chdir(self.cwd) 144 | except Exception as e: 145 | os.chdir(self.cwd) 146 | raise e 147 | 148 | def test_crypto_untar_singlefile_archive_notcwd(self): 149 | shutil.copyfile(self.singlefile_encrypted_archive_sourcepath, self.singlefile_encrypted_archive_destpath) 150 | # execute with testdir not working directory 151 | command = "decrypto testdir10/singlefile.tar.crypt" 152 | child = self.submit_same_passphrase(command) 153 | # directory write occurs in the proper spot 154 | self.assertTrue(dir_exists('testdir10/singlefile')) 155 | # unpacked decrypted directory contains single file 156 | self.assertTrue(file_exists(os.path.join('testdir10', 'singlefile', 'test.txt'))) 157 | # the tar archive is deleted, encrypted file is not 158 | self.assertFalse(file_exists('testdir10/singlefile.tar')) 159 | self.assertTrue(file_exists('testdir10/singlefile.tar.crypt')) 160 | # there is no directory written to the current working directory 161 | self.assertFalse(dir_exists('singlefile')) 162 | child.close() 163 | 164 | # cleanup 165 | shutil.rmtree('testdir10/singlefile') 166 | os.remove(os.path.join('testdir10', 'singlefile.tar.crypt')) 167 | 168 | def test_crypto_untar_multifile_archive_cwd(self): 169 | shutil.copyfile(self.multifile_encrypted_archive_sourcepath, self.multifile_encrypted_archive_destpath) 170 | # execute with testdir as the working directory 171 | try: 172 | os.chdir(self.testdir) 173 | command = "decrypto multifile.tar.crypt" 174 | child = self.submit_same_passphrase(command) 175 | # directory write occurs in the proper spot 176 | self.assertTrue(dir_exists('multifile')) 177 | # unpacked decrypted directory contains correct multiple files 178 | self.assertTrue(file_exists(os.path.join('multifile', 'test.txt'))) 179 | self.assertTrue(file_exists(os.path.join('multifile', 'test2.txt'))) 180 | self.assertTrue(file_exists(os.path.join('multifile', 'test3.txt'))) 181 | # the tar archive is deleted, encrypted file is not 182 | self.assertFalse(file_exists('multifile.tar')) 183 | self.assertTrue(file_exists('multifile.tar.crypt')) 184 | child.close() 185 | 186 | # cleanup 187 | shutil.rmtree('multifile') 188 | os.remove('multifile.tar.crypt') 189 | os.chdir(self.cwd) 190 | except Exception as e: 191 | os.chdir(self.cwd) 192 | raise e 193 | 194 | def test_crypto_untar_multifile_archive_notcwd(self): 195 | shutil.copyfile(self.multifile_encrypted_archive_sourcepath, self.multifile_encrypted_archive_destpath) 196 | # execute with testdir not working directory 197 | command = "decrypto testdir10/multifile.tar.crypt" 198 | child = self.submit_same_passphrase(command) 199 | # directory write occurs in the proper spot 200 | self.assertTrue(dir_exists('testdir10/multifile')) 201 | # unpacked decrypted directory contains single file 202 | self.assertTrue(file_exists(os.path.join('testdir10', 'multifile', 'test.txt'))) 203 | self.assertTrue(file_exists(os.path.join('testdir10', 'multifile', 'test2.txt'))) 204 | self.assertTrue(file_exists(os.path.join('testdir10', 'multifile', 'test3.txt'))) 205 | # the tar archive is deleted, encrypted file is not 206 | self.assertFalse(file_exists('testdir10/multifile.tar')) 207 | self.assertTrue(file_exists('testdir10/multifile.tar.crypt')) 208 | # there is no directory written to the current working directory 209 | self.assertFalse(dir_exists('multifile')) 210 | child.close() 211 | 212 | # cleanup 213 | shutil.rmtree('testdir10/multifile') 214 | os.remove(os.path.join('testdir10', 'multifile.tar.crypt')) 215 | 216 | def test_crypto_untar_subdirs_archive_cwd(self): 217 | shutil.copyfile(self.subdirs_encrypted_archive_sourcepath, self.subdirs_encrypted_archive_destpath) 218 | # execute with testdir as the working directory 219 | try: 220 | os.chdir(self.testdir) 221 | command = "decrypto subdirs.tar.crypt" 222 | child = self.submit_same_passphrase(command) 223 | # directory write occurs in the proper spot 224 | self.assertTrue(dir_exists('subdirs')) 225 | # unpacked decrypted directory contains unpacked subdirectories 226 | self.assertTrue(dir_exists(os.path.join('subdirs', 'dir1'))) 227 | self.assertTrue(dir_exists(os.path.join('subdirs', 'dir2'))) 228 | # unpacked decrypted directory contains the correct path for unpacked file in subdirectory 229 | self.assertTrue(file_exists(os.path.join('subdirs', 'dir1', 'test.txt'))) 230 | # the tar archive is deleted, encrypted file is not 231 | self.assertFalse(file_exists('subdirs.tar')) 232 | self.assertTrue(file_exists('subdirs.tar.crypt')) 233 | child.close() 234 | 235 | # cleanup 236 | shutil.rmtree('subdirs') 237 | os.remove('subdirs.tar.crypt') 238 | os.chdir(self.cwd) 239 | except Exception as e: 240 | os.chdir(self.cwd) 241 | raise e 242 | 243 | def test_crypto_untar_subdirs_archive_notcwd(self): 244 | shutil.copyfile(self.subdirs_encrypted_archive_sourcepath, self.subdirs_encrypted_archive_destpath) 245 | # execute with testdir not working directory 246 | command = "decrypto testdir10/subdirs.tar.crypt" 247 | child = self.submit_same_passphrase(command) 248 | # directory write occurs in the proper spot 249 | self.assertTrue(dir_exists('testdir10/subdirs')) 250 | # unpacked decrypted directory contains unpacked subdirectories 251 | self.assertTrue(dir_exists(os.path.join('testdir10', 'subdirs', 'dir1'))) 252 | self.assertTrue(dir_exists(os.path.join('testdir10', 'subdirs', 'dir2'))) 253 | # unpacked decrypted directory contains the correct path for unpacked file in subdirectory 254 | self.assertTrue(file_exists(os.path.join('testdir10', 'subdirs', 'dir1', 'test.txt'))) 255 | # the tar archive is deleted, encrypted file is not 256 | self.assertFalse(file_exists('testdir10/subdirs.tar')) 257 | self.assertTrue(file_exists('testdir10/subdirs.tar.crypt')) 258 | # there is no directory written to the current working directory 259 | self.assertFalse(dir_exists('subdirs')) 260 | child.close() 261 | 262 | # cleanup 263 | shutil.rmtree('testdir10/subdirs') 264 | os.remove(os.path.join('testdir10', 'subdirs.tar.crypt')) 265 | 266 | def test_crypto_untar_multitar_archives_cwd(self): 267 | shutil.copyfile(self.subdirs_encrypted_archive_sourcepath, self.subdirs_encrypted_archive_destpath) 268 | shutil.copyfile(self.singlefile_encrypted_archive_sourcepath, self.singlefile_encrypted_archive_destpath) 269 | # execute with testdir not working directory 270 | try: 271 | os.chdir(self.testdir) 272 | command = "decrypto subdirs.tar.crypt singlefile.tar.crypt" 273 | child = self.submit_same_passphrase(command) 274 | # directory writes occur in the proper spot 275 | self.assertTrue(dir_exists('subdirs')) 276 | self.assertTrue(dir_exists('singlefile')) 277 | # unpacked decrypted singlefile directory contains single file 278 | self.assertTrue(file_exists(os.path.join('singlefile', 'test.txt'))) 279 | # the tar archive is deleted, encrypted file is not 280 | self.assertFalse(file_exists('singlefile.tar')) 281 | self.assertTrue(file_exists('singlefile.tar.crypt')) 282 | # unpacked decrypted subdirs directory contains unpacked subdirectories 283 | self.assertTrue(dir_exists(os.path.join('subdirs', 'dir1'))) 284 | self.assertTrue(dir_exists(os.path.join('subdirs', 'dir2'))) 285 | # unpacked decrypted subdirs directory contains the correct path for unpacked file in subdirectory 286 | self.assertTrue(file_exists(os.path.join('subdirs', 'dir1', 'test.txt'))) 287 | # the tar archive is deleted, encrypted file is not 288 | self.assertFalse(file_exists('subdirs.tar')) 289 | self.assertTrue(file_exists('subdirs.tar.crypt')) 290 | child.close() 291 | 292 | # cleanup 293 | shutil.rmtree('subdirs') 294 | shutil.rmtree('singlefile') 295 | os.remove('subdirs.tar.crypt') 296 | os.remove('singlefile.tar.crypt') 297 | os.chdir(self.cwd) 298 | except Exception as e: 299 | os.chdir(self.cwd) 300 | raise e 301 | 302 | def test_crypto_untar_multitar_archives_notcwd(self): 303 | shutil.copyfile(self.subdirs_encrypted_archive_sourcepath, self.subdirs_encrypted_archive_destpath) 304 | shutil.copyfile(self.singlefile_encrypted_archive_sourcepath, self.singlefile_encrypted_archive_destpath) 305 | # execute with testdir not working directory 306 | command = "decrypto testdir10/subdirs.tar.crypt testdir10/singlefile.tar.crypt" 307 | child = self.submit_same_passphrase(command) 308 | # directory writes occur in the proper spot 309 | self.assertTrue(dir_exists('testdir10/subdirs')) # write occurs in testdir10 310 | self.assertTrue(dir_exists('testdir10/singlefile')) 311 | self.assertFalse(dir_exists('subdirs')) # not in cwd 312 | self.assertFalse(dir_exists('singlefile')) 313 | # unpacked decrypted singlefile directory contains single file 314 | self.assertTrue(file_exists(os.path.join('testdir10', 'singlefile', 'test.txt'))) 315 | # the tar archive is deleted, encrypted file is not 316 | self.assertFalse(file_exists('testdir10/singlefile.tar')) 317 | self.assertTrue(file_exists('testdir10/singlefile.tar.crypt')) 318 | # unpacked decrypted subdirs directory contains unpacked subdirectories 319 | self.assertTrue(dir_exists(os.path.join('testdir10', 'subdirs', 'dir1'))) 320 | self.assertTrue(dir_exists(os.path.join('testdir10', 'subdirs', 'dir2'))) 321 | # unpacked decrypted subdirs directory contains the correct path for unpacked file in subdirectory 322 | self.assertTrue(file_exists(os.path.join('testdir10', 'subdirs', 'dir1', 'test.txt'))) 323 | # the tar archive is deleted, encrypted file is not 324 | self.assertFalse(file_exists('testdir10/subdirs.tar')) 325 | self.assertTrue(file_exists('testdir10/subdirs.tar.crypt')) 326 | child.close() 327 | 328 | # cleanup 329 | shutil.rmtree('testdir10/subdirs') 330 | shutil.rmtree('testdir10/singlefile') 331 | os.remove('testdir10/subdirs.tar.crypt') 332 | os.remove('testdir10/singlefile.tar.crypt') 333 | 334 | def test_crypto_untar_no_untar_when_nountar_switch(self): 335 | shutil.copyfile(self.singlefile_encrypted_archive_sourcepath, self.singlefile_encrypted_archive_destpath) 336 | # execute with testdir not working directory 337 | command = "decrypto --nountar testdir10/singlefile.tar.crypt" 338 | child = self.submit_same_passphrase(command) 339 | # confirm that the tar archive is not unpacked 340 | self.assertTrue(file_exists(os.path.join('testdir10', 'singlefile.tar'))) 341 | self.assertFalse(dir_exists(os.path.join('testdir10', 'singlefile'))) 342 | child.close() 343 | 344 | # cleanup 345 | os.remove(os.path.join('testdir10', 'singlefile.tar')) 346 | os.remove(os.path.join('testdir10', 'singlefile.tar.crypt')) 347 | 348 | def test_crypto_untar_overwrite_switch_performs_overwrite(self): 349 | shutil.copyfile(self.singlefile_encrypted_archive_sourcepath, self.singlefile_encrypted_archive_destpath) 350 | # execute first time to generate a decrypted, unpacked archive 351 | command = "decrypto testdir10/singlefile.tar.crypt" 352 | child = self.submit_same_passphrase(command) 353 | # confirm that the archive was unpacked 354 | self.assertTrue(dir_exists(os.path.join('testdir10', 'singlefile'))) 355 | self.assertTrue(file_exists(os.path.join('testdir10', 'singlefile', 'test.txt'))) 356 | child.close() 357 | # execute the command again and overwrite existing files, assert that does not raise error 358 | command = "decrypto --overwrite testdir10/singlefile.tar.crypt" 359 | child = self.submit_same_passphrase(command) 360 | # confirm that the directory and file are there 361 | self.assertTrue(dir_exists(os.path.join('testdir10', 'singlefile'))) 362 | self.assertTrue(file_exists(os.path.join('testdir10', 'singlefile', 'test.txt'))) 363 | child.close() 364 | 365 | # cleanup 366 | shutil.rmtree(os.path.join('testdir10', 'singlefile')) 367 | os.remove(os.path.join('testdir10', 'singlefile.tar.crypt')) 368 | 369 | def test_crypto_untar_overwrite_fails_without_overwrite_switch(self): 370 | shutil.copyfile(self.singlefile_encrypted_archive_sourcepath, self.singlefile_encrypted_archive_destpath) 371 | # execute first time to generate a decrypted, unpacked archive 372 | command = "decrypto testdir10/singlefile.tar.crypt" 373 | child = self.submit_same_passphrase(command) 374 | # confirm that the archive was unpacked 375 | self.assertTrue(dir_exists(os.path.join('testdir10', 'singlefile'))) 376 | self.assertTrue(file_exists(os.path.join('testdir10', 'singlefile', 'test.txt'))) 377 | child.close() 378 | # execute the command again and confirm that it raises error message, no file overwrite 379 | command = "decrypto testdir10/singlefile.tar.crypt" 380 | child = pexpect.spawn(command) 381 | child.expect("Please enter your passphrase: ") 382 | child.sendline("test") 383 | child.expect("Please enter your passphrase again: ") 384 | child.sendline("test") 385 | child.expect("Failed to unpack the file 'testdir10/singlefile/test.txt'. File already exists. Use the --overwrite flag to replace existing files.") 386 | child.close() 387 | 388 | # cleanup 389 | shutil.rmtree(os.path.join('testdir10', 'singlefile')) 390 | os.remove(os.path.join('testdir10', 'singlefile.tar.crypt')) 391 | 392 | 393 | 394 | 395 | -------------------------------------------------------------------------------- /lib/crypto/decryptoapp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # ------------------------------------------------------------------------------ 5 | # decrypto 6 | # Copyright 2015 Christopher Simpkins 7 | # MIT license 8 | # ------------------------------------------------------------------------------ 9 | 10 | # Application start 11 | def main(): 12 | import os 13 | import sys 14 | from time import sleep 15 | import getpass 16 | import tarfile 17 | from Naked.commandline import Command 18 | from Naked.toolshed.shell import execute, muterun 19 | from Naked.toolshed.system import dir_exists, file_exists, list_all_files, make_path, stdout, stderr, is_dir 20 | from shellescape import quote 21 | 22 | # ------------------------------------------------------------------------------------------ 23 | # [ Instantiate command line object ] 24 | # used for all subsequent conditional logic in the CLI application 25 | # ------------------------------------------------------------------------------------------ 26 | c = Command(sys.argv[0], sys.argv[1:]) 27 | # ------------------------------------------------------------------------------------------ 28 | # [ VALIDATION LOGIC ] - early validation of appropriate command syntax 29 | # Test that user entered at least one argument to the executable, print usage if not 30 | # ------------------------------------------------------------------------------------------ 31 | if not c.command_suite_validates(): 32 | from crypto.settings import usage as crypto_usage 33 | print(crypto_usage) 34 | sys.exit(1) 35 | # ------------------------------------------------------------------------------------------ 36 | # [ HELP, VERSION, USAGE LOGIC ] 37 | # Naked framework provides default help, usage, and version commands for all applications 38 | # --> settings for user messages are assigned in the lib/crypto/settings.py file 39 | # ------------------------------------------------------------------------------------------ 40 | if c.help(): # User requested crypto help information 41 | from crypto.settings import help as crypto_help 42 | print(crypto_help) 43 | sys.exit(0) 44 | elif c.usage(): # User requested crypto usage information 45 | from crypto.settings import usage as crypto_usage 46 | print(crypto_usage) 47 | sys.exit(0) 48 | elif c.version(): # User requested crypto version information 49 | from crypto.settings import app_name, major_version, minor_version, patch_version 50 | version_display_string = app_name + ' ' + major_version + '.' + minor_version + '.' + patch_version 51 | print(version_display_string) 52 | sys.exit(0) 53 | # ------------------------------------------------------------------------------------------ 54 | # [ APPLICATION LOGIC ] 55 | # 56 | # ------------------------------------------------------------------------------------------ 57 | elif c.argc > 1: 58 | # code for multi-file processing and commands that include options 59 | use_standard_output = False # print to stdout flag 60 | use_file_overwrite = False # overwrite existing file 61 | untar_archives = True # untar decrypted tar archives, true by default 62 | 63 | # set user option flags 64 | if c.option('--stdout') or c.option('-s'): 65 | use_standard_output = True 66 | if c.option('--overwrite') or c.option('-o'): 67 | use_file_overwrite = True 68 | if c.option('--nountar'): 69 | untar_archives = False 70 | 71 | directory_list = [] # directory paths included in the user entered paths from the command line 72 | file_list = [] # file paths included in the user entered paths from the command line (and inside directories entered) 73 | 74 | for argument in c.argv: 75 | if file_exists(argument): # user included a file, add it to the file_list for decryption 76 | if argument.endswith('.crypt'): 77 | file_list.append(argument) # add .crypt files to the list of files for decryption 78 | elif argument.endswith('.gpg'): 79 | file_list.append(argument) 80 | elif argument.endswith('.asc'): 81 | file_list.append(argument) 82 | elif argument.endswith('.pgp'): 83 | file_list.append(argument) 84 | else: 85 | # cannot identify as an encrypted file, give it a shot anyways but warn user 86 | file_list.append(argument) 87 | stdout("Could not confirm that '" + argument + "' is encrypted based upon the file type. Attempting decryption. Keep your fingers crossed...") 88 | elif dir_exists(argument): # user included a directory, add it to the directory_list 89 | directory_list.append(argument) 90 | else: 91 | if argument[0] == "-": 92 | pass # if it is an option, do nothing 93 | else: 94 | stderr("'" + argument + "' does not appear to be an existing file or directory. Aborting decryption attempt for this request.") 95 | 96 | # unroll the contained directory files into the file_list IF they are encrypted file types 97 | if len(directory_list) > 0: 98 | for directory in directory_list: 99 | directory_file_list = list_all_files(directory) 100 | for contained_file in directory_file_list: 101 | if contained_file.endswith('.crypt'): 102 | file_list.append(make_path(directory, contained_file)) # include the file with a filepath 'directory path/contained_file path' 103 | elif contained_file.endswith('.gpg'): 104 | file_list.append(make_path(directory, contained_file)) 105 | elif contained_file.endswith('asc'): 106 | file_list.append(make_path(directory, contained_file)) 107 | elif contained_file.endswith('.pgp'): 108 | file_list.append(make_path(directory, contained_file)) 109 | 110 | # confirm that there are files for decryption, if not abort 111 | if len(file_list) == 0: 112 | stderr("Could not identify files for decryption") 113 | sys.exit(1) 114 | 115 | # get passphrase used to symmetrically decrypt the file 116 | passphrase = getpass.getpass("Please enter your passphrase: ") 117 | if len(passphrase) == 0: # confirm that user entered a passphrase 118 | stderr("You did not enter a passphrase. Please repeat your command and try again.") 119 | sys.exit(1) 120 | passphrase_confirm = getpass.getpass("Please enter your passphrase again: ") 121 | 122 | if passphrase == passphrase_confirm: 123 | # begin decryption of each requested file. the directory path was already added to the file path above 124 | for encrypted_file in file_list: 125 | # create the decrypted file name 126 | decrypted_filename = "" 127 | if encrypted_file.endswith('.crypt'): 128 | decrypted_filename = encrypted_file[0:-6] 129 | elif encrypted_file.endswith('.gpg') or encrypted_file.endswith('.asc') or encrypted_file.endswith('.pgp'): 130 | decrypted_filename = encrypted_file[0:-4] 131 | else: 132 | decrypted_filename = encrypted_file + '.decrypt' # if it was a file without a known encrypted file type, add the .decrypt suffix 133 | 134 | # determine whether file overwrite will take place with the decrypted file 135 | skip_file = False # flag that indicates this file should not be encrypted 136 | created_tmp_files = False 137 | if not use_standard_output: # if not writing a file, no need to check for overwrite 138 | if file_exists(decrypted_filename): 139 | if use_file_overwrite: # rename the existing file to temp file which will be erased or replaced (on decryption failures) below 140 | tmp_filename = decrypted_filename + '.tmp' 141 | os.rename(decrypted_filename, tmp_filename) 142 | created_tmp_files = True 143 | else: 144 | stdout("The file path '" + decrypted_filename + "' already exists. This file was not decrypted.") 145 | skip_file = True 146 | 147 | # begin decryption 148 | if not skip_file: 149 | if use_standard_output: # using --quiet flag to suppress stdout messages from gpg, just want the file data in stdout stream 150 | system_command = "gpg --batch --quiet --passphrase " + quote(passphrase) + " -d " + quote(encrypted_file) 151 | successful_execution = execute(system_command) # use naked execute function to directly push to stdout, rather than return stdout 152 | 153 | if not successful_execution: 154 | stderr("Unable to decrypt file '" + encrypted_file + "'", 0) 155 | if created_tmp_files: # restore the moved tmp file to original if decrypt failed 156 | tmp_filename = decrypted_filename + '.tmp' 157 | if file_exists(tmp_filename): 158 | os.rename(tmp_filename, decrypted_filename) 159 | else: # decryption successful but we are in stdout flag so do not include any other output from decrypto 160 | pass 161 | else: 162 | system_command = "gpg --batch -o " + quote(decrypted_filename) + " --passphrase " + quote(passphrase) + " -d " + quote(encrypted_file) 163 | response = muterun(system_command) 164 | 165 | if response.exitcode == 0: 166 | stdout("'" + encrypted_file + "' decrypted to '" + decrypted_filename + "'") 167 | else: # failed decryption 168 | if created_tmp_files: # restore the moved tmp file to original if decrypt failed 169 | tmp_filename = decrypted_filename + '.tmp' 170 | if file_exists(tmp_filename): 171 | os.rename(tmp_filename, decrypted_filename) 172 | # report the error 173 | stderr(response.stderr) 174 | stderr("Decryption failed for " + encrypted_file) 175 | 176 | # cleanup: remove the tmp file 177 | if created_tmp_files: 178 | tmp_filename = decrypted_filename + '.tmp' 179 | if file_exists(tmp_filename): 180 | os.remove(tmp_filename) 181 | 182 | # untar/extract any detected archive file(s) 183 | if untar_archives is True: 184 | if decrypted_filename.endswith('.tar') and tarfile.is_tarfile(decrypted_filename): 185 | untar_path_tuple = os.path.split(decrypted_filename) 186 | untar_path = untar_path_tuple[0] 187 | if use_file_overwrite: 188 | with tarfile.open(decrypted_filename) as tar: 189 | if len(untar_path) > 0: 190 | tar.extractall(path=untar_path) # use dir path from the decrypted_filename if not CWD 191 | stdout("'" + decrypted_filename + "' unpacked in the directory path '" + untar_path + "'") 192 | else: 193 | tar.extractall() # else use CWD 194 | stdout("'" + decrypted_filename + "' unpacked in the current working directory") 195 | else: 196 | with tarfile.TarFile(decrypted_filename, 'r', errorlevel=1) as tar: 197 | for tarinfo in tar: 198 | t_file = tarinfo.name 199 | if len(untar_path) > 0: 200 | t_file_path = os.path.join(untar_path, t_file) 201 | else: 202 | t_file_path = t_file 203 | if not os.path.exists(t_file_path): 204 | try: 205 | if len(untar_path) > 0: 206 | tar.extract(t_file, path=untar_path) # write to the appropriate dir 207 | else: 208 | tar.extract(t_file) # write to CWD 209 | except IOError as e: 210 | stderr( 211 | "Failed to unpack the file '" + t_file_path + "' [" + str( 212 | e) + "]") 213 | elif is_dir(t_file_path): 214 | pass # do nothing if it exists and is a directory, no need to warn 215 | else: # it is a file and it already exists, provide user error message 216 | stderr( 217 | "Failed to unpack the file '" + t_file_path + "'. File already exists. Use the --overwrite flag to replace existing files.") 218 | 219 | # remove the decrypted tar archive file 220 | os.remove(decrypted_filename) 221 | 222 | # overwrite the entered passphrases after file decryption is complete for all files 223 | passphrase = "" 224 | passphrase_confirm = "" 225 | 226 | # add a short pause to hinder brute force pexpect style password attacks with decrypto 227 | sleep(0.2) # 200ms pause 228 | 229 | else: # passphrases did not match 230 | passphrase = "" 231 | passphrase_confirm = "" 232 | stderr("The passphrases did not match. Please enter your command again.") 233 | sys.exit(1) 234 | 235 | elif c.argc == 1: 236 | # simple single file or directory processing with default settings 237 | path = c.arg0 238 | if file_exists(path): # SINGLE FILE 239 | check_existing_file = False # check for a file with the name of new decrypted filename in the directory 240 | 241 | if path.endswith('.crypt'): 242 | decrypted_filename = path[0:-6] # remove the .crypt suffix 243 | check_existing_file = True 244 | elif path.endswith('.gpg') or path.endswith('.pgp') or path.endswith('.asc'): 245 | decrypted_filename = path[0:-4] 246 | check_existing_file = True 247 | else: 248 | decrypted_filename = path + ".decrypt" # if there is not a standard file type, then add a .decrypt suffix to the decrypted file name 249 | stdout("Could not confirm that the requested file is encrypted based upon the file type. Attempting decryption. Keep your fingers crossed...") 250 | 251 | # confirm that the decrypted path does not already exist, if so abort with warning message to user 252 | if check_existing_file is True: 253 | if file_exists(decrypted_filename): 254 | stderr("Your file will be decrypted to '" + decrypted_filename + "' and this file path already exists. Please move the file or use the --overwrite option with your command if you intend to replace the current file.") 255 | sys.exit(1) 256 | 257 | # get passphrase used to symmetrically decrypt the file 258 | passphrase = getpass.getpass("Please enter your passphrase: ") 259 | if len(passphrase) == 0: # confirm that user entered a passphrase 260 | stderr("You did not enter a passphrase. Please repeat your command and try again.") 261 | sys.exit(1) 262 | passphrase_confirm = getpass.getpass("Please enter your passphrase again: ") 263 | 264 | # confirm that the passphrases match 265 | if passphrase == passphrase_confirm: 266 | system_command = "gpg --batch -o " + quote(decrypted_filename) + " --passphrase " + quote(passphrase) + " -d " + quote(path) 267 | response = muterun(system_command) 268 | 269 | if response.exitcode == 0: 270 | # unpack tar archive generated from the decryption, if present 271 | if decrypted_filename.endswith('.tar') and tarfile.is_tarfile(decrypted_filename): 272 | untar_path_tuple = os.path.split(decrypted_filename) 273 | untar_path = untar_path_tuple[0] 274 | 275 | with tarfile.TarFile(decrypted_filename, 'r', errorlevel=1) as tar: 276 | for tarinfo in tar: 277 | t_file = tarinfo.name 278 | if len(untar_path) > 0: 279 | t_file_path = os.path.join(untar_path, t_file) 280 | else: 281 | t_file_path = t_file 282 | if not os.path.exists(t_file_path): 283 | try: 284 | if len(untar_path) > 0: 285 | tar.extract(t_file, path=untar_path) # write to the appropriate dir 286 | else: 287 | tar.extract(t_file) # write to CWD 288 | except IOError as e: 289 | stderr("Failed to unpack the file '" + t_file_path + "' [" + str(e) + "]") 290 | elif is_dir(t_file_path): 291 | pass # do nothing if it exists and is a directory, no need to warn 292 | else: # it is a file and it already exists, provide user error message 293 | stderr("Failed to unpack the file '" + t_file_path + "'. File already exists. Use the --overwrite flag to replace existing files.") 294 | 295 | # remove the decrypted tar archive 296 | os.remove(decrypted_filename) 297 | 298 | stdout("Decryption complete") 299 | # overwrite user entered passphrases 300 | passphrase = "" 301 | passphrase_confirm = "" 302 | sys.exit(0) 303 | else: 304 | stderr(response.stderr) 305 | stderr("Decryption failed") 306 | # overwrite user entered passphrases 307 | passphrase = "" 308 | passphrase_confirm = "" 309 | # add a short pause to hinder brute force pexpect style password attacks with decrypto 310 | sleep(0.2) # 200ms pause 311 | sys.exit(1) 312 | else: 313 | stderr("The passphrases did not match. Please enter your command again.") 314 | sys.exit(1) 315 | elif dir_exists(path): # SINGLE DIRECTORY 316 | dirty_directory_file_list = list_all_files(path) 317 | directory_file_list = [x for x in dirty_directory_file_list if (x.endswith('.crypt') or x.endswith('.gpg') or x.endswith('.pgp') or x.endswith('.asc'))] 318 | 319 | # if there are no encrypted files found, warn and abort 320 | if len(directory_file_list) == 0: 321 | stderr("There are no encrypted files in the directory") 322 | sys.exit(1) 323 | 324 | # prompt for the passphrase 325 | passphrase = getpass.getpass("Please enter your passphrase: ") 326 | if len(passphrase) == 0: # confirm that user entered a passphrase 327 | stderr("You did not enter a passphrase. Please repeat your command and try again.") 328 | sys.exit(1) 329 | passphrase_confirm = getpass.getpass("Please enter your passphrase again: ") 330 | 331 | if passphrase == passphrase_confirm: 332 | # decrypt all of the encypted files in the directory 333 | for filepath in directory_file_list: 334 | absolute_filepath = make_path(path, filepath) # combine the directory path and file name into absolute path 335 | 336 | # remove file suffix from the decrypted file path that writes to disk 337 | if absolute_filepath.endswith('.crypt'): 338 | decrypted_filepath = absolute_filepath[0:-6] # remove the .crypt suffix 339 | elif absolute_filepath.endswith('.gpg') or absolute_filepath.endswith('.pgp') or absolute_filepath.endswith('.asc'): 340 | decrypted_filepath = absolute_filepath[0:-4] 341 | 342 | # confirm that the file does not already exist 343 | if file_exists(decrypted_filepath): 344 | stdout("The file path '" + decrypted_filepath + "' already exists. This file was not decrypted.") 345 | else: 346 | system_command = "gpg --batch -o " + quote(decrypted_filepath) + " --passphrase " + quote(passphrase) + " -d " + quote(absolute_filepath) 347 | response = muterun(system_command) 348 | 349 | if response.exitcode == 0: 350 | stdout("'" + absolute_filepath + "' decrypted to '" + decrypted_filepath + "'") 351 | else: 352 | stderr(response.stderr) 353 | stderr("Decryption failed for " + absolute_filepath) 354 | # overwrite user entered passphrases 355 | passphrase = "" 356 | passphrase_confirm = "" 357 | 358 | # add a short pause to hinder brute force pexpect style password attacks with decrypto 359 | sleep(0.2) # 200ms pause 360 | else: 361 | # overwrite user entered passphrases 362 | passphrase = "" 363 | passphrase_confirm = "" 364 | stderr("The passphrases did not match. Please enter your command again.") 365 | sys.exit(1) 366 | else: 367 | # error message, not a file or directory. user entry error 368 | stderr("The path that you entered does not appear to be an existing file or directory. Please try again.") 369 | sys.exit(1) 370 | 371 | # ------------------------------------------------------------------------------------------ 372 | # [ DEFAULT MESSAGE FOR MATCH FAILURE ] 373 | # Message to provide to the user when all above conditional logic fails to meet a true condition 374 | # ------------------------------------------------------------------------------------------ 375 | else: 376 | print("Could not complete your request. Please try again.") 377 | sys.exit(1) 378 | 379 | if __name__ == '__main__': 380 | main() 381 | --------------------------------------------------------------------------------