├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.rst ├── docs ├── Makefile ├── conf.py ├── file_encryptor.rst ├── index.rst └── modules.rst ├── file_encryptor ├── __init__.py ├── convergence.py ├── key_generators.py └── settings.py ├── setup.cfg ├── setup.py └── tests ├── test_convergence.py └── test_key_generators.py /.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 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | # OS X 57 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 2.7 4 | - 3.3 5 | - 3.4 6 | install: 7 | - pip install coverage 8 | script: 9 | - python setup.py install 10 | - coverage run setup.py test -a "--doctest-modules --pep8 -v tests/ file_encryptor/" 11 | - coverage report -m --include="file_encryptor/*" 12 | after_success: coveralls 13 | notifications: 14 | slack: 15 | secure: SGzOs68mIHQdliO/9CVKfYVbkS4d72dB5ta7PggnJsIGl6Eq8dRruRa5oj5tYLbVkPW4xMUGvBzETc8w0U0bgIsrkF03OMzZU9nnBOckvf3L4tu3+nLOiiYJgyQdSOYnmL39s1u1fOjQZp6Z2aTF4g8t7OzxZnBoIKNDdFklgos= 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Storj Labs 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. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | File Encryptor 2 | ============== 3 | 4 | |Build Status| |Coverage Status| |PyPI version| 5 | 6 | This is a library used by MetaDisk to convergently encrypt and decrypt 7 | files. It contains helper methods to encrypt and decrypt files inline 8 | (without using extra space) and to stream decryption. 9 | 10 | Installation 11 | ------------ 12 | 13 | You can easily install ``file-encryptor`` using pip: 14 | 15 | :: 16 | 17 | pip install file_encryptor 18 | 19 | Usage 20 | ----- 21 | 22 | Here’s an example to encrypt a file inline using convergent encryption: 23 | 24 | .. code:: python 25 | 26 | import file_encryptor.convergence 27 | 28 | key = convergence.encrypt_inline_file("/path/to/file", None) 29 | 30 | You can also specify a passphrase: 31 | 32 | .. code:: python 33 | 34 | import file_encryptor.convergence 35 | 36 | key = convergence.encrypt_inline_file("/path/to/file", "rainbow dinosaur secret") 37 | 38 | To decrypt a file inline, you need the key that was returned by the 39 | encrypt method: 40 | 41 | .. code:: python 42 | 43 | import file_encryptor.convergence 44 | 45 | key = convergence.encrypt_inline_file("/path/to/file", "rainbow dinosaur secret") 46 | 47 | convergence.decrypt_inline_file("/path/to/file", key) 48 | 49 | The reason why you cannot use the passphrase directly is because the key 50 | is derived from both the passphrase and the SHA-256 of the original 51 | file. 52 | 53 | For streaming applications, you can decrypt a file with a generator: 54 | 55 | .. code:: python 56 | 57 | for chunk in convergence.decrypt_generator("/path/to/file", key): 58 | do_something_with_chunk(chunk) 59 | 60 | Cryptoconcerns 61 | -------------- 62 | 63 | The key generation mechanism is the following: 64 | 65 | .. code:: python 66 | 67 | key = HMAC-SHA256(passphrase, hex(SHA256(file-contents))) 68 | 69 | If no passphrase is given, a default is used. 70 | 71 | The file itself is encrypted using AES128-CTR, from pycrypto. We’re not 72 | specifying any IV, thinking that for convergent encryption that is the 73 | right thing to do. 74 | 75 | Testing 76 | ------- 77 | 78 | To run tests, execute the following command in the project root: 79 | 80 | :: 81 | 82 | python setup.py test -a "--doctest-modules --pep8 -v tests/" 83 | 84 | To run tests with detailed coverage output, execute: 85 | 86 | :: 87 | 88 | coverage run setup.py test -a "--doctest-modules --pep8 -v tests/" 89 | coverage report -m --include="file_encryptor/*" 90 | 91 | .. |Build Status| image:: https://travis-ci.org/Storj/file-encryptor.svg 92 | :target: https://travis-ci.org/Storj/file-encryptor 93 | .. |Coverage Status| image:: https://coveralls.io/repos/Storj/file-encryptor/badge.png?branch=master 94 | :target: https://coveralls.io/r/Storj/file-encryptor?branch=master 95 | .. |PyPI version| image:: https://badge.fury.io/py/file_encryptor.svg 96 | :target: http://badge.fury.io/py/file_encryptor -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/FileEncryptor.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/FileEncryptor.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/FileEncryptor" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/FileEncryptor" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # File Encryptor documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Nov 15 22:30:20 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | #sys.path.insert(0, os.path.abspath('.')) 22 | sys.path.insert(0, os.path.abspath('../file_encryptor')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.doctest', 35 | 'sphinx.ext.intersphinx', 36 | 'sphinx.ext.coverage', 37 | 'sphinx.ext.pngmath', 38 | 'sphinx.ext.viewcode', 39 | ] 40 | 41 | # Add any paths that contain templates here, relative to this directory. 42 | templates_path = ['_templates'] 43 | 44 | # The suffix of source filenames. 45 | source_suffix = '.rst' 46 | 47 | # The encoding of source files. 48 | #source_encoding = 'utf-8-sig' 49 | 50 | # The master toctree document. 51 | master_doc = 'index' 52 | 53 | # General information about the project. 54 | project = u'File Encryptor' 55 | copyright = u'2014, Storj Labs' 56 | 57 | # The version info for the project you're documenting, acts as replacement for 58 | # |version| and |release|, also used in various other places throughout the 59 | # built documents. 60 | # 61 | # The short X.Y version. 62 | version = '0.2.0' 63 | # The full version, including alpha/beta/rc tags. 64 | release = '0.2.0' 65 | 66 | # The language for content autogenerated by Sphinx. Refer to documentation 67 | # for a list of supported languages. 68 | #language = None 69 | 70 | # There are two options for replacing |today|: either, you set today to some 71 | # non-false value, then it is used: 72 | #today = '' 73 | # Else, today_fmt is used as the format for a strftime call. 74 | #today_fmt = '%B %d, %Y' 75 | 76 | # List of patterns, relative to source directory, that match files and 77 | # directories to ignore when looking for source files. 78 | exclude_patterns = ['_build'] 79 | 80 | # The reST default role (used for this markup: `text`) to use for all 81 | # documents. 82 | #default_role = None 83 | 84 | # If true, '()' will be appended to :func: etc. cross-reference text. 85 | #add_function_parentheses = True 86 | 87 | # If true, the current module name will be prepended to all description 88 | # unit titles (such as .. function::). 89 | #add_module_names = True 90 | 91 | # If true, sectionauthor and moduleauthor directives will be shown in the 92 | # output. They are ignored by default. 93 | #show_authors = False 94 | 95 | # The name of the Pygments (syntax highlighting) style to use. 96 | pygments_style = 'sphinx' 97 | 98 | # A list of ignored prefixes for module index sorting. 99 | #modindex_common_prefix = [] 100 | 101 | # If true, keep warnings as "system message" paragraphs in the built documents. 102 | #keep_warnings = False 103 | 104 | 105 | # -- Options for HTML output ---------------------------------------------- 106 | 107 | # The theme to use for HTML and HTML Help pages. See the documentation for 108 | # a list of builtin themes. 109 | html_theme = 'default' 110 | 111 | # Theme options are theme-specific and customize the look and feel of a theme 112 | # further. For a list of options available for each theme, see the 113 | # documentation. 114 | #html_theme_options = {} 115 | 116 | # Add any paths that contain custom themes here, relative to this directory. 117 | #html_theme_path = [] 118 | 119 | # The name for this set of Sphinx documents. If None, it defaults to 120 | # " v documentation". 121 | #html_title = None 122 | 123 | # A shorter title for the navigation bar. Default is the same as html_title. 124 | #html_short_title = None 125 | 126 | # The name of an image file (relative to this directory) to place at the top 127 | # of the sidebar. 128 | #html_logo = None 129 | 130 | # The name of an image file (within the static path) to use as favicon of the 131 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 132 | # pixels large. 133 | #html_favicon = None 134 | 135 | # Add any paths that contain custom static files (such as style sheets) here, 136 | # relative to this directory. They are copied after the builtin static files, 137 | # so a file named "default.css" will overwrite the builtin "default.css". 138 | html_static_path = ['_static'] 139 | 140 | # Add any extra paths that contain custom files (such as robots.txt or 141 | # .htaccess) here, relative to this directory. These files are copied 142 | # directly to the root of the documentation. 143 | #html_extra_path = [] 144 | 145 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 146 | # using the given strftime format. 147 | #html_last_updated_fmt = '%b %d, %Y' 148 | 149 | # If true, SmartyPants will be used to convert quotes and dashes to 150 | # typographically correct entities. 151 | #html_use_smartypants = True 152 | 153 | # Custom sidebar templates, maps document names to template names. 154 | #html_sidebars = {} 155 | 156 | # Additional templates that should be rendered to pages, maps page names to 157 | # template names. 158 | #html_additional_pages = {} 159 | 160 | # If false, no module index is generated. 161 | #html_domain_indices = True 162 | 163 | # If false, no index is generated. 164 | #html_use_index = True 165 | 166 | # If true, the index is split into individual pages for each letter. 167 | #html_split_index = False 168 | 169 | # If true, links to the reST sources are added to the pages. 170 | #html_show_sourcelink = True 171 | 172 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 173 | #html_show_sphinx = True 174 | 175 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 176 | #html_show_copyright = True 177 | 178 | # If true, an OpenSearch description file will be output, and all pages will 179 | # contain a tag referring to it. The value of this option must be the 180 | # base URL from which the finished HTML is served. 181 | #html_use_opensearch = '' 182 | 183 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 184 | #html_file_suffix = None 185 | 186 | # Output file base name for HTML help builder. 187 | htmlhelp_basename = 'FileEncryptordoc' 188 | 189 | 190 | # -- Options for LaTeX output --------------------------------------------- 191 | 192 | latex_elements = { 193 | # The paper size ('letterpaper' or 'a4paper'). 194 | #'papersize': 'letterpaper', 195 | 196 | # The font size ('10pt', '11pt' or '12pt'). 197 | #'pointsize': '10pt', 198 | 199 | # Additional stuff for the LaTeX preamble. 200 | #'preamble': '', 201 | } 202 | 203 | # Grouping the document tree into LaTeX files. List of tuples 204 | # (source start file, target name, title, 205 | # author, documentclass [howto, manual, or own class]). 206 | latex_documents = [ 207 | ('index', 'FileEncryptor.tex', u'File Encryptor Documentation', 208 | u'Storj Labs', 'manual'), 209 | ] 210 | 211 | # The name of an image file (relative to this directory) to place at the top of 212 | # the title page. 213 | #latex_logo = None 214 | 215 | # For "manual" documents, if this is true, then toplevel headings are parts, 216 | # not chapters. 217 | #latex_use_parts = False 218 | 219 | # If true, show page references after internal links. 220 | #latex_show_pagerefs = False 221 | 222 | # If true, show URL addresses after external links. 223 | #latex_show_urls = False 224 | 225 | # Documents to append as an appendix to all manuals. 226 | #latex_appendices = [] 227 | 228 | # If false, no module index is generated. 229 | #latex_domain_indices = True 230 | 231 | 232 | # -- Options for manual page output --------------------------------------- 233 | 234 | # One entry per manual page. List of tuples 235 | # (source start file, name, description, authors, manual section). 236 | man_pages = [ 237 | ('index', 'fileencryptor', u'File Encryptor Documentation', 238 | [u'Storj Labs'], 1) 239 | ] 240 | 241 | # If true, show URL addresses after external links. 242 | #man_show_urls = False 243 | 244 | 245 | # -- Options for Texinfo output ------------------------------------------- 246 | 247 | # Grouping the document tree into Texinfo files. List of tuples 248 | # (source start file, target name, title, author, 249 | # dir menu entry, description, category) 250 | texinfo_documents = [ 251 | ('index', 'FileEncryptor', u'File Encryptor Documentation', 252 | u'Storj Labs', 'FileEncryptor', 'One line description of project.', 253 | 'Miscellaneous'), 254 | ] 255 | 256 | # Documents to append as an appendix to all manuals. 257 | #texinfo_appendices = [] 258 | 259 | # If false, no module index is generated. 260 | #texinfo_domain_indices = True 261 | 262 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 263 | #texinfo_show_urls = 'footnote' 264 | 265 | # If true, do not generate a @detailmenu in the "Top" node's menu. 266 | #texinfo_no_detailmenu = False 267 | 268 | 269 | # Example configuration for intersphinx: refer to the Python standard library. 270 | intersphinx_mapping = {'http://docs.python.org/': None} 271 | -------------------------------------------------------------------------------- /docs/file_encryptor.rst: -------------------------------------------------------------------------------- 1 | file_encryptor package 2 | ====================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | file_encryptor.convergence module 8 | --------------------------------- 9 | 10 | .. automodule:: file_encryptor.convergence 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | file_encryptor.key_generators module 16 | ------------------------------------ 17 | 18 | .. automodule:: file_encryptor.key_generators 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | file_encryptor.settings module 24 | ------------------------------ 25 | 26 | .. automodule:: file_encryptor.settings 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: file_encryptor 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. File Encryptor documentation master file, created by 2 | sphinx-quickstart on Sat Nov 15 22:30:20 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. include:: ../README.rst 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | 11 | 12 | Module Indices and Tables 13 | ========================= 14 | 15 | * :ref:`genindex` 16 | * :ref:`modindex` 17 | * :ref:`search` 18 | 19 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | file_encryptor 2 | ============== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | file_encryptor 8 | -------------------------------------------------------------------------------- /file_encryptor/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2014 Storj Labs 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | from file_encryptor import (convergence, key_generators) 27 | -------------------------------------------------------------------------------- /file_encryptor/convergence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2014 Storj Labs 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | from Crypto.Cipher import AES 27 | from Crypto.Util import Counter 28 | from file_encryptor.settings import CHUNK_SIZE 29 | 30 | from file_encryptor import key_generators 31 | 32 | 33 | def encrypt_file_inline(filename, passphrase): 34 | """Encrypt file inline, with an optional passphrase. 35 | 36 | If you set the passphrase to None, a default is used. 37 | This will make you vulnerable to confirmation attacks 38 | and learn-partial-information attacks. 39 | 40 | :param filename: The name of the file to encrypt. 41 | :type filename: str 42 | :param passphrase: The passphrase used to decrypt the file. 43 | :type passphrase: str or None 44 | :returns: The key required to decrypt the file. 45 | :rtype: str 46 | """ 47 | key = key_generators.key_from_file(filename, passphrase) 48 | 49 | inline_transform(filename, key) 50 | 51 | return key 52 | 53 | 54 | def decrypt_file_inline(filename, key): 55 | """Decrypt file inline with given key. 56 | 57 | The given key must be the same that was 58 | returned by encrypt_file_inline. 59 | 60 | :param filename: The name of the file to decrypt. 61 | :type filename: str 62 | :param key: The key used to decrypt the file. 63 | :type key: str 64 | """ 65 | inline_transform(filename, key) 66 | 67 | 68 | def decrypt_generator(filename, key): 69 | """Stream decrypted file with given key. 70 | 71 | The given key must be the same that was 72 | returned by encrypt_file_inline. 73 | 74 | :param filename: The name of the file to decrypt. 75 | :type filename: str 76 | :param key: The key used to decrypt the file. 77 | :type key: str 78 | :returns: A generator that streams decrypted file chunks. 79 | :rtype: generator 80 | """ 81 | for chunk, _ in iter_transform(filename, key): 82 | yield chunk 83 | 84 | 85 | def inline_transform(filename, key): 86 | """Encrypt file inline. 87 | 88 | Encrypts a given file with the given key, 89 | and replaces it directly without any extra 90 | space requirement. 91 | 92 | :param filename: The name of the file to encrypt. 93 | :type filename: str 94 | :param key: The key used to encrypt the file. 95 | :type key: str 96 | """ 97 | pos = 0 98 | for chunk, fp in iter_transform(filename, key): 99 | fp.seek(pos) 100 | fp.write(chunk) 101 | fp.flush() 102 | pos = fp.tell() 103 | 104 | 105 | def iter_transform(filename, key): 106 | """Generate encrypted file with given key. 107 | 108 | This generator function reads the file 109 | in chunks and encrypts them using AES-CTR, 110 | with the specified key. 111 | 112 | :param filename: The name of the file to encrypt. 113 | :type filename: str 114 | :param key: The key used to encrypt the file. 115 | :type key: str 116 | :returns: A generator that produces encrypted file chunks. 117 | :rtype: generator 118 | """ 119 | # We are not specifying the IV here. 120 | aes = AES.new(key, AES.MODE_CTR, counter=Counter.new(128)) 121 | 122 | with open(filename, 'rb+') as f: 123 | for chunk in iter(lambda: f.read(CHUNK_SIZE), b''): 124 | yield aes.encrypt(chunk), f 125 | -------------------------------------------------------------------------------- /file_encryptor/key_generators.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2014 Storj Labs 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | import hashlib 27 | import hmac 28 | 29 | from file_encryptor.settings import CHUNK_SIZE, DEFAULT_HMAC_PASSPHRASE 30 | 31 | 32 | def sha256_file(path): 33 | """Calculate sha256 hex digest of a file. 34 | 35 | :param path: The path of the file you are calculating the digest of. 36 | :type path: str 37 | :returns: The sha256 hex digest of the specified file. 38 | :rtype: builtin_function_or_method 39 | """ 40 | h = hashlib.sha256() 41 | 42 | with open(path, 'rb') as f: 43 | for chunk in iter(lambda: f.read(CHUNK_SIZE), b''): 44 | h.update(chunk) 45 | 46 | return h.hexdigest() 47 | 48 | 49 | def key_from_file(filename, passphrase): 50 | """Calculate convergent encryption key. 51 | 52 | This takes a filename and an optional passphrase. 53 | If no passphrase is given, a default is used. 54 | Using the default passphrase means you will be 55 | vulnerable to confirmation attacks and 56 | learn-partial-information attacks. 57 | 58 | :param filename: The filename you want to create a key for. 59 | :type filename: str 60 | :param passphrase: The passphrase you want to use to encrypt the file. 61 | :type passphrase: str or None 62 | :returns: A convergent encryption key. 63 | :rtype: str 64 | """ 65 | hexdigest = sha256_file(filename) 66 | 67 | if passphrase is None: 68 | passphrase = DEFAULT_HMAC_PASSPHRASE 69 | 70 | return keyed_hash(hexdigest, passphrase) 71 | 72 | 73 | def keyed_hash(digest, passphrase): 74 | """Calculate a HMAC/keyed hash. 75 | 76 | :param digest: Digest used to create hash. 77 | :type digest: str 78 | :param passphrase: Passphrase used to generate the hash. 79 | :type passphrase: str 80 | :returns: HMAC/keyed hash. 81 | :rtype: str 82 | """ 83 | encodedPassphrase = passphrase.encode() 84 | encodedDigest = digest.encode() 85 | 86 | return hmac.new(encodedPassphrase, encodedDigest, hashlib.sha256).digest() 87 | -------------------------------------------------------------------------------- /file_encryptor/settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2014 Storj Labs 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | # The default chunk size for files. 27 | # The current value is equivalent to 16 kb. 28 | CHUNK_SIZE = 2**14 29 | 30 | # The default HMAC passphrase for encrypting files. 31 | DEFAULT_HMAC_PASSPHRASE = 'Something old, something new.' 32 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.rst 3 | 4 | [build_sphinx] 5 | source-dir = docs 6 | build-dir = docs/_build 7 | all_files = 1 8 | 9 | [upload_sphinx] 10 | upload-dir = docs/_build/html 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2014-2015 Storj Labs 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | import sys 27 | 28 | from setuptools import setup 29 | from setuptools.command.test import test as TestCommand 30 | 31 | LONG_DESCRIPTION = '' 32 | 33 | if sys.version_info[:1] < (3,): 34 | LONG_DESCRIPTION = open('README.rst').read() 35 | else: 36 | LONG_DESCRIPTION = open('README.rst', encoding='utf-8').read() 37 | 38 | VERSION = '0.2.6' 39 | 40 | install_requirements = [ 41 | 'pycrypto>=2.6.1' 42 | ] 43 | 44 | test_requirements = [ 45 | 'pytest', 46 | 'pytest-pep8', 47 | 'pytest-cache', 48 | 'coveralls' 49 | ] 50 | 51 | 52 | class PyTest(TestCommand): 53 | user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")] 54 | 55 | def initialize_options(self): 56 | TestCommand.initialize_options(self) 57 | self.pytest_args = [] 58 | 59 | def finalize_options(self): 60 | TestCommand.finalize_options(self) 61 | self.test_args = [] 62 | self.test_suite = True 63 | 64 | def run_tests(self): 65 | # Import PyTest here because outside, the eggs are not loaded. 66 | import pytest 67 | import sys 68 | errno = pytest.main(self.pytest_args) 69 | sys.exit(errno) 70 | 71 | setup( 72 | name='file_encryptor', 73 | version=VERSION, 74 | url='https://github.com/Storj/file-encryptor', 75 | download_url='https://github.com/storj/file-encryptor/tarball/' + VERSION, 76 | license=open('LICENSE').read(), 77 | author='Storj Labs', 78 | author_email='hello@storj.io', 79 | description='Tool for convergently encrypting files used by MetaDisk.', 80 | long_description=LONG_DESCRIPTION, 81 | packages=['file_encryptor'], 82 | cmdclass={'test': PyTest}, 83 | install_requires=install_requirements, 84 | tests_require=test_requirements, 85 | keywords=['storj', 'metadisk', 'convergent encryption'] 86 | ) 87 | -------------------------------------------------------------------------------- /tests/test_convergence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2014 Storj Labs 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | import unittest 27 | import tempfile 28 | import os 29 | 30 | from file_encryptor import convergence 31 | 32 | 33 | class TestConvergence(unittest.TestCase): 34 | 35 | def setUp(self): 36 | self.directory = tempfile.mkdtemp() 37 | 38 | self.sample1 = os.path.join(self.directory, 'super1.txt') 39 | self.sample2 = os.path.join(self.directory, 'super2.txt') 40 | self.sample3 = os.path.join(self.directory, 'frowny.txt') 41 | 42 | with open(self.sample1, 'wb') as f: 43 | f.write('Superstar!\n'.encode()) 44 | 45 | with open(self.sample2, 'wb') as f: 46 | f.write('Superstar!\n'.encode()) 47 | 48 | with open(self.sample3, 'wb') as f: 49 | f.write('Frowny face :(\n'.encode()) 50 | 51 | def contents(self, name): 52 | with open(name, 'rb') as f: 53 | return f.read() 54 | 55 | def tearDown(self): 56 | os.remove(self.sample1) 57 | os.remove(self.sample2) 58 | os.remove(self.sample3) 59 | os.rmdir(self.directory) 60 | 61 | def test_inline_encryption(self): 62 | convergence.encrypt_file_inline(self.sample1, None) 63 | 64 | self.assertNotEqual( 65 | self.contents(self.sample1), 66 | 'Superstar!\n'.encode()) 67 | 68 | def test_inline_encryption_with_passphrase(self): 69 | convergence.encrypt_file_inline(self.sample1, 'potato') 70 | 71 | def test_deterministic_inline_encryption(self): 72 | convergence.encrypt_file_inline(self.sample1, None) 73 | convergence.encrypt_file_inline(self.sample2, None) 74 | 75 | self.assertEqual( 76 | self.contents(self.sample1), 77 | self.contents(self.sample2)) 78 | 79 | def test_passphrase_does_something(self): 80 | convergence.encrypt_file_inline(self.sample1, 'first') 81 | convergence.encrypt_file_inline(self.sample2, 'second') 82 | 83 | self.assertNotEqual( 84 | self.contents(self.sample1), 85 | self.contents(self.sample2)) 86 | 87 | def test_inline_decryption(self): 88 | plaintext = self.contents(self.sample1) 89 | 90 | key = convergence.encrypt_file_inline(self.sample1, None) 91 | 92 | convergence.decrypt_file_inline(self.sample1, key) 93 | 94 | self.assertEqual(plaintext, self.contents(self.sample1)) 95 | 96 | def test_inline_decryption_with_passphrase(self): 97 | plaintext = self.contents(self.sample1) 98 | 99 | key = convergence.encrypt_file_inline(self.sample1, 'super secret') 100 | 101 | convergence.decrypt_file_inline(self.sample1, key) 102 | 103 | self.assertEqual(plaintext, self.contents(self.sample1)) 104 | 105 | def test_streaming_decryption(self): 106 | plaintext = self.contents(self.sample1) 107 | 108 | key = convergence.encrypt_file_inline(self.sample1, 'super secret') 109 | 110 | decrypted = ''.encode() 111 | for chunk in convergence.decrypt_generator(self.sample1, key): 112 | decrypted += chunk 113 | 114 | self.assertEqual(plaintext, decrypted) 115 | -------------------------------------------------------------------------------- /tests/test_key_generators.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2014 Storj Labs 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | import unittest 27 | import tempfile 28 | import os 29 | 30 | from file_encryptor import key_generators 31 | 32 | 33 | class TestKeyGenerators(unittest.TestCase): 34 | 35 | def setUp(self): 36 | self.directory = tempfile.mkdtemp() 37 | 38 | self.sample1 = os.path.join(self.directory, 'super1.txt') 39 | self.sample2 = os.path.join(self.directory, 'super2.txt') 40 | self.sample3 = os.path.join(self.directory, 'frowny.txt') 41 | 42 | with open(self.sample1, 'wb') as f: 43 | f.write('Superstar!\n'.encode()) 44 | 45 | with open(self.sample2, 'wb') as f: 46 | f.write('Superstar!\n'.encode()) 47 | 48 | with open(self.sample3, 'wb') as f: 49 | f.write('Frowny face :(\n'.encode()) 50 | 51 | def tearDown(self): 52 | os.remove(self.sample1) 53 | os.remove(self.sample2) 54 | os.remove(self.sample3) 55 | os.rmdir(self.directory) 56 | 57 | def test_key_generation(self): 58 | key_generators.key_from_file(self.sample1, None) 59 | 60 | def test_deterministic_key_generation(self): 61 | self.assertEqual( 62 | key_generators.key_from_file(self.sample1, None), 63 | key_generators.key_from_file(self.sample2, None)) 64 | 65 | def test_passphrase_does_something(self): 66 | self.assertNotEqual( 67 | key_generators.key_from_file(self.sample1, 'Shh!'), 68 | key_generators.key_from_file(self.sample2, 'Hello')) 69 | 70 | def test_different_files_yield_different_keys(self): 71 | self.assertNotEqual( 72 | key_generators.key_from_file(self.sample1, None), 73 | key_generators.key_from_file(self.sample3, None)) 74 | --------------------------------------------------------------------------------