├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ ├── code-quality.yml │ └── test.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.rst ├── LICENSE.txt ├── README.rst ├── docs ├── Makefile ├── _static │ └── Magic-Logo.svg ├── alternatives.rst ├── caveats.rst ├── cli.rst ├── compatibility.rst ├── conf.py ├── index.rst ├── installation.rst ├── phe.rst ├── requirements.txt ├── serialisation.rst └── usage.rst ├── examples ├── alternative_base.py ├── benchmarks.py ├── federated_learning_with_encryption.py └── logistic_regression_encrypted_model.py ├── phe ├── __about__.py ├── __init__.py ├── command_line.py ├── encoding.py ├── paillier.py ├── tests │ ├── __init__.py │ ├── cli_test.py │ ├── math_test.py │ ├── paillier_test.py │ └── util_test.py └── util.py ├── requirements.txt ├── setup.cfg ├── setup.py └── third_party ├── gmpy2 ├── COPYING.txt └── README ├── nose └── README ├── numpy ├── README └── license.txt ├── pycrypto ├── COPYRIGHT.txt └── README └── sphinx └── README /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Enable version updates for Python Deps 4 | - package-ecosystem: "pip" 5 | directory: "/" 6 | # Check for updates once a week 7 | schedule: 8 | interval: "weekly" 9 | # Maintain dependencies for GitHub Actions 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: "weekly" 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and upload to PyPI 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build_dist: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Set up Python 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: '3.x' 18 | - name: Install dependencies 19 | run: | 20 | python -m pip install --upgrade pip 21 | pip install build 22 | - name: Build package 23 | run: python -m build 24 | - name: Save artifacts 25 | uses: actions/upload-artifact@v3 26 | with: 27 | name: paillier-dist 28 | path: ./dist 29 | 30 | upload_pypi: 31 | needs: [build_dist] 32 | runs-on: ubuntu-latest 33 | 34 | # upload to PyPI only on release 35 | if: github.event.release && github.event.action == 'published' 36 | steps: 37 | - uses: actions/download-artifact@v3 38 | with: 39 | name: paillier-dist 40 | path: dist 41 | 42 | - uses: pypa/gh-action-pypi-publish@v1.5.0 43 | with: 44 | user: __token__ 45 | password: ${{ secrets.PYPI_API_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/code-quality.yml: -------------------------------------------------------------------------------- 1 | name: Code Quality 2 | on: [pull_request] 3 | 4 | jobs: 5 | linting: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v3 9 | - uses: actions/setup-python@v4 10 | with: 11 | python-version: 3.x 12 | - run: pip install --upgrade pip 13 | - run: pip install pylint 14 | - run: pylint --disable=all --enable=unused-import phe 15 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Unit Test 5 | 6 | on: 7 | pull_request: 8 | branches: 9 | - "*" 10 | 11 | jobs: 12 | testing: 13 | runs-on: "ubuntu-latest" 14 | strategy: 15 | matrix: 16 | python-version: ["3.7", "3.8", "3.9", "3.10"] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Set up Python ${{ matrix.python-version }} 21 | uses: actions/setup-python@v4 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | - name: Install Dependencies 25 | shell: bash 26 | run: python -m pip install -r requirements.txt 27 | - name: Unit Test 28 | run: python setup.py test 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .gitignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | 45 | # Translations 46 | *.mo 47 | *.pot 48 | 49 | # Django stuff: 50 | *.log 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | # PyBuilder 56 | target/ 57 | 58 | 59 | ### JetBrains template 60 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 61 | 62 | *.iml 63 | 64 | ## Directory-based project format: 65 | .idea/ 66 | # if you remove the above rule, at least ignore the following: 67 | 68 | # User-specific stuff: 69 | # .idea/workspace.xml 70 | # .idea/tasks.xml 71 | # .idea/dictionaries 72 | 73 | # Sensitive or high-churn files: 74 | # .idea/dataSources.ids 75 | # .idea/dataSources.xml 76 | # .idea/sqlDataSources.xml 77 | # .idea/dynamic.xml 78 | # .idea/uiDesigner.xml 79 | 80 | # Gradle: 81 | # .idea/gradle.xml 82 | # .idea/libraries 83 | 84 | # Mongo Explorer plugin: 85 | # .idea/mongoSettings.xml 86 | 87 | ## File-based project format: 88 | *.ipr 89 | *.iws 90 | 91 | ## Plugin-specific files: 92 | 93 | # IntelliJ 94 | out/ 95 | 96 | # mpeltonen/sbt-idea plugin 97 | .idea_modules/ 98 | 99 | # JIRA plugin 100 | atlassian-ide-plugin.xml 101 | 102 | # Crashlytics plugin (for Android Studio and IntelliJ) 103 | com_crashlytics_export_strings.xml 104 | crashlytics.properties 105 | crashlytics-build.properties 106 | 107 | # Data folders downloaded for examples 108 | *enron1 109 | *enron2 110 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # After changing this file, check it on: 2 | # http://lint.travis-ci.org/ 3 | language: python 4 | sudo: false 5 | 6 | python: 7 | - '3.4' 8 | - '3.5' 9 | - '3.6' 10 | - '3.7' 11 | - '3.8' 12 | - '3.9-dev' 13 | - 'nightly' 14 | - 'pypy3' 15 | 16 | 17 | env: 18 | - TEST_PYCRYPTO=1 19 | 20 | matrix: 21 | allow_failures: 22 | - python: 'nightly' 23 | - python: 'pypy3' 24 | include: 25 | - python: '3.5' 26 | env: 27 | - USE_WHEEL=1 28 | - TESTMODE=full 29 | 30 | # In order to test with and without some libraries, we explicitly 31 | # install deps instead of installing from the requirements file 32 | 33 | addons: 34 | apt: 35 | packages: 36 | - libatlas-dev 37 | - libatlas-base-dev 38 | - liblapack-dev 39 | - gfortran 40 | - libgmp-dev 41 | - libmpfr-dev 42 | 43 | before_install: 44 | - export PYTHONFAULTHANDLER=1 45 | - export PATH=/usr/lib/ccache:$PATH 46 | - mkdir builds 47 | - pushd builds 48 | # Install gmpy2 dependencies 49 | - mkdir -p $HOME/.local 50 | - wget https://ftp.gnu.org/gnu/mpc/mpc-1.0.3.tar.gz 51 | - tar xzvf mpc-1.0.3.tar.gz 52 | - pushd mpc-1.0.3 53 | - ./configure --prefix=$HOME/.local 54 | - make 55 | - make install 56 | - popd 57 | - export CPATH=$HOME/.local/include 58 | - export LIBRARY_PATH=$HOME/.local/lib 59 | - export LD_LIBRARY_PATH=$HOME/.local/lib 60 | - travis_retry pip install gmpy2 61 | - if [ "${TESTMODE}" == "full" ]; then pip install coverage; fi 62 | - travis_retry pip install -U pip setuptools 63 | - if [ "${USE_WHEEL}" == "1" ]; then pip install wheel; fi 64 | - if [ "${TEST_PYCRYPTO}" == "1" ]; then pip install pycrypto>=2.6.1; fi 65 | - travis_retry pip install nose numpy click nose-timer 66 | - python -V 67 | - popd 68 | - set -o pipefail 69 | 70 | install: 71 | - travis_retry pip install -e . 72 | 73 | script: 74 | - nosetests -v --with-timer --timer-top-n 10 75 | - if [ "${TESTMODE}" == "full" ]; then coverage run `which nosetests`; coverage report -i phe/*; fi 76 | - if [ "${USE_WHEEL}" == "1" ]; then python setup.py bdist_wheel; fi 77 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Version 1.5.0 2 | ============= 3 | 4 | - `mulmod` now available with gmpy2 acceleration. 5 | 6 | Version 1.4.1 7 | ============= 8 | 9 | Remove support for Python 3.3. 10 | 11 | Version 1.4.0 (2018-04-19) 12 | ===== 13 | 14 | Complete pure Python fallback implementation. 15 | 16 | Features 17 | ---- 18 | 19 | - `invert` now available without `gmpy2`, implemented using the extended 20 | Euclidean algorithm (`extended_euclidean_algorithm`) 21 | - `getprimeover` now available without `gmpy2`, along with a probabilitic 22 | primality test `isprime` based on the Miller-Rabin test (`miller_rabin`) 23 | 24 | Version 1.3.0 (2017-02-08) 25 | ===== 26 | 27 | Changes to enhance performance. Using Chinese Remainder Theorem for faster 28 | decryption. Exploit property of the generator to speed up encryption. 29 | 30 | Note both the api and the serialisation has changed. 31 | 32 | - A private key now has a `p` and `q` attribute instead of `lambda` and `mu`. To 33 | continue being able to create a private key using the totient use the 34 | `PaillierPrivateKey.from_totient` static constructor. 35 | - The serialization and constructor of a public key now only requires `n`. 36 | 37 | Version 1.2.3 (2015-06-02) 38 | ===== 39 | 40 | Documentation and bugfix release. 41 | 42 | Features 43 | ---- 44 | 45 | - Better support for alternative encoding schemes (including a new example). Public key now has 46 | an `encrypt_encoded` method. `decrypt_encoded` optionally takes an `Encoding` class. 47 | - Command line tool documentation. 48 | - Much expanded notes on key serialisation. 49 | 50 | Bug Fixes 51 | ---- 52 | 53 | - Several tests for encrypt/decrypt only encoded/decoded. 54 | 55 | 56 | Version 1.2.0 (2015-01-12) 57 | ===== 58 | 59 | Features 60 | ---- 61 | 62 | - Command line tool 63 | 64 | 65 | Version 1.1 (2015-01-08) 66 | ===== 67 | 68 | Bug Fixes 69 | ---- 70 | 71 | PaillierPrivateKeyring used mutable default argument. 72 | 73 | Features 74 | ---- 75 | 76 | 77 | - Support for Python 3.5 78 | - Default keysize increased to 2048 79 | - Allow use of alternative base for EncodedNumber 80 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | python-paillier |release| 2 | ========================== 3 | 4 | +---------------------+ 5 | | |ciTest| | 6 | +---------------------+ 7 | | |rtdM| | 8 | +---------------------+ 9 | | |reqM| | 10 | +---------------------+ 11 | 12 | A Python 3 library implementing the Paillier Partially Homomorphic Encryption. 13 | 14 | The homomorphic properties of the paillier crypto system are: 15 | 16 | - Encrypted numbers can be multiplied by a non encrypted scalar. 17 | - Encrypted numbers can be added together. 18 | - Encrypted numbers can be added to non encrypted scalars. 19 | 20 | Citing 21 | ====== 22 | 23 | `python-paillier` is designed, developed and supported by `CSIRO's Data61 `__. If 24 | you use any part of this library in your research, please cite it using the following BibTex entry:: 25 | 26 | @misc{PythonPaillier, 27 | author = {CSIRO's Data61}, 28 | title = {Python Paillier Library}, 29 | year = {2013}, 30 | publisher = {GitHub}, 31 | journal = {GitHub Repository}, 32 | howpublished = {\url{https://github.com/data61/python-paillier}}, 33 | } 34 | 35 | 36 | Running unit tests 37 | ------------------ 38 | 39 | :: 40 | 41 | python setup.py test 42 | 43 | Or use nose:: 44 | 45 | nosetests 46 | 47 | 48 | Note related to gmpy2 49 | --------------------- 50 | 51 | `gmpy2` is not required to use the library, but is preferred. A pure Python implementation is available but 52 | `gmpy2` drastically improves performances. As indication on a laptop not dedicated to benchmarking, running the example 53 | `examples/federated_learning_with_encryption.py` provided in the library took: 54 | - 4.5s with `gmpy2` installed 55 | - 35.7s without `gmpy2` installed 56 | 57 | However, `gmpy2` is a requirement to run the tests. 58 | 59 | Code History 60 | ------------ 61 | 62 | Developed at `Data61 | CSIRO `_. 63 | 64 | Parts derived from the Apache licensed Google project: 65 | https://code.google.com/p/encrypted-bigquery-client/ 66 | 67 | 68 | .. |release| image:: https://img.shields.io/pypi/v/phe.svg 69 | :target: https://pypi.python.org/pypi/phe/ 70 | :alt: Latest released version on PyPi 71 | 72 | .. |ciTest| image:: https://github.com/data61/python-paillier/actions/workflows/test.yml/badge.svg 73 | :target: https://github.com/data61/python-paillier/actions/workflows/test.yml 74 | :alt: CI Status 75 | 76 | .. |reqM| image:: https://requires.io/github/data61/python-paillier/requirements.svg?branch=master 77 | :target: https://requires.io/github/data61/python-paillier/requirements/?branch=master 78 | :alt: Requirements Status of master 79 | 80 | .. |rtdM| image:: https://readthedocs.org/projects/python-paillier/badge/?version=stable 81 | :target: http://python-paillier.readthedocs.org/en/latest/?badge=stable 82 | :alt: Documentation Status 83 | 84 | -------------------------------------------------------------------------------- /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/phe.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/phe.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/phe" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/phe" 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/_static/Magic-Logo.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml -------------------------------------------------------------------------------- /docs/alternatives.rst: -------------------------------------------------------------------------------- 1 | .. _alternatives: 2 | 3 | Alternative Libraries 4 | ===================== 5 | 6 | These are brief notes on the libraries that we looked at before embarking on 7 | writing our own. 8 | 9 | 10 | Python Libraries 11 | ---------------- 12 | 13 | charm-crypto 14 | ~~~~~~~~~~~~ 15 | 16 | > Charm is a framework for rapidly prototyping advanced cryptosystems. Based on 17 | > the Python language, it was designed from the ground up to minimize development 18 | > time and code complexity while promoting the reuse of components. 19 | > 20 | > Charm uses a hybrid design: performance intensive mathematical operations are 21 | > implemented in native C modules, while cryptosystems themselves are written in 22 | > a readable, high-level language. Charm additionally provides a number of new 23 | > components to facilitate the rapid development of new schemes and protocols. 24 | 25 | 26 | http://charm-crypto.com/Main.html 27 | 28 | 29 | Paillier Code, Pascal Paillier (Public-Key) 30 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 31 | 32 | https://github.com/JHUISI/charm/blob/master/charm/schemes/pkenc/pkenc_paillier99.py 33 | 34 | Worth looking at their object hierarchy, e.g., http://jhuisi.github.io/charm/toolbox/PKEnc.html 35 | They use a Ciphertext class which has the `__add__` and `__mul__` methods overridden. 36 | 37 | 38 | Example: 39 | ^^^^^^^^ 40 | 41 | 42 | >>> from charm.toolbox.integergroup import RSAGroup 43 | >>> group = RSAGroup() 44 | >>> pai = Pai99(group) 45 | >>> (public_key, secret_key) = pai.keygen() 46 | >>> msg_1=12345678987654321 47 | >>> msg_2=12345761234123409 48 | >>> msg_3 = msg_1 + msg_2 49 | >>> msg_1 = pai.encode(public_key['n'], msg_1) 50 | >>> msg_2 = pai.encode(public_key['n'], msg_2) 51 | >>> msg_3 = pai.encode(public_key['n'], msg_3) 52 | >>> cipher_1 = pai.encrypt(public_key, msg_1) 53 | >>> cipher_2 = pai.encrypt(public_key, msg_2) 54 | >>> cipher_3 = cipher_1 + cipher_2 55 | >>> decrypted_msg_3 = pai.decrypt(public_key, secret_key, cipher_3) 56 | >>> decrypted_msg_3 == msg_3 57 | True 58 | 59 | 60 | They have even got it going on Android: http://jhuisi.github.io/charm/mobile.html 61 | 62 | mikeivanov/paillier 63 | ~~~~~~~~~~~~~~~~~~~ 64 | 65 | > Pure Python Paillier Homomorphic Cryptosystem 66 | 67 | Very simple easy to understand code. Doesn't use a Paillier object. No external dependencies. 68 | Based on the java library: https://code.google.com/p/thep/ 69 | 70 | https://github.com/mikeivanov/paillier 71 | 72 | Example Usage:: 73 | 74 | >>> from paillier import * 75 | >>> priv, pub = generate_keypair(128) 76 | >>> x = encrypt(pub, 2) 77 | >>> y = encrypt(pub, 3) 78 | >>> x,y 79 | (72109737005643982735171545918..., 9615446835366886883470187...) 80 | >>> z = e_add(pub, x, y) 81 | >>> z 82 | 71624230283745591274688669... 83 | >>> decrypt(priv, pub, z) 84 | 5 85 | 86 | 87 | Tests: 88 | ^^^^^^ 89 | 90 | Could easily be reused. 91 | 92 | https://github.com/mikeivanov/paillier/blob/master/tests/test_paillier.py 93 | 94 | 95 | encrypted-bigquery-client 96 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 97 | 98 | License: **Apache 2.0** 99 | 100 | > Paillier encryption to perform homomorphic addition on encrypted data 101 | 102 | The ebq client is an experimental client which encrypts data in the specified fields 103 | before loading to Bigquery service. Currently there are various limitations including 104 | support for only a subset of query types on encrypted data. 105 | 106 | Paillier specific code: 107 | 108 | http://pydoc.net/Python/encrypted_bigquery/1.0/paillier/ 109 | 110 | Uses openssl via `ctypes`. 111 | 112 | Features a **Paillier** object with the following methods: 113 | 114 | * `__init__(seed=None, g=None, n=None, Lambda=None, mu=None)` 115 | * `Encrypt(plaintext, r_value=None)` 116 | * `Decrypt(ciphertext)` 117 | * `Add(ciphertext1, ciphertext2)` - returns E(m1 + m2) given E(m1) and E(m2) 118 | * `Affine(self, ciphertext, a=1, b=0)` - Returns E(a*m + b) given E(m), a and b 119 | * `EncryptInt64`/`DecryptInt64` - twos complement to allow negative addition 120 | * `EncryptFloat`/`DecryptFloat` - IEEE754 binary64bit where exponent <= 389 121 | 122 | 123 | Code is well documented python2. Most arguments are `long` or `int` types. 124 | There is also a comprehensive unit test at http://pydoc.net/Python/encrypted_bigquery/1.0/paillier_test/ 125 | 126 | Even if we don't reuse any of their code the tests would be great. 127 | 128 | #### Floating point notes in code: 129 | 130 | Paillier homomorphic addition only directly adds positive binary values, 131 | however, we would like to add both positive and negative float values 132 | of different magnitudes. To achieve this, we will: 133 | 134 | - represent the mantissa and exponent as one long binary value. This means 135 | that with 1024 bit n in paillier, the maximum exponent value is 389 bits. 136 | 137 | - represent negative values with twos complement representation. 138 | 139 | - Nan, +inf, -inf are each indicated by values in there own 32 bit region, 140 | so that when one of them is added, the appropriate region would be 141 | incremented and we would know this in the final aggregated value, assuming 142 | less than 2^32 values were aggregated. 143 | 144 | - We limit the number of numbers that can be added to be less than 2^32 145 | otherwise we would not be able to detect overflows properly, etc. 146 | 147 | - Also, in order to detect overflow after adding multiple values, 148 | the 64 sign bit is extended (or replicated) for an additional 64 bits. 149 | This allows us to detect if an overflow happened and knowing whether the 150 | most significant 32 bits out of 64 is zeroes or ones, we would know if the 151 | result should be a +inf or -inf. 152 | 153 | Project Home: https://code.google.com/p/encrypted-bigquery-client/ 154 | 155 | 156 | C/C++ 157 | ----- 158 | 159 | Encounter 160 | ~~~~~~~~~ 161 | 162 | > Encounter is a software library aimed at providing a production-grade 163 | > implementation of cryptographic counters 164 | 165 | To date, Encounter implements a cryptocounter based on the Paillier 166 | public-key cryptographic scheme 167 | 168 | https://github.com/secYOUre/Encounter 169 | 170 | FNP privacy-preserving set intersection protocol 171 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 172 | 173 | A toolchain and library for privacy-preserving set intersection 174 | 175 | It comes with rudimentary command-line interface: client, server, and 176 | key-generation tool. Extension and reuse is possible through C++ interfaces. 177 | The implementation is fully thread-aware and multi-core ready, thus 178 | computation time can be shortened by modern many-core machines. We have verified 179 | significant performance gains with quad-core Xeons and Opterons, through the 180 | use of bucket allocation in the algorithm. 181 | 182 | For homomorphic encryption and decryption, both modified ElGamal cryptosystem and 183 | **Paillier cryptosystem** have been implemented on top of gmp. And yes, the source 184 | of randomness is always a headache for cryptosystem implementers; we have 185 | keyboard, file and network packet as the sources of entropy. 186 | 187 | It requires OpenSSL, gmp, gmpxx, boost, pthread, and pcap to build. 188 | It currently runs on Linux. 189 | 190 | http://fnp.sourceforge.net/ 191 | 192 | 193 | libpaillier 194 | ~~~~~~~~~~~ 195 | 196 | Library written in C and uses GMP. 197 | The privss toolkit for private stream searching is built on libpaillier. 198 | 199 | http://hms.isi.jhu.edu/acsc/libpaillier/ 200 | 201 | ### HElib 202 | ~~~~~~~~~ 203 | 204 | > HElib is a software library that implements homomorphic encryption (HE). 205 | > Currently available is an implementation of the Brakerski-Gentry-Vaikuntanathan 206 | > (BGV) scheme, along with many optimizations to make homomorphic evaluation runs 207 | > faster, focusing mostly on effective use of the Smart-Vercauteren ciphertext 208 | > packing techniques and the Gentry-Halevi-Smart optimizations. 209 | > 210 | > At its present state, this library is mostly meant for researchers working on 211 | > HE and its uses. Also currently it is fairly low-level, and is best thought of 212 | > as "assembly language for HE". That is, it provides low-level routines (set, add, 213 | > multiply, shift, etc.), with as much access to optimizations as we can give. 214 | > Hopefully in time we will be able to provide higher-level routines. 215 | 216 | 217 | https://github.com/shaih/HElib 218 | 219 | Must read: http://tommd.github.io/posts/HELib-Intro.html 220 | 221 | rinon/Simple-Homomorphic-Encryption 222 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 223 | 224 | Another C++ fully homomorphic encryption implementation. 225 | 226 | https://github.com/rinon/Simple-Homomorphic-Encryption 227 | 228 | Javascript 229 | ---------- 230 | 231 | *Javascript Cryptography Considered Harmful* - http://www.matasano.com/articles/javascript-cryptography/ 232 | 233 | mhe/jspaillier 234 | ~~~~~~~~~~~~~~ 235 | 236 | Adds the methods to the Public and Private keys. 237 | 238 | Dependencies: jsbn 239 | Demo Site: http://mhe.github.io/jspaillier/ 240 | 241 | p2p-paillier 242 | ~~~~~~~~~~~~ 243 | 244 | > allows a peer to add two numbers over a peer-to-peer network. Peers add 245 | > these two numbers without even knowing what they are. It uses Firebase 246 | > (which is centralized) in order to push commands to the peers. 247 | 248 | Demo: http://9ac345a5509a.github.io/p2p-paillier/ 249 | Code: https://github.com/9ac345a5509a/p2p-paillier 250 | 251 | Haskell 252 | ------- 253 | 254 | There is a decent-looking haskell paillier library: 255 | https://github.com/onemouth/HsPaillier 256 | 257 | **BSD license** 258 | 259 | There's just one test, which encrypts 37, decrypts it, and checks that it's still 37. 260 | 261 | 262 | Java 263 | ---- 264 | 265 | There are a bunch of paillier libraries for java. 266 | 267 | Are there any tests? 268 | 269 | UT Dallas 270 | ~~~~~~~~~ 271 | 272 | This one has documentation and two implementations: 273 | 274 | https://www.utdallas.edu/~mxk093120/paillier/javadoc/paillierp/package-summary.html 275 | 276 | Provides the structures and methods to encrypt data with the Paillier encryption scheme with thresholding. This package a simplified implementation of what is specified in the paper A Generalization of Paillier's Public-Key System with Applications to Electronic Voting by Damgård et al. Within this paper, the authors generalize the Paillier encryption scheme to permit computations modulo ns+1, allowing block length for encryption to be chosen freely. In addition to this undertaking, Damgård et al. also constructed a threshold variant of the scheme. 277 | 278 | This package provides the following features of the paper 279 | - The degree of n is fixed to 1. 280 | - A fully functional simple Paillier encryption scheme with separate key classes for easy keysharing. 281 | - Proper Thresholding for an arbitrary number of decryption servers and threshold needed to decrypt. 282 | - Non-interactive zero knowledge proofs to ensure proper encryption and decryption. 283 | 284 | Of particular note, this implementation is simple as s is fixed to be 1. This allows for simplicity at this stage of the design. Further, we hope to have added methods which would make the actual use of this package to be easy and flexible. 285 | 286 | Future features would include support for encrypting arbitrary length strings/byte arrays to avoid padding issues. 287 | 288 | BGU Crypto course 289 | ~~~~~~~~~~~~~~~~~ 290 | 291 | This one is also documented but is for a crypto course so I'm not sure 292 | how complete/practical it is intended to be. For example, it does its own keygen using `java.util.Random`. 293 | https://code.google.com/p/paillier-cryptosystem/ 294 | 295 | UMBC 296 | ~~~~ 297 | 298 | This one is mercifully short but doesn't implement add, multiply as functions or methods. Also it uses `java.util.Random`. 299 | 300 | http://www.csee.umbc.edu/~kunliu1/research/Paillier.html 301 | -------------------------------------------------------------------------------- /docs/caveats.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | Security Caveats 3 | ================ 4 | 5 | Information leakage 6 | ------------------- 7 | 8 | The :attr:`~phe.paillier.EncryptedNumber.exponent` of an 9 | :class:`~phe.paillier.EncryptedNumber` is not encrypted. By default, for floating 10 | point numbers this leads to some information leakage about the magnitude of the 11 | encrypted value. This leakage can be patched up by deciding on a fixed value for 12 | all exponents as part of the protocol; then for each 13 | :class:`~phe.paillier.EncryptedNumber`, 14 | :meth:`~phe.paillier.EncryptedNumber.decrease_exponent_to` can be called before 15 | sharing. In practice this exponent should be a lower bound for any exponent that 16 | would naturally arise. 17 | 18 | .. _alternative-base: 19 | 20 | Alternative Base for EncodedNumber 21 | ---------------------------------- 22 | 23 | *If* you need to interact with a library using another base, create a simple subclass 24 | of :class:`paillier.EncodedNumber` and ensure you include the `BASE` and `LOG2_BASE` 25 | attributes:: 26 | 27 | class AltEncodedNumber(paillier.EncodedNumber): 28 | BASE = 2 29 | LOG2_BASE = math.log(BASE, 2) 30 | 31 | 32 | .. warning:: 33 | 34 | As always, if you don't require a specific value for the unencrypted exponents after 35 | an operation, you might be leaking information about what happened - but with smaller 36 | bases this problem is exacerbated. 37 | 38 | 39 | No audit 40 | -------- 41 | 42 | This code has neither been written nor vetted by any sort of crypto expert. The crypto 43 | parts are mercifully short, however. 44 | 45 | 46 | Number Encoding Scheme 47 | ---------------------- 48 | 49 | Represents a float or int encoded for Paillier encryption. 50 | 51 | For end users, this class is mainly useful for specifying precision 52 | when adding/multiplying an :class:`EncryptedNumber` by a scalar. 53 | 54 | Any custom encoding scheme that results in an unsigned integer is 55 | supported. 56 | 57 | Notes: 58 | Paillier encryption is only defined for non-negative integers less 59 | than :attr:`PaillierPublicKey.n`. Since we frequently want to use 60 | signed integers and/or floating point numbers (luxury!), values 61 | should be encoded as a valid integer before encryption. 62 | 63 | The operations of addition and multiplication [1]_ must be 64 | preserved under this encoding. Namely: 65 | 66 | 1. Decode(Encode(a) + Encode(b)) = a + b 67 | 2. Decode(Encode(a) * Encode(b)) = a * b 68 | 69 | for any real numbers a and b. 70 | 71 | Representing signed integers is relatively easy: we exploit the 72 | modular arithmetic properties of the Paillier scheme. We choose to 73 | represent only integers between 74 | +/-:attr:`~PaillierPublicKey.max_int`, where `max_int` is 75 | approximately :attr:`~PaillierPublicKey.n`/3 (larger integers may 76 | be treated as floats). The range of values between `max_int` and 77 | `n` - `max_int` is reserved for detecting overflows. This encoding 78 | scheme supports properties #1 and #2 above. 79 | 80 | Representing floating point numbers as integers is a harder task. 81 | Here we use a variant of fixed-precision arithmetic. In fixed 82 | precision, you encode by multiplying every float by a large number 83 | (e.g. 1e6) and rounding the resulting product. You decode by 84 | dividing by that number. However, this encoding scheme does not 85 | satisfy property #2 above: upon every multiplication, you must 86 | divide by the large number. In a Paillier scheme, this is not 87 | possible to do without decrypting. For some tasks, this is 88 | acceptable or can be worked around, but for other tasks this can't 89 | be worked around. 90 | 91 | In our scheme, the "large number" is allowed to vary, and we keep 92 | track of it. It is: 93 | 94 | :attr:`BASE` ** :attr:`exponent` 95 | 96 | One number has many possible encodings; this property can be used 97 | to mitigate the leak of information due to the fact that 98 | :attr:`exponent` is never encrypted. 99 | 100 | For more details, see :meth:`~PaillierPublicKey.encode`. 101 | 102 | .. rubric:: Footnotes 103 | 104 | .. [1] Technically, since Paillier encryption only supports 105 | multiplication by a scalar, it may be possible to define a 106 | secondary encoding scheme `Encode'` such that property #2 is 107 | relaxed to: 108 | 109 | Decode(Encode(a) * Encode'(b)) = a * b 110 | 111 | We don't do this. 112 | -------------------------------------------------------------------------------- /docs/cli.rst: -------------------------------------------------------------------------------- 1 | .. _cli: 2 | 3 | ==================== 4 | Command Line Utility 5 | ==================== 6 | 7 | This cli interface allows a user to: 8 | 9 | - generate and serialize key pairs (of different key sizes) 10 | - encrypt and serialize given a public key and a plaintext number 11 | - decrypt given a private key and the ciphertext 12 | - add two encrypted numbers together 13 | - add an encrypted number to a plaintext number 14 | - multiply an encrypted number to a plaintext number 15 | 16 | 17 | Installation 18 | ------------ 19 | 20 | The command line utility is not installed by default. When installing with pip you 21 | must specify the optional extra eg:: 22 | 23 | pip install "phe[cli]" --upgrade 24 | 25 | 26 | After :ref:`installation`, the **pheutil** command line program will be installed on your path. 27 | 28 | 29 | To use the command line client without installing `python-paillier`, run the 30 | :mod:`phe.command_line` module from the project root:: 31 | 32 | python -m phe.command_line 33 | 34 | 35 | Usage Help 36 | ---------- 37 | 38 | For commands, and examples call `--help`:: 39 | 40 | $ pheutil --help 41 | 42 | Usage: pheutil [OPTIONS] COMMAND [ARGS]... 43 | 44 | CLI for interacting with python-paillier 45 | 46 | Options: 47 | --version Show the version and exit. 48 | -v, --verbose Enables verbose mode. 49 | --help Show this message and exit. 50 | 51 | Commands: 52 | add Add encrypted number to unencrypted number. 53 | addenc Add two encrypted numbers together. 54 | decrypt Decrypt ciphertext with private key. 55 | encrypt Encrypt a number with public key. 56 | extract Extract public key from private key. 57 | genpkey Generate a paillier private key. 58 | multiply Multiply encrypted num with unencrypted num. 59 | 60 | 61 | Each command also includes more detail, e.g. for `genpkey`:: 62 | 63 | $ pheutil genpkey --help 64 | Usage: pheutil genpkey [OPTIONS] OUTPUT 65 | 66 | Generate a paillier private key. 67 | 68 | Output as JWK to given output file. Use "-" to output the private key to 69 | stdout. See the extract command to extract the public component of the 70 | private key. 71 | 72 | Note: The default ID text includes the current time. 73 | 74 | Options: 75 | --keysize INTEGER The keysize in bits. Defaults to 2048 76 | --id TEXT Add an identifying comment to the key 77 | 78 | 79 | Example Session 80 | --------------- 81 | 82 | :: 83 | 84 | $ pheutil genpkey --keysize 1024 example_private_key.json 85 | Generating a paillier keypair with keysize of 1024 86 | Keys generated 87 | Private key written to example_private_key.json 88 | $ pheutil extract example_private_key.json example_public_key.json 89 | Loading paillier keypair 90 | Public key written to example_public_key.json 91 | $ pheutil encrypt --output test.enc example_public_key.json 5000 92 | Loading public key 93 | Encrypting: +5000.0000000000000000 94 | $ cat test.enc | python -m json.tool 95 | { 96 | "e": -32, 97 | "v": "8945468961282852256778220989238222172150456425808373953578229301775803205409565637223688006899379858518150634149268673387123813092667724639715011697847472787020974697972558733184395004744948252959649660835719161407306407854534355718203796283103451456746682405859634010362011442548072273622024024463167923466056606817150074423359137917704381669997696942809271828714079014827677816707229329379573217492868913536374239033718507818834874942682659422972598117458546894148344090333255242329686475806834331038677335462130194428967083103705644514152785933564702168267063628303275275994362218144323611010911197842705253655015" 98 | } 99 | $ pheutil add --output result.enc example_public_key.json test.enc 100 100 | Loading public key 101 | Loading encrypted number 102 | Loading unencrypted number 103 | Adding 104 | Exponent is less than -32 105 | $ pheutil decrypt example_private_key.json result.enc 106 | Loading private key 107 | Decrypting ciphertext 108 | 5100.0 109 | 110 | 111 | 112 | Bash Completion 113 | --------------- 114 | 115 | Bash completion can be enabled by adding the following to your `.bashrc` file:: 116 | 117 | eval "$(_PHEUTIL_COMPLETE=source pheutil)" 118 | 119 | Further information on bash completion can be found in the `click `_ 120 | documentation. 121 | -------------------------------------------------------------------------------- /docs/compatibility.rst: -------------------------------------------------------------------------------- 1 | .. _compatibility: 2 | 3 | Compatibility with other libraries 4 | ================================== 5 | 6 | This library may, with *care*, be used with other Paillier implementations. Keep in mind, that in this library 7 | the generator g of the public key is fixed to g = n + 1 (for efficiency reasons) and cannot arbitrarily be 8 | chosen as described in the Paillier paper. 9 | 10 | 11 | - `Paillier.jl `_ - Library for Julia, encoding should be compatible. 12 | - `Javallier `_ - library for Java/Scala. Somewhat 13 | different Encoding scheme. Base of 2 is fixed (see :ref:`alternative-base`). 14 | - `paillier.js `_ - Early prototype library for Javascript/Typescript. 15 | 16 | 17 | .. toctree:: 18 | :maxdepth: 2 19 | 20 | alternatives -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # phe documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Jun 11 15:00:09 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 | import pkg_resources 18 | import pip 19 | 20 | try: 21 | from unittest.mock import MagicMock 22 | except ImportError: 23 | def install(package): 24 | pip.main(['install', package]) 25 | 26 | install('mock') 27 | from mock import Mock as MagicMock 28 | 29 | try: 30 | from sphinxcontrib import spelling 31 | except ImportError: 32 | spelling = None 33 | 34 | 35 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 36 | 37 | html_theme = 'nature' 38 | 39 | # ------------------------------------------------------------------- # 40 | # MOCK MODULES 41 | # ------------------------------------------------------------------- # 42 | 43 | if on_rtd: 44 | class Mock(MagicMock): 45 | @classmethod 46 | def __getattr__(cls, name): 47 | return MagicMock() 48 | 49 | MOCK_MODULES = ['gmpy2', 'numpy'] 50 | sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) 51 | 52 | 53 | # If extensions (or modules to document with autodoc) are in another directory, 54 | # add these directories to sys.path here. If the directory is relative to the 55 | # documentation root, use os.path.abspath to make it absolute, like shown here. 56 | sys.path.insert(0, os.path.abspath('..')) 57 | 58 | # -- General configuration ------------------------------------------------ 59 | 60 | # If your documentation needs a minimal Sphinx version, state it here. 61 | needs_sphinx = '1.2' 62 | 63 | # Add any Sphinx extension module names here, as strings. They can be 64 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 65 | # ones. 66 | extensions = [ 67 | 'sphinx.ext.autodoc', 68 | 'sphinx.ext.intersphinx', 69 | 'sphinx.ext.viewcode', 70 | 'sphinx.ext.doctest', 71 | 'sphinx.ext.napoleon', # Sphinx >= 1.3 72 | #'sphinxcontrib.napoleon' # Sphinx 1.2 73 | ] 74 | 75 | if spelling is not None: 76 | extensions.append('sphinxcontrib.spelling') 77 | 78 | # Don't test blocks that are not doctest directive blocks - e.g. all the 79 | # code in alternitives.rst 80 | doctest_test_doctest_blocks = "" 81 | 82 | # Add any paths that contain templates here, relative to this directory. 83 | templates_path = ['_templates'] 84 | 85 | # The suffix of source filenames. 86 | source_suffix = '.rst' 87 | 88 | # The encoding of source files. 89 | #source_encoding = 'utf-8-sig' 90 | 91 | # The master toctree document. 92 | master_doc = 'index' 93 | 94 | 95 | base_dir = os.path.join(os.path.dirname(__file__), os.pardir) 96 | about = {} 97 | with open(os.path.join(base_dir, "phe", "__about__.py")) as f: 98 | exec(f.read(), about) 99 | 100 | # General information about the project. 101 | project = 'python-paillier' 102 | copyright = about['__copyright__'] 103 | 104 | # The version info for the project you're documenting, acts as replacement for 105 | # |version| and |release|, also used in various other places throughout the 106 | # built documents. 107 | # 108 | # The full version, including alpha/beta/rc tags. 109 | version = about['__version__'] 110 | # The short X.Y version. 111 | release = pkg_resources.parse_version(version).base_version 112 | 113 | # The language for content autogenerated by Sphinx. Refer to documentation 114 | # for a list of supported languages. 115 | #language = None 116 | 117 | # There are two options for replacing |today|: either, you set today to some 118 | # non-false value, then it is used: 119 | #today = '' 120 | # Else, today_fmt is used as the format for a strftime call. 121 | #today_fmt = '%B %d, %Y' 122 | 123 | # List of patterns, relative to source directory, that match files and 124 | # directories to ignore when looking for source files. 125 | exclude_patterns = ['_build'] 126 | 127 | # The reST default role (used for this markup: `text`) to use for all 128 | # documents. 129 | #default_role = None 130 | 131 | # If true, '()' will be appended to :func: etc. cross-reference text. 132 | #add_function_parentheses = True 133 | 134 | # If true, the current module name will be prepended to all description 135 | # unit titles (such as .. function::). 136 | #add_module_names = True 137 | 138 | # If true, sectionauthor and moduleauthor directives will be shown in the 139 | # output. They are ignored by default. 140 | #show_authors = False 141 | 142 | # The name of the Pygments (syntax highlighting) style to use. 143 | pygments_style = 'sphinx' 144 | 145 | # A list of ignored prefixes for module index sorting. 146 | #modindex_common_prefix = [] 147 | 148 | # If true, keep warnings as "system message" paragraphs in the built documents. 149 | #keep_warnings = False 150 | 151 | 152 | # -- Options for HTML output ---------------------------------------------- 153 | 154 | # Theme options are theme-specific and customize the look and feel of a theme 155 | # further. For a list of options available for each theme, see the 156 | # documentation. 157 | html_theme_options = { 158 | } 159 | 160 | # Add any paths that contain custom themes here, relative to this directory. 161 | html_theme_path = ['.'] 162 | 163 | # The name for this set of Sphinx documents. If None, it defaults to 164 | # " v documentation". 165 | #html_title = None 166 | 167 | # A shorter title for the navigation bar. Default is the same as html_title. 168 | html_short_title = "phe" 169 | 170 | # The name of an image file (relative to this directory) to place at the top 171 | # of the sidebar. 172 | html_logo = '_static/Magic-Logo.svg' 173 | 174 | # The name of an image file (within the static path) to use as favicon of the 175 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 176 | # pixels large. 177 | #html_favicon = None 178 | 179 | # Add any paths that contain custom static files (such as style sheets) here, 180 | # relative to this directory. They are copied after the builtin static files, 181 | # so a file named "default.css" will overwrite the builtin "default.css". 182 | html_static_path = ['_static'] 183 | 184 | # Add any extra paths that contain custom files (such as robots.txt or 185 | # .htaccess) here, relative to this directory. These files are copied 186 | # directly to the root of the documentation. 187 | #html_extra_path = [] 188 | 189 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 190 | # using the given strftime format. 191 | #html_last_updated_fmt = '%b %d, %Y' 192 | 193 | # If true, SmartyPants will be used to convert quotes and dashes to 194 | # typographically correct entities. 195 | #html_use_smartypants = True 196 | 197 | # Custom sidebar templates, maps document names to template names. 198 | #html_sidebars = {} 199 | 200 | # Additional templates that should be rendered to pages, maps page names to 201 | # template names. 202 | #html_additional_pages = {} 203 | 204 | # If false, no module index is generated. 205 | #html_domain_indices = True 206 | 207 | # If false, no index is generated. 208 | #html_use_index = True 209 | 210 | # If true, the index is split into individual pages for each letter. 211 | #html_split_index = False 212 | 213 | # If true, links to the reST sources are added to the pages. 214 | html_show_sourcelink = True 215 | 216 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 217 | #html_show_sphinx = True 218 | 219 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 220 | html_show_copyright = True 221 | 222 | # If true, an OpenSearch description file will be output, and all pages will 223 | # contain a tag referring to it. The value of this option must be the 224 | # base URL from which the finished HTML is served. 225 | #html_use_opensearch = '' 226 | 227 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 228 | #html_file_suffix = None 229 | 230 | # Output file base name for HTML help builder. 231 | htmlhelp_basename = 'phedoc' 232 | 233 | 234 | # -- Options for LaTeX output --------------------------------------------- 235 | 236 | latex_elements = { 237 | # The paper size ('letterpaper' or 'a4paper'). 238 | #'papersize': 'letterpaper', 239 | 240 | # The font size ('10pt', '11pt' or '12pt'). 241 | #'pointsize': '10pt', 242 | 243 | # Additional stuff for the LaTeX preamble. 244 | #'preamble': '', 245 | } 246 | 247 | # Grouping the document tree into LaTeX files. List of tuples 248 | # (source start file, target name, title, 249 | # author, documentclass [howto, manual, or own class]). 250 | latex_documents = [ 251 | ('index', 'python-paillier.tex', u'Python Paillier Documentation', 252 | u"CSIRO's Data61", 'manual'), 253 | ] 254 | 255 | # The name of an image file (relative to this directory) to place at the top of 256 | # the title page. 257 | #latex_logo = None 258 | 259 | # For "manual" documents, if this is true, then toplevel headings are parts, 260 | # not chapters. 261 | #latex_use_parts = False 262 | 263 | # If true, show page references after internal links. 264 | #latex_show_pagerefs = False 265 | 266 | # If true, show URL addresses after external links. 267 | #latex_show_urls = False 268 | 269 | # Documents to append as an appendix to all manuals. 270 | #latex_appendices = [] 271 | 272 | # If false, no module index is generated. 273 | #latex_domain_indices = True 274 | 275 | 276 | # -- Options for manual page output --------------------------------------- 277 | 278 | # One entry per manual page. List of tuples 279 | # (source start file, name, description, authors, manual section). 280 | man_pages = [ 281 | ('index', 'python-paillier', u'pyphe Documentation', 282 | [u"CSIRO's DATA61"], 1) 283 | ] 284 | 285 | # If true, show URL addresses after external links. 286 | #man_show_urls = False 287 | 288 | 289 | # -- Options for Texinfo output ------------------------------------------- 290 | 291 | # Grouping the document tree into Texinfo files. List of tuples 292 | # (source start file, target name, title, author, 293 | # dir menu entry, description, category) 294 | texinfo_documents = [ 295 | ('index', 'pyphe', u'pyphe Documentation', 296 | u"CSIRO's Data61", 'pyphe', 297 | 'Paillier encryption library for partially homomorphic encryption.', 298 | 'Miscellaneous'), 299 | ] 300 | 301 | # Documents to append as an appendix to all manuals. 302 | #texinfo_appendices = [] 303 | 304 | # If false, no module index is generated. 305 | #texinfo_domain_indices = True 306 | 307 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 308 | #texinfo_show_urls = 'footnote' 309 | 310 | # If true, do not generate a @detailmenu in the "Top" node's menu. 311 | #texinfo_no_detailmenu = False 312 | 313 | 314 | # Napoleon settings 315 | napoleon_include_private_with_doc = True 316 | napoleon_include_special_with_doc = True 317 | 318 | # napoleon_numpy_docstring = True 319 | # napoleon_google_docstring = True 320 | # napoleon_use_admonition_for_examples = False 321 | # napoleon_use_admonition_for_notes = False 322 | # napoleon_use_admonition_for_references = False 323 | # napoleon_use_ivar = False 324 | # napoleon_use_param = True 325 | # napoleon_use_rtype = True 326 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | 2 | python-paillier 3 | =============== 4 | 5 | A Python 3 library for **P**\ artially **H**\ omomorphic **E**\ ncryption using the 6 | `Paillier crypto system `_. 7 | 8 | The homomorphic properties of the Paillier crypto system are: 9 | 10 | - Encrypted numbers can be multiplied by a non encrypted scalar. 11 | - Encrypted numbers can be added together. 12 | - Encrypted numbers can be added to non encrypted scalars. 13 | 14 | 15 | .. toctree:: 16 | :maxdepth: 2 17 | 18 | installation 19 | usage 20 | serialisation 21 | caveats 22 | cli 23 | phe 24 | compatibility 25 | 26 | 27 | 28 | Example 29 | ------- 30 | 31 | .. doctest:: 32 | 33 | >>> from phe import paillier 34 | >>> public_key, private_key = paillier.generate_paillier_keypair() 35 | >>> secret_number_list = [3.141592653, 300, -4.6e-12] 36 | >>> encrypted_number_list = [public_key.encrypt(x) for x in secret_number_list] 37 | >>> [private_key.decrypt(x) for x in encrypted_number_list] 38 | [3.141592653, 300, -4.6e-12] 39 | 40 | See :ref:`usage` for more extensive examples taking advantage of the homomorphic 41 | properties of the *paillier* cryptosystem. 42 | 43 | 44 | 45 | .. admonition:: Documentation generated 46 | 47 | |today| 48 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. _installation: 2 | 3 | Installation 4 | ============ 5 | 6 | The python-paillier library requires a minimum Python version of 7 | at least 3.3. 8 | 9 | .. note:: 10 | 11 | A big integer math library is used to increase the speed of 12 | python-paillier and to access a cryptographic random source. 13 | All big integer math has been implemented with 14 | `GMP `_ - the GNU Multiple Precision 15 | arithmetic library. This dependency should be installed for 16 | your operating system. 17 | 18 | On Ubuntu systems the following packages should be installed:: 19 | 20 | libmpc-dev libmpfr-dev libmpfr4 libgmp3-dev 21 | 22 | 23 | Using pip 24 | --------- 25 | 26 | Using pip at the command line, to install the base library from `PyPi `_:: 27 | 28 | $ pip install phe 29 | 30 | 31 | To also install the :ref:`command line utility `, introduced at version 1.2:: 32 | 33 | pip install "phe[cli]>1.2" 34 | 35 | Examples have been written which have their own additional requirements such as sklearn. 36 | To also install those:: 37 | 38 | pip install "phe[cli,examples]" 39 | 40 | 41 | Or, if you have `virtualenvwrapper `_ 42 | installed:: 43 | 44 | $ mkvirtualenv phe 45 | $ pip install -e ".[CLI]" 46 | 47 | 48 | Manual installation 49 | ------------------- 50 | 51 | To install from the source package, first install any of the (optional) 52 | dependencies (eg pycrypto, gmpy2). A list can be found in 53 | ``requirements.txt``. 54 | 55 | Then install as normal:: 56 | 57 | $ python setup.py install 58 | 59 | 60 | Docker 61 | ------ 62 | 63 | A minimal Docker file based on alpine linux:: 64 | 65 | FROM python:3-alpine 66 | RUN ["apk", "add", "--no-cache", \ 67 | "g++", \ 68 | "musl-dev", \ 69 | "gmp-dev", \ 70 | "mpfr-dev", \ 71 | "mpc1-dev" \ 72 | ] 73 | RUN pip install phe 74 | 75 | -------------------------------------------------------------------------------- /docs/phe.rst: -------------------------------------------------------------------------------- 1 | API Documentation 2 | ================= 3 | 4 | 5 | Paillier 6 | -------- 7 | 8 | 9 | .. automodule:: phe.paillier 10 | :members: 11 | :undoc-members: 12 | :show-inheritance: 13 | 14 | Encoding 15 | -------- 16 | 17 | .. automodule:: phe.encoding 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: 21 | 22 | 23 | 24 | Utilities 25 | --------- 26 | 27 | 28 | .. automodule:: phe.util 29 | :members: 30 | :undoc-members: 31 | :show-inheritance: 32 | 33 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx>=1.6.2 2 | sphinxcontrib-napoleon>=0.6.1 3 | -------------------------------------------------------------------------------- /docs/serialisation.rst: -------------------------------------------------------------------------------- 1 | .. _serialisation: 2 | 3 | ============= 4 | Serialisation 5 | ============= 6 | 7 | This library does not enforce any particular serialisation scheme. 8 | 9 | Every :class:`~phe.paillier.EncryptedNumber` 10 | instance has a :attr:`~phe.paillier.EncryptedNumber.public_key` attribute, and serialising each 11 | :class:`~phe.paillier.EncryptedNumber` independently would be heinously inefficient when sending 12 | a large list of instances. It is up to you to serialise in a way that is efficient for your use 13 | case. 14 | 15 | .. _basic-serialisation: 16 | 17 | Basic JSON Serialisation 18 | ------------------------ 19 | 20 | This basic serialisation method is an example of serialising a vector of encrypted numbers. 21 | Note that if you are only using the python-paillier library **g** will always be **n + 1**, 22 | so these is no need to serialise it as part of the public key. 23 | 24 | To send a list of values encrypted against one public key, the following is one way to serialise:: 25 | 26 | >>> import json 27 | >>> enc_with_one_pub_key = {} 28 | >>> enc_with_one_pub_key['public_key'] = {'n': public_key.n} 29 | >>> enc_with_one_pub_key['values'] = [ 30 | ... (str(x.ciphertext()), x.exponent) for x in encrypted_number_list 31 | ... ] 32 | >>> serialised = json.dumps(enc_with_one_pub_key) 33 | 34 | Deserialisation of the above scheme might look as follows:: 35 | 36 | >>> received_dict = json.loads(serialised) 37 | >>> pk = received_dict['public_key'] 38 | >>> public_key_rec = paillier.PaillierPublicKey(n=int(pk['n'])) 39 | >>> enc_nums_rec = [ 40 | ... paillier.EncryptedNumber(public_key_rec, int(x[0]), int(x[1])) 41 | ... for x in received_dict['values'] 42 | ... ] 43 | 44 | If both parties already know `public_key`, then you might instead send a hash of the public key. 45 | 46 | 47 | .. _json-serialisation: 48 | 49 | JWK Serialisation 50 | ----------------- 51 | 52 | This serialisation scheme is used by the :ref:`cli`, and is based on the 53 | `JSON Web Key (JWK) `_ format. This 54 | serialisation scheme should be used to increase compatibility between libraries. 55 | 56 | .. _b64: 57 | 58 | All cryptographic integers are represented as Base64UrlEncoded numbers. 59 | Note the existence of :func:`~phe.util.base64_to_int` and :func:`~phe.util.int_to_base64`. 60 | 61 | "kty" (Key Type) Parameter 62 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 63 | 64 | We define the family for all Paillier keys as "DAJ" for Damgard Jurik. 65 | 66 | 67 | "alg" (Algorithm) Parameter 68 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 69 | 70 | We identify the algorithm for our Paillier keys as: "PAI-GN1" 71 | 72 | "key_ops" (Key Operations) Parameter 73 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 74 | 75 | Values will be "encrypt" and "decrypt" for public and private keys respectively. 76 | We decided not to add homomorphic properties to the key operations. 77 | 78 | "kid" (Key Identifier) 79 | ~~~~~~~~~~~~~~~~~~~~~~ 80 | 81 | The **kid** may be set to any ascii string. Useful for storing key names, 82 | generation tools, times etc. 83 | 84 | Public Key 85 | ~~~~~~~~~~ 86 | 87 | In addition to the "kty", "kid", "key_ops" and "alg" attributes, a public key will have: 88 | 89 | - **n** The public key's modulus - :ref:`Base64 url encoded ` 90 | 91 | 92 | Example of a 256 bit public key:: 93 | 94 | 95 | python -m phe.command_line genpkey --keysize 256 - | python -m phe.command_line extract - - 96 | { 97 | "kty": "DAJ", 98 | "kid": "Example Paillier public key", 99 | "key_ops": [ "encrypt" ], 100 | "n": "m0lOEwDHVA_VieL2k3BKMjf_HIgagfhNIZy1YhgZF5M", 101 | "alg": "PAI-GN1" 102 | } 103 | 104 | 105 | Private Key 106 | ~~~~~~~~~~~ 107 | 108 | .. note:: 109 | 110 | The serialised private key includes the public key. 111 | 112 | In addition to the "kty", "kid", "key_ops" and "alg" attributes, a private key will have: 113 | 114 | - **mu** and **lambda** - The private key's secrets. See Paillier's paper for details. 115 | - **pub** - The Public Key serialised as described above. 116 | 117 | 118 | Example of a 256 bit private key:: 119 | 120 | 121 | python -m phe.command_line genpkey --keysize 256 - 122 | { 123 | "mu": "Dzq1_tz2qDX_-S4shia9Rw34Z9ix9b-fhPi3In76NaI", 124 | "kty": "DAJ", 125 | "key_ops": [ "decrypt" ], 126 | "kid": "Paillier private key generated by pheutil on 2016-05-24 14:18:25", 127 | "lambda": "haFTvA70KcI5XXReJUlQWRQdYHxaUS8baGQGug9dewA", 128 | "pub": { 129 | "alg": "PAI-GN1", 130 | "n": "haFTvA70KcI5XXReJUlQWoZus12aSJJ5EXAvu93xR7k", 131 | "kty": "DAJ", 132 | "key_ops": [ "encrypt" ], 133 | "kid": "Paillier public key generated by pheutil on 2016-05-24 14:18:25" 134 | } 135 | } 136 | 137 | 138 | 139 | .. warning:: 140 | 141 | "kty" and "alg" values should be registered in the 142 | `IANA "JSON Web Key Types" registry `_ 143 | established by JWA. We have not registered **DAJ** or **PAI-GN1** - however we intend to begin that 144 | conversation. 145 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | .. _usage: 2 | 3 | Usage 4 | ===== 5 | 6 | There are two roles that use this library. In the first, you control the private keys. In the 7 | second, you don't. This guide shows you how to play either role. 8 | 9 | In either case, you of course begin by importing the library:: 10 | 11 | from phe import paillier 12 | 13 | 14 | Role #1 15 | ------- 16 | 17 | This party holds the private keys and typically will generate the keys and do the decryption. 18 | 19 | Key generation 20 | ^^^^^^^^^^^^^^ 21 | 22 | First, you're going to have to generate a public and private key pair:: 23 | 24 | >>> public_key, private_key = paillier.generate_paillier_keypair() 25 | 26 | If you're going to have lots of private keys lying around, then perhaps you should invest in 27 | a keyring on which to store your :class:`~phe.paillier.PaillierPrivateKey` instances:: 28 | 29 | >>> keyring = paillier.PaillierPrivateKeyring() 30 | >>> keyring.add(private_key) 31 | >>> public_key1, private_key1 = paillier.generate_paillier_keypair(keyring) 32 | >>> public_key2, private_key2 = paillier.generate_paillier_keypair(keyring) 33 | 34 | In any event, you can then start encrypting numbers:: 35 | 36 | >>> secret_number_list = [3.141592653, 300, -4.6e-12] 37 | >>> encrypted_number_list = [public_key.encrypt(x) for x in secret_number_list] 38 | 39 | Presumably, you would now share the ciphertext with whoever is playing Role 2 40 | (see :ref:`serialisation` and :ref:`compatibility`). 41 | 42 | 43 | Decryption 44 | ^^^^^^^^^^ 45 | 46 | To decrypt an :class:`~phe.paillier.EncryptedNumber`, use the relevant 47 | :class:`~phe.paillier.PaillierPrivateKey`:: 48 | 49 | >>> [private_key.decrypt(x) for x in encrypted_number_list] 50 | [3.141592653, 300, -4.6e-12] 51 | 52 | If you have multiple key pairs stored in a :class:`~phe.paillier.PaillierPrivateKeyring`, 53 | then you don't need to manually find the relevant :class:`~phe.paillier.PaillierPrivateKey`:: 54 | 55 | >>> [keyring.decrypt(x) for x in encrypted_number_list] 56 | [3.141592653, 300, -4.6e-12] 57 | 58 | 59 | Role #2 60 | ------- 61 | 62 | This party does not have access to the private keys, and typically performs operations on 63 | supplied encrypted data with their own, unencrypted data. 64 | 65 | Once this party has received some :class:`~phe.paillier.EncryptedNumber` instances (e.g. see 66 | :ref:`serialisation`), it can perform basic mathematical operations supported by the Paillier 67 | encryption: 68 | 69 | 1. Addition of an :class:`~phe.paillier.EncryptedNumber` to a scalar 70 | 2. Addition of two :class:`~phe.paillier.EncryptedNumber` instances 71 | 3. Multiplication of an :class:`~phe.paillier.EncryptedNumber` by a scalar 72 | 73 | :: 74 | 75 | >>> a, b, c = encrypted_number_list 76 | >>> a 77 | 78 | 79 | >>> a_plus_5 = a + 5 80 | >>> a_plus_b = a + b 81 | >>> a_times_3_5 = a * 3.5 82 | 83 | as well as some simple extensions:: 84 | 85 | >>> a_minus_1_3 = a - 1 # = a + (-1) 86 | >>> a_div_minus_3_1 = a / -3.1 # = a * (-1 / 3.1) 87 | >>> a_minus_b = a - b # = a + (b * -1) 88 | 89 | Numpy operations that rely only on these operations are allowed:: 90 | 91 | >>> import numpy as np 92 | >>> enc_mean = np.mean(encrypted_number_list) 93 | >>> enc_dot = np.dot(encrypted_number_list, [2, -400.1, 5318008]) 94 | 95 | Operations that aren't supported by Paillier's *partially* homomorphic scheme raise an error:: 96 | 97 | >>> a * b 98 | NotImplementedError: Good luck with that... 99 | 100 | >>> 1 / a 101 | TypeError: unsupported operand type(s) for /: 'int' and 'EncryptedNumber' 102 | 103 | 104 | Once the necessary computations have been done, this party would send the resulting 105 | :class:`~phe.paillier.EncryptedNumber` instances back to the holder of the private keys for 106 | decryption. 107 | 108 | In some cases it might be possible to boost performance by reducing the precision of floating point numbers:: 109 | 110 | >>> a_times_3_5_lp = a * paillier.EncodedNumber.encode(a.public_key, 3.5, 1e-2) 111 | 112 | 113 | -------------------------------------------------------------------------------- /examples/alternative_base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.4 2 | import math 3 | 4 | import phe.encoding 5 | from phe import paillier 6 | 7 | 8 | class ExampleEncodedNumber(phe.encoding.EncodedNumber): 9 | BASE = 64 10 | LOG2_BASE = math.log(BASE, 2) 11 | 12 | 13 | print("Generating paillier keypair") 14 | public_key, private_key = paillier.generate_paillier_keypair() 15 | 16 | 17 | def encode_and_encrypt_example(): 18 | print("Encoding a large positive number. With a BASE {} encoding scheme".format(ExampleEncodedNumber.BASE)) 19 | encoded = ExampleEncodedNumber.encode(public_key, 2.1 ** 20) 20 | print("Checking that decoding gives the same number...") 21 | assert 2.1 ** 20 == encoded.decode() 22 | 23 | print("Encrypting the encoded number") 24 | encrypted = public_key.encrypt(encoded) 25 | 26 | print("Decrypting...") 27 | decrypted_but_encoded = \ 28 | private_key.decrypt_encoded(encrypted, ExampleEncodedNumber) 29 | 30 | print("Checking the decrypted number is what we started with") 31 | assert abs(2.1 ** 20 - decrypted_but_encoded.decode()) < 1e-12 32 | 33 | 34 | def math_example(): 35 | print("Encoding two large positive numbers. BASE={}".format(ExampleEncodedNumber.BASE)) 36 | 37 | a = 102545 + (64 ** 8) 38 | b = 123 + (8 ** 20) 39 | 40 | encoded_a = ExampleEncodedNumber.encode(public_key, a) 41 | encoded_b = ExampleEncodedNumber.encode(public_key, b) 42 | 43 | print("Checking that decoding gives the same number...") 44 | assert a == encoded_a.decode() 45 | assert b == encoded_b.decode() 46 | 47 | print("Encrypting the encoded numbers") 48 | encrypted_a = public_key.encrypt(encoded_a) 49 | encrypted_b = public_key.encrypt(encoded_b) 50 | 51 | print("Adding the encrypted numbers") 52 | encrypted_c = encrypted_a + encrypted_b 53 | 54 | print("Decrypting the one encrypted sum") 55 | decrypted_but_encoded = \ 56 | private_key.decrypt_encoded(encrypted_c, ExampleEncodedNumber) 57 | 58 | print("Checking the decrypted number is what we started with") 59 | 60 | print("Decrypted: {}".format(decrypted_but_encoded.decode())) 61 | assert abs((a + b) - decrypted_but_encoded.decode()) < 1e-15 62 | 63 | 64 | if __name__ == "__main__": 65 | encode_and_encrypt_example() 66 | 67 | math_example() 68 | -------------------------------------------------------------------------------- /examples/benchmarks.py: -------------------------------------------------------------------------------- 1 | """ 2 | Benchmark key generation, encryption and decryption. 3 | 4 | """ 5 | 6 | import random 7 | import resource 8 | import time 9 | import phe.paillier as paillier 10 | 11 | 12 | def bench_encrypt(pubkey, nums): 13 | for num in nums: 14 | pubkey.encrypt(num) 15 | 16 | 17 | def bench_decrypt(prikey, nums): 18 | for num in nums: 19 | prikey.decrypt(num) 20 | 21 | 22 | def bench_add(nums1, nums2): 23 | for num1, num2 in zip(nums1, nums2): 24 | num1 + num2 25 | 26 | 27 | def bench_mul(nums1, nums2): 28 | for num1, num2 in zip(nums1, nums2): 29 | num1 * num2 30 | 31 | 32 | def time_method(method, *args): 33 | start = time.time() 34 | method(*args) 35 | return time.time() - start 36 | 37 | 38 | def bench_time(test_size, key_size=128): 39 | 40 | print('Paillier Benchmarks with key size of {} bits'.format(key_size)) 41 | pubkey, prikey = paillier.generate_paillier_keypair(n_length=key_size) 42 | nums1 = [random.random() for _ in range(test_size)] 43 | nums2 = [random.random() for _ in range(test_size)] 44 | nums1_enc = [pubkey.encrypt(n) for n in nums1] 45 | nums2_enc = [pubkey.encrypt(n) for n in nums2] 46 | ones = [1.0 for _ in range(test_size)] 47 | 48 | times = [ 49 | time_method(bench_encrypt, pubkey, nums1), 50 | time_method(bench_decrypt, prikey, nums1_enc), 51 | time_method(bench_add, nums1_enc, nums2), 52 | time_method(bench_add, nums1_enc, nums2_enc), 53 | time_method(bench_add, nums1_enc, ones), 54 | time_method(bench_mul, nums1_enc, nums2) 55 | ] 56 | times = [t / test_size for t in times] 57 | ops = [int(1.0 / t) for t in times] 58 | 59 | print( 60 | 'operation: time in seconds (# operations per second)\n' 61 | 'encrypt: {:.6f} s ({} ops/s)\n' 62 | 'decrypt: {:.6f} s ({} ops/s)\n' 63 | 'add unencrypted and encrypted: {:.6f} s ({} ops/s)\n' 64 | 'add encrypted and encrypted: {:.6f} s ({} ops/s)\n' 65 | 'add encrypted and 1: {:.6f} s ({} ops/s)\n' 66 | 'multiply encrypted and unencrypted: {:.6f} s ({} ops/s)'.format( 67 | times[0], ops[0], times[1], ops[1], times[2], ops[2], 68 | times[3], ops[3], times[4], ops[4], times[5], ops[5] 69 | ) 70 | ) 71 | return times 72 | 73 | 74 | def bench_mem(test_size): 75 | r_init = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss 76 | pubkey, prikey = paillier.generate_paillier_keypair() 77 | nums = [] 78 | for i in range(test_size): 79 | if not i % 10000: 80 | # This is probably KB (i.e. 1000 bytes) when run on linux 81 | r = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss - r_init 82 | print('Memory usage for {:,} encrypted numbers = {:,} ({:.4f} per ' 83 | 'number)'.format(i, r, i and r / i)) 84 | nums.append(pubkey.encrypt(random.random())) 85 | 86 | # bench_mem(1000000) # NOTE: this will take a long time 87 | 88 | 89 | times = [] 90 | key_sizes = [128, 256, 512, 1024, 2048, 4096, 8192] 91 | for key_size in key_sizes: 92 | times.append(bench_time(1000, key_size)) 93 | -------------------------------------------------------------------------------- /examples/federated_learning_with_encryption.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example involves learning using sensitive medical data from multiple hospitals 3 | to predict diabetes progression in patients. The data is a standard dataset from 4 | sklearn[1]. 5 | 6 | Recorded variables are: 7 | - age, 8 | - gender, 9 | - body mass index, 10 | - average blood pressure, 11 | - and six blood serum measurements. 12 | 13 | The target variable is a quantitative measure of the disease progression. 14 | Since this measure is continuous, we solve the problem using linear regression. 15 | 16 | The patients' data is split between 3 hospitals, all sharing the same features 17 | but different entities. We refer to this scenario as horizontally partitioned. 18 | 19 | The objective is to make use of the whole (virtual) training set to improve 20 | upon the model that can be trained locally at each hospital. 21 | 22 | 50 patients will be kept as a test set and not used for training. 23 | 24 | An additional agent is the 'server' who facilitates the information exchange 25 | among the hospitals under the following privacy constraints: 26 | 27 | 1) The individual patient's record at each hospital cannot leave the premises, 28 | not even in encrypted form. 29 | 2) Information derived (read: gradients) from any hospital's dataset 30 | cannot be shared, unless it is first encrypted. 31 | 3) None of the parties (hospitals AND server) should be able to infer WHERE 32 | (in which hospital) a patient in the training set has been treated. 33 | 34 | Note that we do not protect from inferring IF a particular patient's data 35 | has been used during learning. Differential privacy could be used on top of 36 | our protocol for addressing the problem. For simplicity, we do not discuss 37 | it in this example. 38 | 39 | In this example linear regression is solved by gradient descent. The server 40 | creates a paillier public/private keypair and does not share the private key. 41 | The hospital clients are given the public key. The protocol works as follows. 42 | Until convergence: hospital 1 computes its gradient, encrypts it and sends it 43 | to hospital 2; hospital 2 computes its gradient, encrypts and sums it to 44 | hospital 1's; hospital 3 does the same and passes the overall sum to the 45 | server. The server obtains the gradient of the whole (virtual) training set; 46 | decrypts it and sends the gradient back - in the clear - to every client. 47 | The clients then update their respective local models. 48 | 49 | From the learning viewpoint, notice that we are NOT assuming that each 50 | hospital sees an unbiased sample from the same patients' distribution: 51 | hospitals could be geographically very distant or serve a diverse population. 52 | We simulate this condition by sampling patients NOT uniformly at random, 53 | but in a biased fashion. 54 | The test set is instead an unbiased sample from the overall distribution. 55 | 56 | From the security viewpoint, we consider all parties to be "honest but curious". 57 | Even by seeing the aggregated gradient in the clear, no participant can pinpoint 58 | where patients' data originated. This is true if this RING protocol is run by 59 | at least 3 clients, which prevents reconstruction of each others' gradients 60 | by simple difference. 61 | 62 | This example was inspired by Google's work on secure protocols for federated 63 | learning[2]. 64 | 65 | [1]: http://scikit-learn.org/stable/datasets/index.html#diabetes-dataset 66 | [2]: https://research.googleblog.com/2017/04/federated-learning-collaborative.html 67 | 68 | Dependencies: numpy, sklearn 69 | """ 70 | 71 | import numpy as np 72 | from sklearn.datasets import load_diabetes 73 | 74 | import phe as paillier 75 | 76 | seed = 43 77 | np.random.seed(seed) 78 | 79 | 80 | def get_data(n_clients): 81 | """ 82 | Import the dataset via sklearn, shuffle and split train/test. 83 | Return training, target lists for `n_clients` and a holdout test set 84 | """ 85 | print("Loading data") 86 | diabetes = load_diabetes() 87 | y = diabetes.target 88 | X = diabetes.data 89 | # Add constant to emulate intercept 90 | X = np.c_[X, np.ones(X.shape[0])] 91 | 92 | # The features are already preprocessed 93 | # Shuffle 94 | perm = np.random.permutation(X.shape[0]) 95 | X, y = X[perm, :], y[perm] 96 | 97 | # Select test at random 98 | test_size = 50 99 | test_idx = np.random.choice(X.shape[0], size=test_size, replace=False) 100 | train_idx = np.ones(X.shape[0], dtype=bool) 101 | train_idx[test_idx] = False 102 | X_test, y_test = X[test_idx, :], y[test_idx] 103 | X_train, y_train = X[train_idx, :], y[train_idx] 104 | 105 | # Split train among multiple clients. 106 | # The selection is not at random. We simulate the fact that each client 107 | # sees a potentially very different sample of patients. 108 | X, y = [], [] 109 | step = int(X_train.shape[0] / n_clients) 110 | for c in range(n_clients): 111 | X.append(X_train[step * c: step * (c + 1), :]) 112 | y.append(y_train[step * c: step * (c + 1)]) 113 | 114 | return X, y, X_test, y_test 115 | 116 | 117 | def mean_square_error(y_pred, y): 118 | """ 1/m * \sum_{i=1..m} (y_pred_i - y_i)^2 """ 119 | return np.mean((y - y_pred) ** 2) 120 | 121 | 122 | def encrypt_vector(public_key, x): 123 | return [public_key.encrypt(i) for i in x] 124 | 125 | 126 | def decrypt_vector(private_key, x): 127 | return np.array([private_key.decrypt(i) for i in x]) 128 | 129 | 130 | def sum_encrypted_vectors(x, y): 131 | if len(x) != len(y): 132 | raise ValueError('Encrypted vectors must have the same size') 133 | return [x[i] + y[i] for i in range(len(x))] 134 | 135 | 136 | class Server: 137 | """Private key holder. Decrypts the average gradient""" 138 | 139 | def __init__(self, key_length): 140 | keypair = paillier.generate_paillier_keypair(n_length=key_length) 141 | self.pubkey, self.privkey = keypair 142 | 143 | def decrypt_aggregate(self, input_model, n_clients): 144 | return decrypt_vector(self.privkey, input_model) / n_clients 145 | 146 | 147 | class Client: 148 | """Runs linear regression with local data or by gradient steps, 149 | where gradient can be passed in. 150 | 151 | Using public key can encrypt locally computed gradients. 152 | """ 153 | 154 | def __init__(self, name, X, y, pubkey): 155 | self.name = name 156 | self.pubkey = pubkey 157 | self.X, self.y = X, y 158 | self.weights = np.zeros(X.shape[1]) 159 | 160 | def fit(self, n_iter, eta=0.01): 161 | """Linear regression for n_iter""" 162 | for _ in range(n_iter): 163 | gradient = self.compute_gradient() 164 | self.gradient_step(gradient, eta) 165 | 166 | def gradient_step(self, gradient, eta=0.01): 167 | """Update the model with the given gradient""" 168 | self.weights -= eta * gradient 169 | 170 | def compute_gradient(self): 171 | """Compute the gradient of the current model using the training set 172 | """ 173 | delta = self.predict(self.X) - self.y 174 | return delta.dot(self.X) / len(self.X) 175 | 176 | def predict(self, X): 177 | """Score test data""" 178 | return X.dot(self.weights) 179 | 180 | def encrypted_gradient(self, sum_to=None): 181 | """Compute and encrypt gradient. 182 | 183 | When `sum_to` is given, sum the encrypted gradient to it, assumed 184 | to be another vector of the same size 185 | """ 186 | gradient = self.compute_gradient() 187 | encrypted_gradient = encrypt_vector(self.pubkey, gradient) 188 | 189 | if sum_to is not None: 190 | return sum_encrypted_vectors(sum_to, encrypted_gradient) 191 | else: 192 | return encrypted_gradient 193 | 194 | 195 | def federated_learning(X, y, X_test, y_test, config): 196 | n_clients = config['n_clients'] 197 | n_iter = config['n_iter'] 198 | names = ['Hospital {}'.format(i) for i in range(1, n_clients + 1)] 199 | 200 | # Instantiate the server and generate private and public keys 201 | # NOTE: using smaller keys sizes wouldn't be cryptographically safe 202 | server = Server(key_length=config['key_length']) 203 | 204 | # Instantiate the clients. 205 | # Each client gets the public key at creation and its own local dataset 206 | clients = [] 207 | for i in range(n_clients): 208 | clients.append(Client(names[i], X[i], y[i], server.pubkey)) 209 | 210 | # The federated learning with gradient descent 211 | print('Running distributed gradient aggregation for {:d} iterations' 212 | .format(n_iter)) 213 | for i in range(n_iter): 214 | 215 | # Compute gradients, encrypt and aggregate 216 | encrypt_aggr = clients[0].encrypted_gradient(sum_to=None) 217 | for c in clients[1:]: 218 | encrypt_aggr = c.encrypted_gradient(sum_to=encrypt_aggr) 219 | 220 | # Send aggregate to server and decrypt it 221 | aggr = server.decrypt_aggregate(encrypt_aggr, n_clients) 222 | 223 | # Take gradient steps 224 | for c in clients: 225 | c.gradient_step(aggr, config['eta']) 226 | 227 | print('Error (MSE) that each client gets after running the protocol:') 228 | for c in clients: 229 | y_pred = c.predict(X_test) 230 | mse = mean_square_error(y_pred, y_test) 231 | print('{:s}:\t{:.2f}'.format(c.name, mse)) 232 | 233 | 234 | def local_learning(X, y, X_test, y_test, config): 235 | n_clients = config['n_clients'] 236 | names = ['Hospital {}'.format(i) for i in range(1, n_clients + 1)] 237 | 238 | # Instantiate the clients. 239 | # Each client gets the public key at creation and its own local dataset 240 | clients = [] 241 | for i in range(n_clients): 242 | clients.append(Client(names[i], X[i], y[i], None)) 243 | 244 | # Each client trains a linear regressor on its own data 245 | print('Error (MSE) that each client gets on test set by ' 246 | 'training only on own local data:') 247 | for c in clients: 248 | c.fit(config['n_iter'], config['eta']) 249 | y_pred = c.predict(X_test) 250 | mse = mean_square_error(y_pred, y_test) 251 | print('{:s}:\t{:.2f}'.format(c.name, mse)) 252 | 253 | 254 | if __name__ == '__main__': 255 | config = { 256 | 'n_clients': 5, 257 | 'key_length': 1024, 258 | 'n_iter': 50, 259 | 'eta': 1.5, 260 | } 261 | # load data, train/test split and split training data between clients 262 | X, y, X_test, y_test = get_data(n_clients=config['n_clients']) 263 | # first each hospital learns a model on its respective dataset for comparison. 264 | local_learning(X, y, X_test, y_test, config) 265 | # and now the full glory of federated learning 266 | federated_learning(X, y, X_test, y_test, config) 267 | -------------------------------------------------------------------------------- /examples/logistic_regression_encrypted_model.py: -------------------------------------------------------------------------------- 1 | """ 2 | In this example Alice trains a spam classifier on some e-mails dataset she 3 | owns. She wants to apply it to Bob's personal e-mails, without 4 | 5 | 1) asking Bob to send his e-mails anywhere 6 | 2) leaking information about the learned model or the dataset she has learned 7 | from 8 | 3) letting Bob know which of his e-mails are spam or not. 9 | 10 | Alice trains a spam classifier with logistic regression on some data she 11 | possesses. After learning, she generates public/private key pair with a 12 | Paillier schema. The model is encrypted with the public key. The public key and 13 | the encrypted model are sent to Bob. Bob applies the encrypted model to his own 14 | data, obtaining encrypted scores for each e-mail. Bob sends them to Alice. 15 | Alice decrypts them with the private key to obtain the predictions spam vs. not 16 | spam. 17 | 18 | Example inspired by @iamtrask blog post: 19 | https://iamtrask.github.io/2017/06/05/homomorphic-surveillance/ 20 | 21 | Dependencies: numpy, sklearn 22 | """ 23 | 24 | import time 25 | import os.path 26 | from zipfile import ZipFile 27 | from urllib.request import urlopen 28 | from contextlib import contextmanager 29 | 30 | import numpy as np 31 | from sklearn.linear_model import LogisticRegression 32 | from sklearn.feature_extraction.text import CountVectorizer 33 | 34 | import phe as paillier 35 | 36 | np.random.seed(42) 37 | 38 | # Enron spam dataset hosted by https://cloudstor.aarnet.edu.au 39 | url = [ 40 | 'https://cloudstor.aarnet.edu.au/plus/index.php/s/RpHZ57z2E3BTiSQ/download', 41 | 'https://cloudstor.aarnet.edu.au/plus/index.php/s/QVD4Xk5Cz3UVYLp/download' 42 | ] 43 | 44 | 45 | def download_data(): 46 | """Download two sets of Enron1 spam/ham e-mails if they are not here 47 | We will use the first as trainset and the second as testset. 48 | Return the path prefix to us to load the data from disk.""" 49 | 50 | n_datasets = 2 51 | for d in range(1, n_datasets + 1): 52 | if not os.path.isdir('enron%d' % d): 53 | 54 | URL = url[d-1] 55 | print("Downloading %d/%d: %s" % (d, n_datasets, URL)) 56 | folderzip = 'enron%d.zip' % d 57 | 58 | with urlopen(URL) as remotedata: 59 | with open(folderzip, 'wb') as z: 60 | z.write(remotedata.read()) 61 | 62 | with ZipFile(folderzip) as z: 63 | z.extractall() 64 | os.remove(folderzip) 65 | 66 | 67 | def preprocess_data(): 68 | """ 69 | Get the Enron e-mails from disk. 70 | Represent them as bag-of-words. 71 | Shuffle and split train/test. 72 | """ 73 | 74 | print("Importing dataset from disk...") 75 | path = 'enron1/ham/' 76 | ham1 = [open(path + f, 'r', errors='replace').read().strip(r"\n") 77 | for f in os.listdir(path) if os.path.isfile(path + f)] 78 | path = 'enron1/spam/' 79 | spam1 = [open(path + f, 'r', errors='replace').read().strip(r"\n") 80 | for f in os.listdir(path) if os.path.isfile(path + f)] 81 | path = 'enron2/ham/' 82 | ham2 = [open(path + f, 'r', errors='replace').read().strip(r"\n") 83 | for f in os.listdir(path) if os.path.isfile(path + f)] 84 | path = 'enron2/spam/' 85 | spam2 = [open(path + f, 'r', errors='replace').read().strip(r"\n") 86 | for f in os.listdir(path) if os.path.isfile(path + f)] 87 | 88 | # Merge and create labels 89 | emails = ham1 + spam1 + ham2 + spam2 90 | y = np.array([-1] * len(ham1) + [1] * len(spam1) + 91 | [-1] * len(ham2) + [1] * len(spam2)) 92 | 93 | # Words count, keep only frequent words 94 | count_vect = CountVectorizer(decode_error='replace', stop_words='english', 95 | min_df=0.001) 96 | X = count_vect.fit_transform(emails) 97 | 98 | print('Vocabulary size: %d' % X.shape[1]) 99 | 100 | # Shuffle 101 | perm = np.random.permutation(X.shape[0]) 102 | X, y = X[perm, :], y[perm] 103 | 104 | # Split train and test 105 | split = 500 106 | X_train, X_test = X[-split:, :], X[:-split, :] 107 | y_train, y_test = y[-split:], y[:-split] 108 | 109 | print("Labels in trainset are {:.2f} spam : {:.2f} ham".format( 110 | np.mean(y_train == 1), np.mean(y_train == -1))) 111 | 112 | return X_train, y_train, X_test, y_test 113 | 114 | 115 | @contextmanager 116 | def timer(): 117 | """Helper for measuring runtime""" 118 | 119 | time0 = time.perf_counter() 120 | yield 121 | print('[elapsed time: %.2f s]' % (time.perf_counter() - time0)) 122 | 123 | 124 | class Alice: 125 | """ 126 | Trains a Logistic Regression model on plaintext data, 127 | encrypts the model for remote use, 128 | decrypts encrypted scores using the paillier private key. 129 | """ 130 | 131 | def __init__(self): 132 | self.model = LogisticRegression() 133 | 134 | def generate_paillier_keypair(self, n_length): 135 | self.pubkey, self.privkey = \ 136 | paillier.generate_paillier_keypair(n_length=n_length) 137 | 138 | def fit(self, X, y): 139 | self.model = self.model.fit(X, y) 140 | 141 | def predict(self, X): 142 | return self.model.predict(X) 143 | 144 | def encrypt_weights(self): 145 | coef = self.model.coef_[0, :] 146 | encrypted_weights = [self.pubkey.encrypt(coef[i]) 147 | for i in range(coef.shape[0])] 148 | encrypted_intercept = self.pubkey.encrypt(self.model.intercept_[0]) 149 | return encrypted_weights, encrypted_intercept 150 | 151 | def decrypt_scores(self, encrypted_scores): 152 | return [self.privkey.decrypt(s) for s in encrypted_scores] 153 | 154 | 155 | class Bob: 156 | """ 157 | Is given the encrypted model and the public key. 158 | 159 | Scores local plaintext data with the encrypted model, but cannot decrypt 160 | the scores without the private key held by Alice. 161 | """ 162 | 163 | def __init__(self, pubkey): 164 | self.pubkey = pubkey 165 | 166 | def set_weights(self, weights, intercept): 167 | self.weights = weights 168 | self.intercept = intercept 169 | 170 | def encrypted_score(self, x): 171 | """Compute the score of `x` by multiplying with the encrypted model, 172 | which is a vector of `paillier.EncryptedNumber`""" 173 | score = self.intercept 174 | _, idx = x.nonzero() 175 | for i in idx: 176 | score += x[0, i] * self.weights[i] 177 | return score 178 | 179 | def encrypted_evaluate(self, X): 180 | return [self.encrypted_score(X[i, :]) for i in range(X.shape[0])] 181 | 182 | 183 | if __name__ == '__main__': 184 | 185 | download_data() 186 | X, y, X_test, y_test = preprocess_data() 187 | 188 | print("Alice: Generating paillier keypair") 189 | alice = Alice() 190 | # NOTE: using smaller keys sizes wouldn't be cryptographically safe 191 | alice.generate_paillier_keypair(n_length=1024) 192 | 193 | print("Alice: Learning spam classifier") 194 | with timer() as t: 195 | alice.fit(X, y) 196 | 197 | print("Classify with model in the clear -- " 198 | "what Alice would get having Bob's data locally") 199 | with timer() as t: 200 | error = np.mean(alice.predict(X_test) != y_test) 201 | print("Error {:.3f}".format(error)) 202 | 203 | print("Alice: Encrypting classifier") 204 | with timer() as t: 205 | encrypted_weights, encrypted_intercept = alice.encrypt_weights() 206 | 207 | print("Bob: Scoring with encrypted classifier") 208 | bob = Bob(alice.pubkey) 209 | bob.set_weights(encrypted_weights, encrypted_intercept) 210 | with timer() as t: 211 | encrypted_scores = bob.encrypted_evaluate(X_test) 212 | 213 | print("Alice: Decrypting Bob's scores") 214 | with timer() as t: 215 | scores = alice.decrypt_scores(encrypted_scores) 216 | error = np.mean(np.sign(scores) != y_test) 217 | print("Error {:.3f} -- this is not known to Alice, who does not possess " 218 | "the ground truth labels".format(error)) 219 | -------------------------------------------------------------------------------- /phe/__about__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | 3 | __all__ = [ 4 | "__title__", "__summary__", "__uri__", "__version__", "__author__", 5 | "__email__", "__license__", "__copyright__", 6 | ] 7 | 8 | __title__ = "phe" 9 | __summary__ = "Partially Homomorphic Encryption library for Python" 10 | __uri__ = "https://github.com/data61/python-paillier" 11 | 12 | # We use semantic versioning - semver.org 13 | __version__ = "1.5.0" 14 | 15 | __author__ = "CSIRO's Data61" 16 | __email__ = "confidential-computing@data61.csiro.au" 17 | __license__ = "GPLv3" 18 | __copyright__ = "Copyright 2013-2019 {0}".format(__author__) 19 | -------------------------------------------------------------------------------- /phe/__init__.py: -------------------------------------------------------------------------------- 1 | from phe.__about__ import * 2 | from phe.encoding import EncodedNumber 3 | from phe.paillier import generate_paillier_keypair 4 | from phe.paillier import EncryptedNumber 5 | from phe.paillier import PaillierPrivateKey, PaillierPublicKey 6 | from phe.paillier import PaillierPrivateKeyring 7 | 8 | import phe.util 9 | 10 | try: 11 | import phe.command_line 12 | except ImportError: 13 | pass 14 | -------------------------------------------------------------------------------- /phe/command_line.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from __future__ import print_function 3 | import datetime 4 | import json 5 | 6 | import click 7 | import phe 8 | 9 | __author__ = 'brian' 10 | 11 | 12 | def log(m, color='red'): 13 | click.echo(click.style(m, fg=color), err=True) 14 | 15 | 16 | @click.group("pheutil") 17 | @click.version_option('1.0-alpha') 18 | @click.option('--verbose', '-v', is_flag=True, 19 | help='Enables verbose mode.') 20 | def cli(verbose=False): 21 | """CLI for interacting with python-paillier 22 | """ 23 | 24 | @cli.command("genpkey") 25 | @click.argument('output', type=click.File('w')) 26 | @click.option("--keysize", type=int, default=2048, 27 | help="The keysize in bits. Defaults to 2048") 28 | @click.option("--id", type=str, default=None, 29 | help="Add an identifying comment to the key") 30 | def generate_keypair(keysize, id, output): 31 | """Generate a paillier private key. 32 | 33 | Output as JWK to given output file. Use "-" to output the private key to 34 | stdout. See the extract command to extract the public component of the 35 | private key. 36 | 37 | Note: 38 | The default ID text includes the current time. 39 | """ 40 | log("Generating a paillier keypair with keysize of {}".format(keysize)) 41 | pub, priv = phe.generate_paillier_keypair(n_length=keysize) 42 | 43 | log("Keys generated") 44 | 45 | date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 46 | jwk_public = { 47 | 'kty': "DAJ", 48 | 'alg': "PAI-GN1", 49 | "key_ops": ["encrypt"], 50 | 'n': phe.util.int_to_base64(pub.n), 51 | 'kid': "Paillier public key generated by pheutil on {}".format(date) 52 | } 53 | 54 | jwk_private = { 55 | 'kty': "DAJ", 56 | 'key_ops': ["decrypt"], 57 | 'p': phe.util.int_to_base64(priv.p), 58 | 'q': phe.util.int_to_base64(priv.q), 59 | 'pub': jwk_public, 60 | 'kid': "Paillier private key generated by pheutil on {}".format(date) 61 | } 62 | 63 | json.dump(jwk_private, output) 64 | output.write('\n') 65 | 66 | log("Private key written to {}".format(output.name)) 67 | 68 | 69 | @cli.command() 70 | @click.argument('input', type=click.File('r')) 71 | @click.argument('output', type=click.File('w')) 72 | def extract(input, output): 73 | """Extract public key from private key. 74 | 75 | Given INPUT a private paillier key file as generated by generate, extract the 76 | public key portion to OUTPUT. 77 | 78 | Use "-" to output to stdout. 79 | """ 80 | log("Loading paillier keypair") 81 | priv = json.load(input) 82 | error_msg = "Invalid private key" 83 | assert 'pub' in priv, error_msg 84 | assert priv['kty'] == 'DAJ', error_msg 85 | json.dump(priv['pub'], output) 86 | output.write('\n') 87 | log("Public key written to {}".format(output.name)) 88 | 89 | 90 | 91 | @cli.command() 92 | @click.argument('public', type=click.File('r')) 93 | @click.argument('plaintext', type=str) 94 | @click.option('--output', type=click.File('w'), 95 | help="Save to file instead of stdout") 96 | def encrypt(public, plaintext, output=None): 97 | """Encrypt a number with public key. 98 | 99 | The PLAINTEXT input will be interpreted as a floating point number. 100 | 101 | Output will be a JSON object with a "v" attribute containing the 102 | ciphertext as a string, and "e" the exponent as a Number, 103 | where possible fixed at -32. 104 | 105 | Note if you are passing a negative number to encrypt, you will 106 | need to include a "--" between the public key and your plaintext. 107 | """ 108 | num = float(plaintext) 109 | 110 | log("Loading public key") 111 | publickeydata = json.load(public) 112 | pub = load_public_key(publickeydata) 113 | 114 | log("Encrypting: {:+.16f}".format(num)) 115 | enc = pub.encrypt(num) 116 | serialised = serialise_encrypted(enc) 117 | print(serialised, file=output) 118 | 119 | 120 | def serialise_encrypted(enc): 121 | if enc.exponent > -32: 122 | enc = enc.decrease_exponent_to(-32) 123 | assert enc.exponent == -32 124 | else: 125 | log("Exponent is less than -32") 126 | 127 | obj = json.dumps({ 128 | "v": str(enc.ciphertext()), 129 | "e": enc.exponent 130 | }) 131 | return obj 132 | 133 | 134 | @cli.command() 135 | @click.argument('private', type=click.File('r')) 136 | @click.argument('ciphertext', type=click.File('r')) 137 | @click.option('--output', type=click.File('w'), 138 | help="Save to file instead of stdout") 139 | def decrypt(private, ciphertext, output): 140 | """Decrypt ciphertext with private key. 141 | 142 | Requires PRIVATE key file and the CIPHERTEXT encrypted with 143 | the corresponding public key. 144 | """ 145 | privatekeydata = json.load(private) 146 | assert 'pub' in privatekeydata 147 | pub = load_public_key(privatekeydata['pub']) 148 | 149 | log("Loading private key") 150 | private_key_error = "Invalid private key" 151 | assert 'key_ops' in privatekeydata, private_key_error 152 | assert "decrypt" in privatekeydata['key_ops'], private_key_error 153 | assert 'p' in privatekeydata, private_key_error 154 | assert 'q' in privatekeydata, private_key_error 155 | assert privatekeydata['kty'] == 'DAJ', private_key_error 156 | 157 | _p = phe.util.base64_to_int(privatekeydata['p']) 158 | _q = phe.util.base64_to_int(privatekeydata['q']) 159 | 160 | private_key = phe.PaillierPrivateKey(pub, _p, _q) 161 | 162 | log("Decrypting ciphertext") 163 | enc = load_encrypted_number(ciphertext, pub) 164 | out = private_key.decrypt(enc) 165 | print(out, file=output) 166 | 167 | 168 | @cli.command("addenc") 169 | @click.argument('public', type=click.File('r')) 170 | @click.argument('encrypted_a', type=click.File('r')) 171 | @click.argument('encrypted_b', type=click.File('r')) 172 | @click.option('--output', type=click.File('w'), 173 | help="Save to file instead of stdout") 174 | def add_encrypted(public, encrypted_a, encrypted_b, output): 175 | """Add two encrypted numbers together. 176 | 177 | """ 178 | log("Loading public key") 179 | publickeydata = json.load(public) 180 | pub = load_public_key(publickeydata) 181 | 182 | log("Loading first encrypted number") 183 | enc_a = load_encrypted_number(encrypted_a, pub) 184 | 185 | log("Loading second encrypted number") 186 | enc_b = load_encrypted_number(encrypted_b, pub) 187 | 188 | log("Adding encrypted numbers together") 189 | 190 | enc_result = enc_a + enc_b 191 | serialised_result = serialise_encrypted(enc_result) 192 | print(serialised_result, file=output) 193 | 194 | 195 | @cli.command("add") 196 | @click.argument('public', type=click.File('r')) 197 | @click.argument('encrypted', type=click.File('r')) 198 | @click.argument('plaintext', type=str) 199 | @click.option('--output', type=click.File('w'), 200 | help="Save to file instead of stdout") 201 | def add_encrypted_to_plaintext(public, encrypted, plaintext, output): 202 | """Add encrypted number to unencrypted number. 203 | 204 | Requires a PUBLIC key file, a number ENCRYPTED with that public key 205 | also as a file, and the PLAINTEXT number to add. 206 | 207 | Creates a new encrypted number. 208 | """ 209 | log("Loading public key") 210 | publickeydata = json.load(public) 211 | pub = load_public_key(publickeydata) 212 | 213 | log("Loading encrypted number") 214 | enc = load_encrypted_number(encrypted, pub) 215 | 216 | log("Loading unencrypted number") 217 | num = float(plaintext) 218 | 219 | log("Adding") 220 | enc_result = enc + num 221 | serialised_result = serialise_encrypted(enc_result) 222 | print(serialised_result, file=output) 223 | 224 | 225 | @cli.command("multiply") 226 | @click.argument('public', type=click.File('r')) 227 | @click.argument('encrypted', type=click.File('r')) 228 | @click.argument('plaintext', type=str) 229 | @click.option('--output', type=click.File('w'), 230 | help="Save to file instead of stdout") 231 | def multiply_encrypted_to_plaintext(public, encrypted, plaintext, output): 232 | """Multiply encrypted num with unencrypted num. 233 | 234 | Requires a PUBLIC key file, a number ENCRYPTED with that public key 235 | also as a file, and the PLAINTEXT number to multiply. 236 | 237 | Creates a new encrypted number. 238 | """ 239 | log("Loading public key") 240 | publickeydata = json.load(public) 241 | pub = load_public_key(publickeydata) 242 | 243 | log("Loading encrypted number") 244 | enc = load_encrypted_number(encrypted, pub) 245 | 246 | log("Loading unencrypted number") 247 | num = float(plaintext) 248 | 249 | log("Multiplying") 250 | enc_result = enc * num 251 | 252 | serialised_result = serialise_encrypted(enc_result) 253 | print(serialised_result, file=output) 254 | 255 | 256 | def load_public_key(public_key_data): 257 | error_msg = "Invalid public key" 258 | assert 'alg' in public_key_data, error_msg 259 | assert public_key_data['alg'] == 'PAI-GN1', error_msg 260 | assert public_key_data['kty'] == 'DAJ', error_msg 261 | 262 | n = phe.util.base64_to_int(public_key_data['n']) 263 | pub = phe.PaillierPublicKey(n) 264 | return pub 265 | 266 | 267 | def load_encrypted_number(enc_number_file, pub): 268 | ciphertext_data = json.load(enc_number_file) 269 | assert 'v' in ciphertext_data 270 | assert 'e' in ciphertext_data 271 | 272 | enc = phe.EncryptedNumber(pub, 273 | int(ciphertext_data['v']), 274 | exponent=ciphertext_data['e'] 275 | ) 276 | return enc 277 | 278 | 279 | if __name__ == "__main__": 280 | cli() 281 | -------------------------------------------------------------------------------- /phe/encoding.py: -------------------------------------------------------------------------------- 1 | import fractions 2 | import math 3 | import sys 4 | 5 | 6 | class EncodedNumber(object): 7 | """Represents a float or int encoded for Paillier encryption. 8 | 9 | For end users, this class is mainly useful for specifying precision 10 | when adding/multiplying an :class:`EncryptedNumber` by a scalar. 11 | 12 | If you want to manually encode a number for Paillier encryption, 13 | then use :meth:`encode`, if de-serializing then use 14 | :meth:`__init__`. 15 | 16 | 17 | .. note:: 18 | If working with other Paillier libraries you will have to agree on 19 | a specific :attr:`BASE` and :attr:`LOG2_BASE` - inheriting from this 20 | class and overriding those two attributes will enable this. 21 | 22 | Notes: 23 | Paillier encryption is only defined for non-negative integers less 24 | than :attr:`PaillierPublicKey.n`. Since we frequently want to use 25 | signed integers and/or floating point numbers (luxury!), values 26 | should be encoded as a valid integer before encryption. 27 | 28 | The operations of addition and multiplication [1]_ must be 29 | preserved under this encoding. Namely: 30 | 31 | 1. Decode(Encode(a) + Encode(b)) = a + b 32 | 2. Decode(Encode(a) * Encode(b)) = a * b 33 | 34 | for any real numbers a and b. 35 | 36 | Representing signed integers is relatively easy: we exploit the 37 | modular arithmetic properties of the Paillier scheme. We choose to 38 | represent only integers between 39 | +/-:attr:`~PaillierPublicKey.max_int`, where `max_int` is 40 | approximately :attr:`~PaillierPublicKey.n`/3 (larger integers may 41 | be treated as floats). The range of values between `max_int` and 42 | `n` - `max_int` is reserved for detecting overflows. This encoding 43 | scheme supports properties #1 and #2 above. 44 | 45 | Representing floating point numbers as integers is a harder task. 46 | Here we use a variant of fixed-precision arithmetic. In fixed 47 | precision, you encode by multiplying every float by a large number 48 | (e.g. 1e6) and rounding the resulting product. You decode by 49 | dividing by that number. However, this encoding scheme does not 50 | satisfy property #2 above: upon every multiplication, you must 51 | divide by the large number. In a Paillier scheme, this is not 52 | possible to do without decrypting. For some tasks, this is 53 | acceptable or can be worked around, but for other tasks this can't 54 | be worked around. 55 | 56 | In our scheme, the "large number" is allowed to vary, and we keep 57 | track of it. It is: 58 | 59 | :attr:`BASE` ** :attr:`exponent` 60 | 61 | One number has many possible encodings; this property can be used 62 | to mitigate the leak of information due to the fact that 63 | :attr:`exponent` is never encrypted. 64 | 65 | For more details, see :meth:`encode`. 66 | 67 | .. rubric:: Footnotes 68 | 69 | .. [1] Technically, since Paillier encryption only supports 70 | multiplication by a scalar, it may be possible to define a 71 | secondary encoding scheme `Encode'` such that property #2 is 72 | relaxed to: 73 | 74 | Decode(Encode(a) * Encode'(b)) = a * b 75 | 76 | We don't do this. 77 | 78 | 79 | Args: 80 | public_key (PaillierPublicKey): public key for which to encode 81 | (this is necessary because :attr:`~PaillierPublicKey.max_int` 82 | varies) 83 | encoding (int): The encoded number to store. Must be positive and 84 | less than :attr:`~PaillierPublicKey.max_int`. 85 | exponent (int): Together with :attr:`BASE`, determines the level 86 | of fixed-precision used in encoding the number. 87 | 88 | Attributes: 89 | public_key (PaillierPublicKey): public key for which to encode 90 | (this is necessary because :attr:`~PaillierPublicKey.max_int` 91 | varies) 92 | encoding (int): The encoded number to store. Must be positive and 93 | less than :attr:`~PaillierPublicKey.max_int`. 94 | exponent (int): Together with :attr:`BASE`, determines the level 95 | of fixed-precision used in encoding the number. 96 | """ 97 | BASE = 16 98 | """Base to use when exponentiating. Larger `BASE` means 99 | that :attr:`exponent` leaks less information. If you vary this, 100 | you'll have to manually inform anyone decoding your numbers. 101 | """ 102 | LOG2_BASE = math.log(BASE, 2) 103 | FLOAT_MANTISSA_BITS = sys.float_info.mant_dig 104 | 105 | def __init__(self, public_key, encoding, exponent): 106 | self.public_key = public_key 107 | self.encoding = encoding 108 | self.exponent = exponent 109 | 110 | @classmethod 111 | def encode(cls, public_key, scalar, precision=None, max_exponent=None): 112 | """Return an encoding of an int or float. 113 | 114 | This encoding is carefully chosen so that it supports the same 115 | operations as the Paillier cryptosystem. 116 | 117 | If *scalar* is a float, first approximate it as an int, `int_rep`: 118 | 119 | scalar = int_rep * (:attr:`BASE` ** :attr:`exponent`), 120 | 121 | for some (typically negative) integer exponent, which can be 122 | tuned using *precision* and *max_exponent*. Specifically, 123 | :attr:`exponent` is chosen to be equal to or less than 124 | *max_exponent*, and such that the number *precision* is not 125 | rounded to zero. 126 | 127 | Having found an integer representation for the float (or having 128 | been given an int `scalar`), we then represent this integer as 129 | a non-negative integer < :attr:`~PaillierPublicKey.n`. 130 | 131 | Paillier homomorphic arithemetic works modulo 132 | :attr:`~PaillierPublicKey.n`. We take the convention that a 133 | number x < n/3 is positive, and that a number x > 2n/3 is 134 | negative. The range n/3 < x < 2n/3 allows for overflow 135 | detection. 136 | 137 | Args: 138 | public_key (PaillierPublicKey): public key for which to encode 139 | (this is necessary because :attr:`~PaillierPublicKey.n` 140 | varies). 141 | scalar: an int or float to be encrypted. 142 | If int, it must satisfy abs(*value*) < 143 | :attr:`~PaillierPublicKey.n`/3. 144 | If float, it must satisfy abs(*value* / *precision*) << 145 | :attr:`~PaillierPublicKey.n`/3 146 | (i.e. if a float is near the limit then detectable 147 | overflow may still occur) 148 | precision (float): Choose exponent (i.e. fix the precision) so 149 | that this number is distinguishable from zero. If `scalar` 150 | is a float, then this is set so that minimal precision is 151 | lost. Lower precision leads to smaller encodings, which 152 | might yield faster computation. 153 | max_exponent (int): Ensure that the exponent of the returned 154 | `EncryptedNumber` is at most this. 155 | 156 | Returns: 157 | EncodedNumber: Encoded form of *scalar*, ready for encryption 158 | against *public_key*. 159 | """ 160 | # Calculate the maximum exponent for desired precision 161 | if precision is None: 162 | if isinstance(scalar, int): 163 | prec_exponent = 0 164 | elif isinstance(scalar, float): 165 | # Encode with *at least* as much precision as the python float 166 | # What's the base-2 exponent on the float? 167 | bin_flt_exponent = math.frexp(scalar)[1] 168 | 169 | # What's the base-2 exponent of the least significant bit? 170 | # The least significant bit has value 2 ** bin_lsb_exponent 171 | bin_lsb_exponent = bin_flt_exponent - cls.FLOAT_MANTISSA_BITS 172 | 173 | # What's the corresponding base BASE exponent? Round that down. 174 | prec_exponent = math.floor(bin_lsb_exponent / cls.LOG2_BASE) 175 | else: 176 | raise TypeError("Don't know the precision of type %s." 177 | % type(scalar)) 178 | else: 179 | prec_exponent = math.floor(math.log(precision, cls.BASE)) 180 | 181 | # Remember exponents are negative for numbers < 1. 182 | # If we're going to store numbers with a more negative 183 | # exponent than demanded by the precision, then we may 184 | # as well bump up the actual precision. 185 | if max_exponent is None: 186 | exponent = prec_exponent 187 | else: 188 | exponent = min(max_exponent, prec_exponent) 189 | 190 | # Use rationals instead of floats to avoid overflow. 191 | int_rep = round(fractions.Fraction(scalar) 192 | * fractions.Fraction(cls.BASE) ** -exponent) 193 | 194 | if abs(int_rep) > public_key.max_int: 195 | raise ValueError('Integer needs to be within +/- %d but got %d' 196 | % (public_key.max_int, int_rep)) 197 | 198 | # Wrap negative numbers by adding n 199 | return cls(public_key, int_rep % public_key.n, exponent) 200 | 201 | def decode(self): 202 | """Decode plaintext and return the result. 203 | 204 | Returns: 205 | an int or float: the decoded number. N.B. if the number 206 | returned is an integer, it will not be of type float. 207 | 208 | Raises: 209 | OverflowError: if overflow is detected in the decrypted number. 210 | """ 211 | if self.encoding >= self.public_key.n: 212 | # Should be mod n 213 | raise ValueError('Attempted to decode corrupted number') 214 | elif self.encoding <= self.public_key.max_int: 215 | # Positive 216 | mantissa = self.encoding 217 | elif self.encoding >= self.public_key.n - self.public_key.max_int: 218 | # Negative 219 | mantissa = self.encoding - self.public_key.n 220 | else: 221 | raise OverflowError('Overflow detected in decrypted number') 222 | 223 | if self.exponent >= 0: 224 | # Integer multiplication. This is exact. 225 | return mantissa * self.BASE ** self.exponent 226 | else: 227 | # BASE ** -e is an integer, so below is a division of ints. 228 | # Not coercing mantissa to float prevents some overflows. 229 | try: 230 | return mantissa / self.BASE ** -self.exponent 231 | except OverflowError as e: 232 | raise OverflowError( 233 | 'decoded result too large for a float') from e 234 | 235 | def decrease_exponent_to(self, new_exp): 236 | """Return an `EncodedNumber` with same value but lower exponent. 237 | 238 | If we multiply the encoded value by :attr:`BASE` and decrement 239 | :attr:`exponent`, then the decoded value does not change. Thus 240 | we can almost arbitrarily ratchet down the exponent of an 241 | :class:`EncodedNumber` - we only run into trouble when the encoded 242 | integer overflows. There may not be a warning if this happens. 243 | 244 | This is necessary when adding :class:`EncodedNumber` instances, 245 | and can also be useful to hide information about the precision 246 | of numbers - e.g. a protocol can fix the exponent of all 247 | transmitted :class:`EncodedNumber` to some lower bound(s). 248 | 249 | Args: 250 | new_exp (int): the desired exponent. 251 | 252 | Returns: 253 | EncodedNumber: Instance with the same value and desired 254 | exponent. 255 | 256 | Raises: 257 | ValueError: You tried to increase the exponent, which can't be 258 | done without decryption. 259 | """ 260 | if new_exp > self.exponent: 261 | raise ValueError('New exponent %i should be more negative than' 262 | 'old exponent %i' % (new_exp, self.exponent)) 263 | factor = pow(self.BASE, self.exponent - new_exp) 264 | new_enc = self.encoding * factor % self.public_key.n 265 | return self.__class__(self.public_key, new_enc, new_exp) 266 | -------------------------------------------------------------------------------- /phe/paillier.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Portions copyright 2012 Google Inc. All Rights Reserved. 3 | # This file has been modified by NICTA 4 | 5 | # This file is part of pyphe. 6 | # 7 | # pyphe is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # pyphe is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with pyphe. If not, see . 19 | 20 | """Paillier encryption library for partially homomorphic encryption.""" 21 | import random 22 | 23 | try: 24 | from collections.abc import Mapping 25 | except ImportError: 26 | Mapping = dict 27 | 28 | from phe import EncodedNumber 29 | from phe.util import invert, powmod, mulmod, getprimeover, isqrt 30 | 31 | # Paillier cryptosystem is based on integer factorisation. 32 | # The default is chosen to give a minimum of 128 bits of security. 33 | # https://www.keylength.com/en/4/ 34 | DEFAULT_KEYSIZE = 3072 35 | 36 | 37 | def generate_paillier_keypair(private_keyring=None, n_length=DEFAULT_KEYSIZE): 38 | """Return a new :class:`PaillierPublicKey` and :class:`PaillierPrivateKey`. 39 | 40 | Add the private key to *private_keyring* if given. 41 | 42 | Args: 43 | private_keyring (PaillierPrivateKeyring): a 44 | :class:`PaillierPrivateKeyring` on which to store the private 45 | key. 46 | n_length: key size in bits. 47 | 48 | Returns: 49 | tuple: The generated :class:`PaillierPublicKey` and 50 | :class:`PaillierPrivateKey` 51 | """ 52 | p = q = n = None 53 | n_len = 0 54 | while n_len != n_length: 55 | p = getprimeover(n_length // 2) 56 | q = p 57 | while q == p: 58 | q = getprimeover(n_length // 2) 59 | n = p * q 60 | n_len = n.bit_length() 61 | 62 | public_key = PaillierPublicKey(n) 63 | private_key = PaillierPrivateKey(public_key, p, q) 64 | 65 | if private_keyring is not None: 66 | private_keyring.add(private_key) 67 | 68 | return public_key, private_key 69 | 70 | 71 | class PaillierPublicKey(object): 72 | """Contains a public key and associated encryption methods. 73 | 74 | Args: 75 | 76 | n (int): the modulus of the public key - see Paillier's paper. 77 | 78 | Attributes: 79 | g (int): part of the public key - see Paillier's paper. 80 | n (int): part of the public key - see Paillier's paper. 81 | nsquare (int): :attr:`n` ** 2, stored for frequent use. 82 | max_int (int): Maximum int that may safely be stored. This can be 83 | increased, if you are happy to redefine "safely" and lower the 84 | chance of detecting an integer overflow. 85 | """ 86 | def __init__(self, n): 87 | self.g = n + 1 88 | self.n = n 89 | self.nsquare = n * n 90 | self.max_int = n // 3 - 1 91 | 92 | def __repr__(self): 93 | publicKeyHash = hex(hash(self))[2:] 94 | return "".format(publicKeyHash[:10]) 95 | 96 | def __eq__(self, other): 97 | return self.n == other.n 98 | 99 | def __hash__(self): 100 | return hash(self.n) 101 | 102 | def raw_encrypt(self, plaintext, r_value=None): 103 | """Paillier encryption of a positive integer plaintext < :attr:`n`. 104 | 105 | You probably should be using :meth:`encrypt` instead, because it 106 | handles positive and negative ints and floats. 107 | 108 | Args: 109 | plaintext (int): a positive integer < :attr:`n` to be Paillier 110 | encrypted. Typically this is an encoding of the actual 111 | number you want to encrypt. 112 | r_value (int): obfuscator for the ciphertext; by default (i.e. 113 | r_value is None), a random value is used. 114 | 115 | Returns: 116 | int: Paillier encryption of plaintext. 117 | 118 | Raises: 119 | TypeError: if plaintext is not an int. 120 | """ 121 | if not isinstance(plaintext, int): 122 | raise TypeError('Expected int type plaintext but got: %s' % 123 | type(plaintext)) 124 | 125 | if self.n - self.max_int <= plaintext < self.n: 126 | # Very large plaintext, take a sneaky shortcut using inverses 127 | neg_plaintext = self.n - plaintext # = abs(plaintext - nsquare) 128 | # avoid using gmpy2's mulmod when a * b < c 129 | neg_ciphertext = (self.n * neg_plaintext + 1) % self.nsquare 130 | nude_ciphertext = invert(neg_ciphertext, self.nsquare) 131 | else: 132 | # we chose g = n + 1, so that we can exploit the fact that 133 | # (n+1)^plaintext = n*plaintext + 1 mod n^2 134 | nude_ciphertext = (self.n * plaintext + 1) % self.nsquare 135 | 136 | r = r_value or self.get_random_lt_n() 137 | obfuscator = powmod(r, self.n, self.nsquare) 138 | 139 | return mulmod(nude_ciphertext, obfuscator, self.nsquare) 140 | 141 | def get_random_lt_n(self): 142 | """Return a cryptographically random number less than :attr:`n`""" 143 | return random.SystemRandom().randrange(1, self.n) 144 | 145 | def encrypt(self, value, precision=None, r_value=None): 146 | """Encode and Paillier encrypt a real number *value*. 147 | 148 | Args: 149 | value: an int or float to be encrypted. 150 | If int, it must satisfy abs(*value*) < :attr:`n`/3. 151 | If float, it must satisfy abs(*value* / *precision*) << 152 | :attr:`n`/3 153 | (i.e. if a float is near the limit then detectable 154 | overflow may still occur) 155 | precision (float): Passed to :meth:`EncodedNumber.encode`. 156 | If *value* is a float then *precision* is the maximum 157 | **absolute** error allowed when encoding *value*. Defaults 158 | to encoding *value* exactly. 159 | r_value (int): obfuscator for the ciphertext; by default (i.e. 160 | if *r_value* is None), a random value is used. 161 | 162 | Returns: 163 | EncryptedNumber: An encryption of *value*. 164 | 165 | Raises: 166 | ValueError: if *value* is out of range or *precision* is so 167 | high that *value* is rounded to zero. 168 | """ 169 | 170 | if isinstance(value, EncodedNumber): 171 | encoding = value 172 | else: 173 | encoding = EncodedNumber.encode(self, value, precision) 174 | 175 | return self.encrypt_encoded(encoding, r_value) 176 | 177 | def encrypt_encoded(self, encoding, r_value): 178 | """Paillier encrypt an encoded value. 179 | 180 | Args: 181 | encoding: The EncodedNumber instance. 182 | r_value (int): obfuscator for the ciphertext; by default (i.e. 183 | if *r_value* is None), a random value is used. 184 | 185 | Returns: 186 | EncryptedNumber: An encryption of *value*. 187 | """ 188 | # If r_value is None, obfuscate in a call to .obfuscate() (below) 189 | obfuscator = r_value or 1 190 | ciphertext = self.raw_encrypt(encoding.encoding, r_value=obfuscator) 191 | encrypted_number = EncryptedNumber(self, ciphertext, encoding.exponent) 192 | if r_value is None: 193 | encrypted_number.obfuscate() 194 | return encrypted_number 195 | 196 | 197 | class PaillierPrivateKey(object): 198 | """Contains a private key and associated decryption method. 199 | 200 | Args: 201 | public_key (:class:`PaillierPublicKey`): The corresponding public 202 | key. 203 | p (int): private secret - see Paillier's paper. 204 | q (int): private secret - see Paillier's paper. 205 | 206 | Attributes: 207 | public_key (PaillierPublicKey): The corresponding public 208 | key. 209 | p (int): private secret - see Paillier's paper. 210 | q (int): private secret - see Paillier's paper. 211 | psquare (int): p^2 212 | qsquare (int): q^2 213 | p_inverse (int): p^-1 mod q 214 | hp (int): h(p) - see Paillier's paper. 215 | hq (int): h(q) - see Paillier's paper. 216 | """ 217 | def __init__(self, public_key, p, q): 218 | if not p*q == public_key.n: 219 | raise ValueError('given public key does not match the given p and q.') 220 | if p == q: 221 | # check that p and q are different, otherwise we can't compute p^-1 mod q 222 | raise ValueError('p and q have to be different') 223 | self.public_key = public_key 224 | if q < p: #ensure that p < q. 225 | self.p = q 226 | self.q = p 227 | else: 228 | self.p = p 229 | self.q = q 230 | self.psquare = self.p * self.p 231 | 232 | self.qsquare = self.q * self.q 233 | self.p_inverse = invert(self.p, self.q) 234 | self.hp = self.h_function(self.p, self.psquare) 235 | self.hq = self.h_function(self.q, self.qsquare) 236 | 237 | @staticmethod 238 | def from_totient(public_key, totient): 239 | """given the totient, one can factorize the modulus 240 | 241 | The totient is defined as totient = (p - 1) * (q - 1), 242 | and the modulus is defined as modulus = p * q 243 | 244 | Args: 245 | public_key (PaillierPublicKey): The corresponding public 246 | key 247 | totient (int): the totient of the modulus 248 | 249 | Returns: 250 | the :class:`PaillierPrivateKey` that corresponds to the inputs 251 | 252 | Raises: 253 | ValueError: if the given totient is not the totient of the modulus 254 | of the given public key 255 | """ 256 | p_plus_q = public_key.n - totient + 1 257 | p_minus_q = isqrt(p_plus_q * p_plus_q - public_key.n * 4) 258 | q = (p_plus_q - p_minus_q) // 2 259 | p = p_plus_q - q 260 | if not p*q == public_key.n: 261 | raise ValueError('given public key and totient do not match.') 262 | return PaillierPrivateKey(public_key, p, q) 263 | 264 | def __repr__(self): 265 | pub_repr = repr(self.public_key) 266 | return "".format(pub_repr) 267 | 268 | def decrypt(self, encrypted_number): 269 | """Return the decrypted & decoded plaintext of *encrypted_number*. 270 | 271 | Uses the default :class:`EncodedNumber`, if using an alternative encoding 272 | scheme, use :meth:`decrypt_encoded` or :meth:`raw_decrypt` instead. 273 | 274 | Args: 275 | encrypted_number (EncryptedNumber): an 276 | :class:`EncryptedNumber` with a public key that matches this 277 | private key. 278 | 279 | Returns: 280 | the int or float that `EncryptedNumber` was holding. N.B. if 281 | the number returned is an integer, it will not be of type 282 | float. 283 | 284 | Raises: 285 | TypeError: If *encrypted_number* is not an 286 | :class:`EncryptedNumber`. 287 | ValueError: If *encrypted_number* was encrypted against a 288 | different key. 289 | """ 290 | encoded = self.decrypt_encoded(encrypted_number) 291 | return encoded.decode() 292 | 293 | def decrypt_encoded(self, encrypted_number, Encoding=None): 294 | """Return the :class:`EncodedNumber` decrypted from *encrypted_number*. 295 | 296 | Args: 297 | encrypted_number (EncryptedNumber): an 298 | :class:`EncryptedNumber` with a public key that matches this 299 | private key. 300 | Encoding (class): A class to use instead of :class:`EncodedNumber`, the 301 | encoding used for the *encrypted_number* - used to support alternative 302 | encodings. 303 | 304 | Returns: 305 | :class:`EncodedNumber`: The decrypted plaintext. 306 | 307 | Raises: 308 | TypeError: If *encrypted_number* is not an 309 | :class:`EncryptedNumber`. 310 | ValueError: If *encrypted_number* was encrypted against a 311 | different key. 312 | """ 313 | if not isinstance(encrypted_number, EncryptedNumber): 314 | raise TypeError('Expected encrypted_number to be an EncryptedNumber' 315 | ' not: %s' % type(encrypted_number)) 316 | 317 | if self.public_key != encrypted_number.public_key: 318 | raise ValueError('encrypted_number was encrypted against a ' 319 | 'different key!') 320 | 321 | if Encoding is None: 322 | Encoding = EncodedNumber 323 | 324 | encoded = self.raw_decrypt(encrypted_number.ciphertext(be_secure=False)) 325 | return Encoding(self.public_key, encoded, 326 | encrypted_number.exponent) 327 | 328 | def raw_decrypt(self, ciphertext): 329 | """Decrypt raw ciphertext and return raw plaintext. 330 | 331 | Args: 332 | ciphertext (int): (usually from :meth:`EncryptedNumber.ciphertext()`) 333 | that is to be Paillier decrypted. 334 | 335 | Returns: 336 | int: Paillier decryption of ciphertext. This is a positive 337 | integer < :attr:`public_key.n`. 338 | 339 | Raises: 340 | TypeError: if ciphertext is not an int. 341 | """ 342 | if not isinstance(ciphertext, int): 343 | raise TypeError('Expected ciphertext to be an int, not: %s' % 344 | type(ciphertext)) 345 | 346 | decrypt_to_p = mulmod( 347 | self.l_function(powmod(ciphertext, self.p-1, self.psquare), self.p), 348 | self.hp, 349 | self.p) 350 | decrypt_to_q = mulmod( 351 | self.l_function(powmod(ciphertext, self.q-1, self.qsquare), self.q), 352 | self.hq, 353 | self.q) 354 | return self.crt(decrypt_to_p, decrypt_to_q) 355 | 356 | def h_function(self, x, xsquare): 357 | """Computes the h-function as defined in Paillier's paper page 12, 358 | 'Decryption using Chinese-remaindering'. 359 | """ 360 | return invert(self.l_function(powmod(self.public_key.g, x - 1, xsquare),x), x) 361 | 362 | def l_function(self, x, p): 363 | """Computes the L function as defined in Paillier's paper. That is: L(x,p) = (x-1)/p""" 364 | return (x - 1) // p 365 | 366 | def crt(self, mp, mq): 367 | """The Chinese Remainder Theorem as needed for decryption. Returns the solution modulo n=pq. 368 | 369 | Args: 370 | mp(int): the solution modulo p. 371 | mq(int): the solution modulo q. 372 | """ 373 | u = mulmod(mq - mp, self.p_inverse, self.q) 374 | return mp + (u * self.p) 375 | 376 | def __eq__(self, other): 377 | return self.p == other.p and self.q == other.q 378 | 379 | def __hash__(self): 380 | return hash((self.p, self.q)) 381 | 382 | 383 | class PaillierPrivateKeyring(Mapping): 384 | """Holds several private keys and can decrypt using any of them. 385 | 386 | Acts like a dict, supports :func:`del`, and indexing with **[]**, 387 | but adding keys is done using :meth:`add`. 388 | 389 | Args: 390 | private_keys (list of PaillierPrivateKey): an optional starting 391 | list of :class:`PaillierPrivateKey` instances. 392 | """ 393 | def __init__(self, private_keys=None): 394 | if private_keys is None: 395 | private_keys = [] 396 | public_keys = [k.public_key for k in private_keys] 397 | self.__keyring = dict(zip(public_keys, private_keys)) 398 | 399 | def __getitem__(self, key): 400 | return self.__keyring[key] 401 | 402 | def __len__(self): 403 | return len(self.__keyring) 404 | 405 | def __iter__(self): 406 | return iter(self.__keyring) 407 | 408 | def __delitem__(self, public_key): 409 | del self.__keyring[public_key] 410 | 411 | def add(self, private_key): 412 | """Add a key to the keyring. 413 | 414 | Args: 415 | private_key (PaillierPrivateKey): a key to add to this keyring. 416 | """ 417 | if not isinstance(private_key, PaillierPrivateKey): 418 | raise TypeError("private_key should be of type PaillierPrivateKey, " 419 | "not %s" % type(private_key)) 420 | self.__keyring[private_key.public_key] = private_key 421 | 422 | def decrypt(self, encrypted_number): 423 | """Return the decrypted & decoded plaintext of *encrypted_number*. 424 | 425 | Args: 426 | encrypted_number (EncryptedNumber): encrypted against a known public 427 | key, i.e., one for which the private key is on this keyring. 428 | 429 | Returns: 430 | the int or float that *encrypted_number* was holding. N.B. if 431 | the number returned is an integer, it will not be of type 432 | float. 433 | 434 | Raises: 435 | KeyError: If the keyring does not hold the private key that 436 | decrypts *encrypted_number*. 437 | """ 438 | relevant_private_key = self.__keyring[encrypted_number.public_key] 439 | return relevant_private_key.decrypt(encrypted_number) 440 | 441 | 442 | class EncryptedNumber(object): 443 | """Represents the Paillier encryption of a float or int. 444 | 445 | Typically, an `EncryptedNumber` is created by 446 | :meth:`PaillierPublicKey.encrypt`. You would only instantiate an 447 | `EncryptedNumber` manually if you are de-serializing a number 448 | someone else encrypted. 449 | 450 | 451 | Paillier encryption is only defined for non-negative integers less 452 | than :attr:`PaillierPublicKey.n`. :class:`EncodedNumber` provides 453 | an encoding scheme for floating point and signed integers that is 454 | compatible with the partially homomorphic properties of the Paillier 455 | cryptosystem: 456 | 457 | 1. D(E(a) * E(b)) = a + b 458 | 2. D(E(a)**b) = a * b 459 | 460 | where `a` and `b` are ints or floats, `E` represents encoding then 461 | encryption, and `D` represents decryption then decoding. 462 | 463 | Args: 464 | public_key (PaillierPublicKey): the :class:`PaillierPublicKey` 465 | against which the number was encrypted. 466 | ciphertext (int): encrypted representation of the encoded number. 467 | exponent (int): used by :class:`EncodedNumber` to keep track of 468 | fixed precision. Usually negative. 469 | 470 | Attributes: 471 | public_key (PaillierPublicKey): the :class:`PaillierPublicKey` 472 | against which the number was encrypted. 473 | exponent (int): used by :class:`EncodedNumber` to keep track of 474 | fixed precision. Usually negative. 475 | 476 | Raises: 477 | TypeError: if *ciphertext* is not an int, or if *public_key* is 478 | not a :class:`PaillierPublicKey`. 479 | """ 480 | def __init__(self, public_key, ciphertext, exponent=0): 481 | self.public_key = public_key 482 | self.__ciphertext = ciphertext 483 | self.exponent = exponent 484 | self.__is_obfuscated = False 485 | if isinstance(self.ciphertext, EncryptedNumber): 486 | raise TypeError('ciphertext should be an integer') 487 | if not isinstance(self.public_key, PaillierPublicKey): 488 | raise TypeError('public_key should be a PaillierPublicKey') 489 | 490 | def __add__(self, other): 491 | """Add an int, float, `EncryptedNumber` or `EncodedNumber`.""" 492 | if isinstance(other, EncryptedNumber): 493 | return self._add_encrypted(other) 494 | elif isinstance(other, EncodedNumber): 495 | return self._add_encoded(other) 496 | else: 497 | return self._add_scalar(other) 498 | 499 | def __radd__(self, other): 500 | """Called when Python evaluates `34 + ` 501 | Required for builtin `sum` to work. 502 | """ 503 | return self.__add__(other) 504 | 505 | def __mul__(self, other): 506 | """Multiply by an int, float, or EncodedNumber.""" 507 | if isinstance(other, EncryptedNumber): 508 | raise NotImplementedError('Good luck with that...') 509 | 510 | if isinstance(other, EncodedNumber): 511 | encoding = other 512 | else: 513 | encoding = EncodedNumber.encode(self.public_key, other) 514 | product = self._raw_mul(encoding.encoding) 515 | exponent = self.exponent + encoding.exponent 516 | 517 | return EncryptedNumber(self.public_key, product, exponent) 518 | 519 | def __rmul__(self, other): 520 | return self.__mul__(other) 521 | 522 | def __sub__(self, other): 523 | return self + (other * -1) 524 | 525 | def __rsub__(self, other): 526 | return other + (self * -1) 527 | 528 | def __truediv__(self, scalar): 529 | return self.__mul__(1 / scalar) 530 | 531 | def ciphertext(self, be_secure=True): 532 | """Return the ciphertext of the EncryptedNumber. 533 | 534 | Choosing a random number is slow. Therefore, methods like 535 | :meth:`__add__` and :meth:`__mul__` take a shortcut and do not 536 | follow Paillier encryption fully - every encrypted sum or 537 | product should be multiplied by r ** 538 | :attr:`~PaillierPublicKey.n` for random r < n (i.e., the result 539 | is obfuscated). Not obfuscating provides a big speed up in, 540 | e.g., an encrypted dot product: each of the product terms need 541 | not be obfuscated, since only the final sum is shared with 542 | others - only this final sum needs to be obfuscated. 543 | 544 | Not obfuscating is OK for internal use, where you are happy for 545 | your own computer to know the scalars you've been adding and 546 | multiplying to the original ciphertext. But this is *not* OK if 547 | you're going to be sharing the new ciphertext with anyone else. 548 | 549 | So, by default, this method returns an obfuscated ciphertext - 550 | obfuscating it if necessary. If instead you set `be_secure=False` 551 | then the ciphertext will be returned, regardless of whether it 552 | has already been obfuscated. We thought that this approach, 553 | while a little awkward, yields a safe default while preserving 554 | the option for high performance. 555 | 556 | Args: 557 | be_secure (bool): If any untrusted parties will see the 558 | returned ciphertext, then this should be True. 559 | 560 | Returns: 561 | an int, the ciphertext. If `be_secure=False` then it might be 562 | possible for attackers to deduce numbers involved in 563 | calculating this ciphertext. 564 | """ 565 | if be_secure and not self.__is_obfuscated: 566 | self.obfuscate() 567 | 568 | return self.__ciphertext 569 | 570 | def decrease_exponent_to(self, new_exp): 571 | """Return an EncryptedNumber with same value but lower exponent. 572 | 573 | If we multiply the encoded value by :attr:`EncodedNumber.BASE` and 574 | decrement :attr:`exponent`, then the decoded value does not change. 575 | Thus we can almost arbitrarily ratchet down the exponent of an 576 | `EncryptedNumber` - we only run into trouble when the encoded 577 | integer overflows. There may not be a warning if this happens. 578 | 579 | When adding `EncryptedNumber` instances, their exponents must 580 | match. 581 | 582 | This method is also useful for hiding information about the 583 | precision of numbers - e.g. a protocol can fix the exponent of 584 | all transmitted `EncryptedNumber` instances to some lower bound(s). 585 | 586 | Args: 587 | new_exp (int): the desired exponent. 588 | 589 | Returns: 590 | EncryptedNumber: Instance with the same plaintext and desired 591 | exponent. 592 | 593 | Raises: 594 | ValueError: You tried to increase the exponent. 595 | """ 596 | if new_exp > self.exponent: 597 | raise ValueError('New exponent %i should be more negative than ' 598 | 'old exponent %i' % (new_exp, self.exponent)) 599 | multiplied = self * pow(EncodedNumber.BASE, self.exponent - new_exp) 600 | multiplied.exponent = new_exp 601 | return multiplied 602 | 603 | def obfuscate(self): 604 | """Disguise ciphertext by multiplying by r ** n with random r. 605 | 606 | This operation must be performed for every `EncryptedNumber` 607 | that is sent to an untrusted party, otherwise eavesdroppers 608 | might deduce relationships between this and an antecedent 609 | `EncryptedNumber`. 610 | 611 | For example:: 612 | 613 | enc = public_key.encrypt(1337) 614 | send_to_nsa(enc) # NSA can't decrypt (we hope!) 615 | product = enc * 3.14 616 | send_to_nsa(product) # NSA can deduce 3.14 by bruteforce attack 617 | product2 = enc * 2.718 618 | product2.obfuscate() 619 | send_to_nsa(product) # NSA can't deduce 2.718 by bruteforce attack 620 | """ 621 | r = self.public_key.get_random_lt_n() 622 | r_pow_n = powmod(r, self.public_key.n, self.public_key.nsquare) 623 | self.__ciphertext = mulmod(self.__ciphertext, r_pow_n, self.public_key.nsquare) 624 | self.__is_obfuscated = True 625 | 626 | def _add_scalar(self, scalar): 627 | """Returns E(a + b), given self=E(a) and b. 628 | 629 | Args: 630 | scalar: an int or float b, to be added to `self`. 631 | 632 | Returns: 633 | EncryptedNumber: E(a + b), calculated by encrypting b and 634 | taking the product of E(a) and E(b) modulo 635 | :attr:`~PaillierPublicKey.n` ** 2. 636 | 637 | Raises: 638 | ValueError: if scalar is out of range or precision. 639 | """ 640 | encoded = EncodedNumber.encode(self.public_key, scalar, 641 | max_exponent=self.exponent) 642 | 643 | return self._add_encoded(encoded) 644 | 645 | def _add_encoded(self, encoded): 646 | """Returns E(a + b), given self=E(a) and b. 647 | 648 | Args: 649 | encoded (EncodedNumber): an :class:`EncodedNumber` to be added 650 | to `self`. 651 | 652 | Returns: 653 | EncryptedNumber: E(a + b), calculated by encrypting b and 654 | taking the product of E(a) and E(b) modulo 655 | :attr:`~PaillierPublicKey.n` ** 2. 656 | 657 | Raises: 658 | ValueError: if scalar is out of range or precision. 659 | """ 660 | if self.public_key != encoded.public_key: 661 | raise ValueError("Attempted to add numbers encoded against " 662 | "different public keys!") 663 | 664 | # In order to add two numbers, their exponents must match. 665 | a, b = self, encoded 666 | if a.exponent > b.exponent: 667 | a = self.decrease_exponent_to(b.exponent) 668 | elif a.exponent < b.exponent: 669 | b = b.decrease_exponent_to(a.exponent) 670 | 671 | # Don't bother to salt/obfuscate in a basic operation, do it 672 | # just before leaving the computer. 673 | encrypted_scalar = a.public_key.raw_encrypt(b.encoding, 1) 674 | 675 | sum_ciphertext = a._raw_add(a.ciphertext(False), encrypted_scalar) 676 | return EncryptedNumber(a.public_key, sum_ciphertext, a.exponent) 677 | 678 | def _add_encrypted(self, other): 679 | """Returns E(a + b) given E(a) and E(b). 680 | 681 | Args: 682 | other (EncryptedNumber): an `EncryptedNumber` to add to self. 683 | 684 | Returns: 685 | EncryptedNumber: E(a + b), calculated by taking the product 686 | of E(a) and E(b) modulo :attr:`~PaillierPublicKey.n` ** 2. 687 | 688 | Raises: 689 | ValueError: if numbers were encrypted against different keys. 690 | """ 691 | if self.public_key != other.public_key: 692 | raise ValueError("Attempted to add numbers encrypted against " 693 | "different public keys!") 694 | 695 | # In order to add two numbers, their exponents must match. 696 | a, b = self, other 697 | if a.exponent > b.exponent: 698 | a = self.decrease_exponent_to(b.exponent) 699 | elif a.exponent < b.exponent: 700 | b = b.decrease_exponent_to(a.exponent) 701 | 702 | sum_ciphertext = a._raw_add(a.ciphertext(False), b.ciphertext(False)) 703 | return EncryptedNumber(a.public_key, sum_ciphertext, a.exponent) 704 | 705 | def _raw_add(self, e_a, e_b): 706 | """Returns the integer E(a + b) given ints E(a) and E(b). 707 | 708 | N.B. this returns an int, not an `EncryptedNumber`, and ignores 709 | :attr:`ciphertext` 710 | 711 | Args: 712 | e_a (int): E(a), first term 713 | e_b (int): E(b), second term 714 | 715 | Returns: 716 | int: E(a + b), calculated by taking the product of E(a) and 717 | E(b) modulo :attr:`~PaillierPublicKey.n` ** 2. 718 | """ 719 | return mulmod(e_a, e_b, self.public_key.nsquare) 720 | 721 | def _raw_mul(self, plaintext): 722 | """Returns the integer E(a * plaintext), where E(a) = ciphertext 723 | 724 | Args: 725 | plaintext (int): number by which to multiply the 726 | `EncryptedNumber`. *plaintext* is typically an encoding. 727 | 0 <= *plaintext* < :attr:`~PaillierPublicKey.n` 728 | 729 | Returns: 730 | int: Encryption of the product of `self` and the scalar 731 | encoded in *plaintext*. 732 | 733 | Raises: 734 | TypeError: if *plaintext* is not an int. 735 | ValueError: if *plaintext* is not between 0 and 736 | :attr:`PaillierPublicKey.n`. 737 | """ 738 | if not isinstance(plaintext, int): 739 | raise TypeError('Expected ciphertext to be int, not %s' % 740 | type(plaintext)) 741 | 742 | if plaintext < 0 or plaintext >= self.public_key.n: 743 | raise ValueError('Scalar out of bounds: %i' % plaintext) 744 | 745 | if self.public_key.n - self.public_key.max_int <= plaintext: 746 | # Very large plaintext, play a sneaky trick using inverses 747 | neg_c = invert(self.ciphertext(False), self.public_key.nsquare) 748 | neg_scalar = self.public_key.n - plaintext 749 | return powmod(neg_c, neg_scalar, self.public_key.nsquare) 750 | else: 751 | return powmod(self.ciphertext(False), plaintext, self.public_key.nsquare) 752 | 753 | -------------------------------------------------------------------------------- /phe/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data61/python-paillier/7d9911eb03c3c2d64399bc15405feb5e628379d1/phe/tests/__init__.py -------------------------------------------------------------------------------- /phe/tests/cli_test.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | from unittest import TestCase 4 | import tempfile 5 | 6 | from click.testing import CliRunner 7 | 8 | from phe.command_line import cli 9 | 10 | 11 | class TestConsoleBasics(TestCase): 12 | 13 | def test_cli_includes_help(self): 14 | runner = CliRunner() 15 | result = runner.invoke(cli, ['--help']) 16 | assert result.exit_code == 0 17 | 18 | assert 'Usage' in result.output 19 | assert 'Options' in result.output 20 | assert 'Commands' in result.output 21 | 22 | def test_generate_keypair_to_file(self): 23 | runner = CliRunner() 24 | 25 | with tempfile.NamedTemporaryFile() as outfile: 26 | result = runner.invoke(cli, ['genpkey', '--keysize', '256', outfile.name]) 27 | print(result.output) 28 | assert result.exit_code == 0 29 | 30 | outfile.seek(0) 31 | written_data = outfile.read() 32 | 33 | priv_key = json.loads(written_data.decode('utf-8')) 34 | 35 | assert 'pub' in priv_key 36 | assert 'kty' in priv_key 37 | assert 'p' in priv_key 38 | 39 | def test_generate_keypair_to_stdout(self): 40 | runner = CliRunner() 41 | 42 | result = runner.invoke(cli, ['genpkey', '--keysize', '256', '-']) 43 | 44 | assert 'pub' in result.output 45 | assert 'kty' in result.output 46 | assert 'p' in result.output 47 | 48 | def test_extract_public_key(self): 49 | runner = CliRunner() 50 | 51 | with tempfile.NamedTemporaryFile() as private_keyfile: 52 | runner.invoke(cli, ['genpkey', '--keysize', '256', private_keyfile.name]) 53 | 54 | with tempfile.NamedTemporaryFile() as public_keyfile: 55 | result = runner.invoke(cli, ['extract', private_keyfile.name, public_keyfile.name]) 56 | assert result.exit_code == 0 57 | 58 | public_keyfile.seek(0) 59 | written_data = public_keyfile.read().decode('utf-8') 60 | 61 | assert '"kty":' in written_data 62 | assert '"n":' in written_data 63 | assert '"alg":' in written_data 64 | 65 | assert '"p":' not in written_data 66 | assert '"q":' not in written_data 67 | 68 | 69 | class TestConsoleEncryption(TestCase): 70 | 71 | @classmethod 72 | def setUpClass(cls): 73 | """Generate a keypair and extract the public key. 74 | """ 75 | cls.private_keyfile = tempfile.NamedTemporaryFile() 76 | cls.public_keyfile = tempfile.NamedTemporaryFile() 77 | 78 | cls.runner = CliRunner() 79 | cls.runner.invoke(cli, ['genpkey', '--keysize', '256', cls.private_keyfile.name]) 80 | cls.runner.invoke(cli, ['extract', cls.private_keyfile.name, cls.public_keyfile.name]) 81 | 82 | @classmethod 83 | def tearDownClass(cls): 84 | cls.private_keyfile.close() 85 | cls.public_keyfile.close() 86 | 87 | def test_encrypt_positive_integers(self): 88 | numbers = [0, 1, 2, 5, 10, '1', '10550'] 89 | 90 | for num in numbers: 91 | result = self.runner.invoke(cli, ['encrypt', self.public_keyfile.name, str(num)]) 92 | assert result.exit_code == 0 93 | 94 | def test_encrypt_signed_integers(self): 95 | """encrypting positive and negative integer""" 96 | numbers = [0, 1, -1, 10, '1', '-10550'] 97 | 98 | for num in numbers: 99 | result = self.runner.invoke(cli, ['encrypt', self.public_keyfile.name, "--", str(num)]) 100 | assert result.exit_code == 0 101 | 102 | def test_encrypt_float(self): 103 | numbers = [0.0, 1.1, -0.0001, 100000.01, '1e-20', '-10550e20'] 104 | 105 | for num in numbers: 106 | result = self.runner.invoke(cli, ['encrypt', self.public_keyfile.name, "--", str(num)]) 107 | assert result.exit_code == 0 108 | 109 | def test_encrypt_to_stdout(self): 110 | """Test encrypting and writing output to a file""" 111 | numbers = [0.0, 1.1, -0.0001, 100000.01, '1e-20', '-10550e20'] 112 | 113 | for num in numbers: 114 | result = self.runner.invoke(cli, ['encrypt', self.public_keyfile.name, "--", str(num)]) 115 | assert result.exit_code == 0 116 | 117 | def test_decrypt_positive_integers(self): 118 | numbers = [0, 1, 2, 5, 10, '1', '10550'] 119 | 120 | for num in numbers: 121 | with tempfile.NamedTemporaryFile() as encfile: 122 | fname = encfile.name 123 | 124 | self.runner.invoke(cli, [ 125 | 'encrypt', self.public_keyfile.name, str(num), '--output', fname 126 | ]) 127 | 128 | result = self.runner.invoke(cli, [ 129 | 'decrypt', self.private_keyfile.name, fname 130 | ]) 131 | assert result.exit_code == 0 132 | 133 | assert "{}".format(num) in result.output 134 | 135 | def test_decrypt_signed_integers(self): 136 | numbers = [0, 1, -1, 10, '1', '-10550'] 137 | 138 | for num in numbers: 139 | with tempfile.NamedTemporaryFile() as encfile: 140 | fname = encfile.name 141 | self.runner.invoke(cli, [ 142 | 'encrypt', self.public_keyfile.name, '--output', fname, '--', str(num), 143 | ]) 144 | 145 | result = self.runner.invoke(cli, [ 146 | 'decrypt', self.private_keyfile.name, fname 147 | ]) 148 | assert result.exit_code == 0 149 | 150 | print(result.output) 151 | assert "{}".format(num) in result.output 152 | 153 | def test_decrypt_float(self): 154 | numbers = [0.0, 1.1, -0.0001, 100000.01, '1e-20', '-10550e20'] 155 | 156 | for num in numbers: 157 | with tempfile.NamedTemporaryFile() as encfile: 158 | fname = encfile.name 159 | self.runner.invoke(cli, [ 160 | 'encrypt', self.public_keyfile.name, '--output', fname, '--', str(num), 161 | ]) 162 | 163 | with tempfile.NamedTemporaryFile() as outfile: 164 | result = self.runner.invoke(cli, [ 165 | 'decrypt', self.private_keyfile.name, fname, '--output', outfile.name 166 | ]) 167 | assert result.exit_code == 0 168 | 169 | out = outfile.read() 170 | self.assertAlmostEqual(float(num), float(out)) 171 | 172 | 173 | class TestConsoleHelpers(TestCase): 174 | 175 | @classmethod 176 | def setUpClass(cls): 177 | """Generate a keypair, extract the public key, and encrypt 178 | a list of numbers 179 | 180 | """ 181 | cls.private_keyfile = tempfile.NamedTemporaryFile() 182 | cls.public_keyfile = tempfile.NamedTemporaryFile() 183 | 184 | 185 | cls.runner = CliRunner() 186 | cls.runner.invoke(cli, ['genpkey', '--keysize', '256', cls.private_keyfile.name]) 187 | cls.runner.invoke(cli, ['extract', cls.private_keyfile.name, cls.public_keyfile.name]) 188 | 189 | def setUp(self): 190 | self.enc_a_file = tempfile.NamedTemporaryFile() 191 | self.enc_b_file = tempfile.NamedTemporaryFile() 192 | self.enc_result_file = tempfile.NamedTemporaryFile() 193 | 194 | def encrypt_and_add(self, a, b): 195 | self.runner.invoke(cli, 196 | ['encrypt', self.public_keyfile.name, '--output', self.enc_a_file.name, '--', str(a)]) 197 | self.runner.invoke(cli, 198 | ['encrypt', self.public_keyfile.name, '--output', self.enc_b_file.name, '--', str(b)]) 199 | 200 | result = self.runner.invoke(cli, [ 201 | 'addenc', 202 | self.public_keyfile.name, 203 | self.enc_a_file.name, 204 | self.enc_b_file.name, 205 | '--output', 206 | self.enc_result_file.name 207 | ]) 208 | 209 | assert result.exit_code == 0 210 | 211 | with tempfile.NamedTemporaryFile() as outfile: 212 | result = self.runner.invoke(cli, [ 213 | 'decrypt', self.private_keyfile.name, self.enc_result_file.name, '--output', outfile.name 214 | ]) 215 | assert result.exit_code == 0 216 | 217 | out = outfile.read() 218 | return float(out) 219 | 220 | def _a_b_encrypt_helper(self, a, b, operation): 221 | self.runner.invoke(cli, 222 | [ 223 | 'encrypt', 224 | self.public_keyfile.name, 225 | '--output', 226 | self.enc_a_file.name, 227 | '--', 228 | str(a) 229 | ]) 230 | 231 | result = self.runner.invoke(cli, [ 232 | operation, 233 | '--output', 234 | self.enc_result_file.name, 235 | self.public_keyfile.name, 236 | self.enc_a_file.name, 237 | '--', 238 | str(b) 239 | ]) 240 | 241 | assert result.exit_code == 0, "Problem carrying out the {} operation".format(operation) 242 | 243 | with tempfile.NamedTemporaryFile() as outfile: 244 | result = self.runner.invoke(cli, [ 245 | 'decrypt', self.private_keyfile.name, self.enc_result_file.name, '--output', outfile.name 246 | ]) 247 | assert result.exit_code == 0 248 | 249 | out = outfile.read() 250 | return float(out) 251 | 252 | def encrypt_a_and_add_b(self, a, b): 253 | return self._a_b_encrypt_helper(a, b, 'add') 254 | 255 | def encrypt_a_and_multiply_b(self, a, b): 256 | return self._a_b_encrypt_helper(a, b, 'multiply') 257 | 258 | 259 | class TestConsoleAddition(TestConsoleHelpers): 260 | 261 | def test_addenc_int(self): 262 | a, b = 12345, 6789 263 | out = self.encrypt_and_add(a, b) 264 | self.assertAlmostEqual(float(a + b), float(out)) 265 | 266 | def test_add_int(self): 267 | a, b = 12345, 6789 268 | out = self.encrypt_a_and_add_b(a, b) 269 | self.assertAlmostEqual(float(a + b), float(out)) 270 | 271 | def test_addenc_large_ints(self): 272 | """Test adding large integers. 273 | """ 274 | a, b = int(1.2e10), int(1e15) 275 | out = self.encrypt_and_add(a, b) 276 | self.assertAlmostEqual(float(a + b), float(out)) 277 | 278 | def test_add_large_ints(self): 279 | """Test adding large integers. 280 | """ 281 | a, b = int(1.2e10), int(1e15) 282 | out = self.encrypt_a_and_add_b(a, b) 283 | self.assertAlmostEqual(float(a + b), float(out)) 284 | 285 | 286 | def test_addenc_signed_int(self): 287 | a, b = 12345, -6789 288 | out = self.encrypt_and_add(a, b) 289 | self.assertAlmostEqual(float(a + b), float(out)) 290 | 291 | def test_add_signed_int(self): 292 | a, b = 12345, -6789 293 | out = self.encrypt_a_and_add_b(a, b) 294 | self.assertAlmostEqual(float(a + b), float(out)) 295 | 296 | def test_addenc_floats(self): 297 | a, b = 123.45, 67.89 298 | out = self.encrypt_and_add(a, b) 299 | self.assertAlmostEqual(float(a + b), float(out)) 300 | 301 | def test_add_floats(self): 302 | a, b = 123.45, 67.89 303 | out = self.encrypt_a_and_add_b(a, b) 304 | self.assertAlmostEqual(float(a + b), float(out)) 305 | 306 | def test_addenc_large_floats(self): 307 | """Test adding large integers. 308 | """ 309 | a, b = 2.3e32, 1.4e32 310 | out = self.encrypt_and_add(a, b) 311 | self.assertAlmostEqual(float(a + b), float(out)) 312 | 313 | def test_add_large_floats(self): 314 | """Test adding large integers. 315 | """ 316 | a, b = 2.3e32, 1.4e32 317 | out = self.encrypt_a_and_add_b(a, b) 318 | self.assertAlmostEqual(float(a + b), float(out)) 319 | 320 | 321 | class TestConsoleMultiplication(TestConsoleHelpers): 322 | """ 323 | Expected to fail until we decide if encrypted numbers with different 324 | exponents are allowed for this CLI... 325 | """ 326 | def test_multiply_ints(self): 327 | a, b = 15, 6 328 | out = self.encrypt_a_and_multiply_b(a, b) 329 | self.assertAlmostEqual(int(a * b), int(out)) 330 | 331 | def test_multiply_floats(self): 332 | a, b = 1.2345, 0.6 333 | out = self.encrypt_a_and_multiply_b(a, b) 334 | self.assertAlmostEqual(float(a * b), float(out)) 335 | 336 | def test_multiply_random_ints(self): 337 | """ 338 | """ 339 | MAX = 100000000000 340 | MIN = -MAX 341 | 342 | for _ in range(50): 343 | a, b = random.randrange(MIN, MAX), random.randrange(MIN, MAX) 344 | out = self.encrypt_a_and_multiply_b(a, b) 345 | self.assertAlmostEqual(float(a * b), float(out)) 346 | 347 | 348 | class TestFuzz(TestConsoleHelpers): 349 | 350 | def test_addenc_random_ints(self): 351 | """Test adding random ints 352 | """ 353 | MAX = 1000000000000000 354 | MIN = -MAX 355 | 356 | for _ in range(20): 357 | a, b = random.randrange(MIN, MAX), random.randrange(MIN, MAX) 358 | out = self.encrypt_and_add(a, b) 359 | self.assertAlmostEqual(float(a + b), float(out)) 360 | 361 | def test_add_random_ints(self): 362 | """Test adding random ints 363 | """ 364 | MAX = 1000000000000000 365 | MIN = -MAX 366 | 367 | for _ in range(20): 368 | a, b = random.randrange(MIN, MAX), random.randrange(MIN, MAX) 369 | out = self.encrypt_a_and_add_b(a, b) 370 | self.assertAlmostEqual(float(a + b), float(out)) 371 | 372 | def test_addenc_random_floats(self): 373 | """Test adding random floating point numbers from the range [0.0, 1.0) 374 | """ 375 | for _ in range(20): 376 | a, b = random.random(), random.random() 377 | out = self.encrypt_and_add(a, b) 378 | self.assertAlmostEqual(float(a + b), float(out)) 379 | 380 | def test_add_random_floats(self): 381 | """Test adding random floating point numbers from the range [0.0, 1.0) 382 | """ 383 | for _ in range(20): 384 | a, b = random.random(), random.random() 385 | out = self.encrypt_a_and_add_b(a, b) 386 | self.assertAlmostEqual(float(a + b), float(out)) 387 | 388 | 389 | def test_multiply_random_ints(self): 390 | """ 391 | """ 392 | MAX = 10000 393 | MIN = -MAX 394 | 395 | for _ in range(20): 396 | a, b = random.randrange(MIN, MAX), random.randrange(MIN, MAX) 397 | out = self.encrypt_a_and_multiply_b(a, b) 398 | self.assertAlmostEqual(float(a * b), float(out)) 399 | 400 | 401 | -------------------------------------------------------------------------------- /phe/tests/math_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Unittest for maths involving the paillier module.""" 4 | 5 | # This file is part of pyphe. 6 | # 7 | # Pyphe is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Pyphe is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with pyphe. If not, see . 19 | 20 | import unittest 21 | import numpy as np 22 | 23 | from phe import paillier 24 | 25 | 26 | class PaillierTest(unittest.TestCase): 27 | 28 | @classmethod 29 | def setUpClass(cls): 30 | # Could move this into setUpModule() if we get too many classes 31 | cls.public_key, cls.private_key = paillier.generate_paillier_keypair() 32 | 33 | enc_flt = cls.public_key.encrypt 34 | 35 | cls.vec4_1_non_neg = [0.3, 1.7, 6857.6, 1e-6] 36 | cls.vec4_2 = [-68, 1.8, 34, 1.5e6] 37 | 38 | cls.e_vec4_1 = [enc_flt(x) for x in cls.vec4_1_non_neg] 39 | cls.e_vec4_2 = [enc_flt(x) for x in cls.vec4_2] 40 | 41 | 42 | class ArithmeticTest(PaillierTest): 43 | 44 | def testMean(self): 45 | # Check that we can take an average as good as numpy 46 | e_mean4_1 = np.mean(self.e_vec4_1) 47 | self.assertAlmostEqual(np.mean(self.vec4_1_non_neg), 48 | self.private_key.decrypt(e_mean4_1)) 49 | 50 | emean4_2 = np.mean(self.e_vec4_2) 51 | self.assertAlmostEqual(np.mean(self.vec4_2), 52 | self.private_key.decrypt(emean4_2)) 53 | 54 | def testDot(self): 55 | # Check that our dot product is as good as numpy's 56 | e_dot_4_2_4_1 = np.dot(self.e_vec4_2, self.vec4_1_non_neg) 57 | self.assertAlmostEqual(np.dot(self.vec4_2, self.vec4_1_non_neg), 58 | self.private_key.decrypt(e_dot_4_2_4_1)) 59 | 60 | 61 | if __name__ == '__main__': 62 | unittest.main() 63 | -------------------------------------------------------------------------------- /phe/tests/util_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.4 2 | 3 | # This file is part of pyphe. 4 | # 5 | # Pyphe is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Pyphe is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with pyphe. If not, see . 17 | 18 | import unittest 19 | import random 20 | import math 21 | try: 22 | from math import gcd # new in Python 3.5 23 | except ImportError: 24 | from fractions import gcd # deprecated since Python 3.5 25 | 26 | from phe import util 27 | 28 | 29 | class PaillierUtilTest(unittest.TestCase): 30 | 31 | def testPowMod(self): 32 | self.assertEqual(util.powmod(5, 3, 3), 2) 33 | self.assertEqual(util.powmod(2, 10, 1000), 24) 34 | 35 | def testInvert(self): 36 | p = 101 37 | for i in range(1, p): 38 | iinv = util.invert(i, p) 39 | self.assertEqual((iinv * i) % p, 1) 40 | 41 | def testInvertNonPrime(self): 42 | a = 3 43 | p = 4 44 | self.assertEqual(a * util.invert(a, p) % p, 1) 45 | 46 | def testPrimeOverN(self): 47 | self.assertIn(util.getprimeover(3), {5, 7, 11, 13}) 48 | # Test we get at least a N-bit prime 49 | for n in range(2, 50): 50 | p = util.getprimeover(n) 51 | self.assertGreaterEqual(p, 1 << (n-1)) 52 | 53 | def testIsqrt(self): 54 | for _ in range(100): 55 | n = random.randint(2, 10000000) 56 | nsq = n*n 57 | self.assertEqual(int(math.floor(math.sqrt(n))), util.isqrt(n)) 58 | self.assertEqual(util.isqrt(nsq), util.improved_i_sqrt(nsq)) 59 | 60 | 61 | # same tests as above, but with gmpy2 and Crypto libraries disabled 62 | class PaillierUtilFallbacksTest(PaillierUtilTest): 63 | 64 | def setUp(self): 65 | # save presence of libraries 66 | self.HAVE_GMP = util.HAVE_GMP 67 | self.HAVE_CRYPTO = util.HAVE_CRYPTO 68 | # disable libraties 69 | util.HAVE_GMP = False 70 | util.HAVE_CRYPTO = False 71 | 72 | def tearDown(self): 73 | # restore presence of libraries 74 | util.HAVE_GMP = self.HAVE_GMP 75 | util.HAVE_CRYPTO = self.HAVE_CRYPTO 76 | 77 | def testExtendedEuclieanAlgorithm(self): 78 | # from 79 | self.assertEqual(util.extended_euclidean_algorithm(240, 46), (2, -9, 47)) 80 | 81 | # tests with arbirary values 82 | for a, b in [(77, 99), (45, 127)]: # non-coprime pair, coprime pair 83 | r, s, t = util.extended_euclidean_algorithm(a, b) 84 | self.assertEqual(r, s*a + t*b) 85 | self.assertEqual(r, gcd(a, b)) 86 | 87 | def testMillerRabin(self): 88 | a = 2 # witness, enough by itself for checking n < 2047 89 | self.assertFalse(util.miller_rabin(4, a)) 90 | self.assertTrue(util.miller_rabin(127, a)) 91 | composite = util.first_primes[-1] * util.first_primes[-2] 92 | self.assertFalse(util.miller_rabin(composite, a)) 93 | 94 | def testIsPrime(self): 95 | self.assertTrue(util.is_prime(17881)) # first not in first_primes 96 | self.assertFalse(util.is_prime(-17881)) 97 | 98 | self.assertFalse(util.is_prime(-4)) 99 | self.assertFalse(util.is_prime(-2)) 100 | self.assertFalse(util.is_prime(-1)) 101 | self.assertFalse(util.is_prime(0)) 102 | self.assertFalse(util.is_prime(1)) 103 | self.assertTrue(util.is_prime(2)) 104 | self.assertTrue(util.is_prime(3)) 105 | 106 | # same tests as for miller_rabin() 107 | self.assertFalse(util.is_prime(4)) 108 | self.assertTrue(util.is_prime(127)) 109 | composite = util.first_primes[-1] * util.first_primes[-2] 110 | self.assertFalse(util.is_prime(composite)) 111 | 112 | 113 | class Base64UtilTest(unittest.TestCase): 114 | 115 | def testEncodeDecodePositiveNonZeroInt(self): 116 | for a in range(1, 1000000, 100): 117 | 118 | self.assertEqual(a, util.base64_to_int(util.int_to_base64(a))) 119 | 120 | def testFailToEncodeZero(self): 121 | with self.assertRaises(AssertionError): 122 | util.int_to_base64(0) 123 | 124 | 125 | if __name__ == "__main__": 126 | unittest.main() 127 | -------------------------------------------------------------------------------- /phe/util.py: -------------------------------------------------------------------------------- 1 | # This file is part of pyphe. 2 | # 3 | # pyphe is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # pyphe is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with pyphe. If not, see . 15 | 16 | import os 17 | import random 18 | from base64 import urlsafe_b64encode, urlsafe_b64decode 19 | from binascii import hexlify, unhexlify 20 | 21 | try: 22 | import gmpy2 23 | HAVE_GMP = True 24 | except ImportError: 25 | HAVE_GMP = False 26 | 27 | try: 28 | from Crypto.Util import number 29 | HAVE_CRYPTO = True 30 | except ImportError: 31 | HAVE_CRYPTO = False 32 | 33 | # GMP's powmod has greater overhead than Python's pow, but is faster. 34 | # From a quick experiment on our machine, this seems to be the break even: 35 | _USE_MOD_FROM_GMP_SIZE = (1 << (8*2)) 36 | _USE_MULMOD_FROM_GMP_SIZE = (1 << 1000) # pow(2, 1000) 37 | 38 | def powmod(a, b, c): 39 | """ 40 | Uses GMP, if available, to do a^b mod c where a, b, c 41 | are integers. 42 | 43 | :return int: (a ** b) % c 44 | """ 45 | if a == 1: 46 | return 1 47 | if not HAVE_GMP or max(a, b, c) < _USE_MOD_FROM_GMP_SIZE: 48 | return pow(a, b, c) 49 | else: 50 | return int(gmpy2.powmod(a, b, c)) 51 | 52 | 53 | def mulmod(a, b, c): 54 | """ 55 | Uses GMP, if available, to do a * b mod c, where a, b, c 56 | are integers. 57 | 58 | :return int: (a * b) % c 59 | """ 60 | if not HAVE_GMP or max(a, b, c) < _USE_MULMOD_FROM_GMP_SIZE: 61 | return a * b % c 62 | else: 63 | a, b, c = gmpy2.mpz(a), gmpy2.mpz(b), gmpy2.mpz(c) 64 | return int(gmpy2.mod(gmpy2.mul(a, b), c)) 65 | 66 | 67 | def extended_euclidean_algorithm(a, b): 68 | """Extended Euclidean algorithm 69 | 70 | Returns r, s, t such that r = s*a + t*b and r is gcd(a, b) 71 | 72 | See 73 | """ 74 | r0, r1 = a, b 75 | s0, s1 = 1, 0 76 | t0, t1 = 0, 1 77 | while r1 != 0: 78 | q = r0 // r1 79 | r0, r1 = r1, r0 - q*r1 80 | s0, s1 = s1, s0 - q*s1 81 | t0, t1 = t1, t0 - q*t1 82 | return r0, s0, t0 83 | 84 | 85 | def invert(a, b): 86 | """ 87 | The multiplicitive inverse of a in the integers modulo b. 88 | 89 | :return int: x, where a * x == 1 mod b 90 | """ 91 | if HAVE_GMP: 92 | s = int(gmpy2.invert(a, b)) 93 | # according to documentation, gmpy2.invert might return 0 on 94 | # non-invertible element, although it seems to actually raise an 95 | # exception; for consistency, we always raise the exception 96 | if s == 0: 97 | raise ZeroDivisionError('invert() no inverse exists') 98 | return s 99 | else: 100 | r, s, _ = extended_euclidean_algorithm(a, b) 101 | if r != 1: 102 | raise ZeroDivisionError('invert() no inverse exists') 103 | return s % b 104 | 105 | 106 | def getprimeover(N): 107 | """Return a random N-bit prime number using the System's best 108 | Cryptographic random source. 109 | 110 | Use GMP if available, otherwise fallback to PyCrypto 111 | """ 112 | if HAVE_GMP: 113 | randfunc = random.SystemRandom() 114 | r = gmpy2.mpz(randfunc.getrandbits(N)) 115 | r = gmpy2.bit_set(r, N - 1) 116 | return int(gmpy2.next_prime(r)) 117 | elif HAVE_CRYPTO: 118 | return number.getPrime(N, os.urandom) 119 | else: 120 | randfunc = random.SystemRandom() 121 | n = randfunc.randrange(2**(N-1), 2**N) | 1 122 | while not is_prime(n): 123 | n += 2 124 | return n 125 | 126 | 127 | def isqrt(N): 128 | """ returns the integer square root of N """ 129 | if HAVE_GMP: 130 | return int(gmpy2.isqrt(N)) 131 | else: 132 | return improved_i_sqrt(N) 133 | 134 | 135 | def improved_i_sqrt(n): 136 | """ taken from 137 | http://stackoverflow.com/questions/15390807/integer-square-root-in-python 138 | Thanks, mathmandan """ 139 | assert n >= 0 140 | if n == 0: 141 | return 0 142 | i = n.bit_length() >> 1 # i = floor( (1 + floor(log_2(n))) / 2 ) 143 | m = 1 << i # m = 2^i 144 | # 145 | # Fact: (2^(i + 1))^2 > n, so m has at least as many bits 146 | # as the floor of the square root of n. 147 | # 148 | # Proof: (2^(i+1))^2 = 2^(2i + 2) >= 2^(floor(log_2(n)) + 2) 149 | # >= 2^(ceil(log_2(n) + 1) >= 2^(log_2(n) + 1) > 2^(log_2(n)) = n. QED. 150 | # 151 | while (m << i) > n: # (m<>= 1 153 | i -= 1 154 | d = n - (m << i) # d = n-m^2 155 | for k in range(i-1, -1, -1): 156 | j = 1 << k 157 | new_diff = d - (((m<<1) | j) << k) # n-(m+2^k)^2 = n-m^2-2*m*2^k-2^(2k) 158 | if new_diff >= 0: 159 | d = new_diff 160 | m |= j 161 | return m 162 | 163 | # base64 utils from jwcrypto 164 | 165 | def base64url_encode(payload): 166 | if not isinstance(payload, bytes): 167 | payload = payload.encode('utf-8') 168 | encode = urlsafe_b64encode(payload) 169 | return encode.decode('utf-8').rstrip('=') 170 | 171 | 172 | def base64url_decode(payload): 173 | l = len(payload) % 4 174 | if l == 2: 175 | payload += '==' 176 | elif l == 3: 177 | payload += '=' 178 | elif l != 0: 179 | raise ValueError('Invalid base64 string') 180 | return urlsafe_b64decode(payload.encode('utf-8')) 181 | 182 | 183 | def base64_to_int(source): 184 | return int(hexlify(base64url_decode(source)), 16) 185 | 186 | 187 | def int_to_base64(source): 188 | assert source != 0 189 | I = hex(source).rstrip("L").lstrip("0x") 190 | return base64url_encode(unhexlify((len(I) % 2) * '0' + I)) 191 | 192 | 193 | # prime testing 194 | 195 | first_primes = [ 196 | 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 197 | 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 198 | 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 199 | 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 200 | 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 201 | 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 202 | 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 203 | 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 204 | 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 205 | 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 206 | 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 207 | 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 208 | 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 209 | 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 210 | 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 211 | 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 212 | 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 213 | 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 214 | 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 215 | 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 216 | 1801, 1811, 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 217 | 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, 218 | 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 219 | 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 220 | 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 221 | 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 222 | 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 223 | 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 224 | 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 225 | 2671, 2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 226 | 2731, 2741, 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, 227 | 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, 2909, 2917, 228 | 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, 229 | 3037, 3041, 3049, 3061, 3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, 230 | 3163, 3167, 3169, 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 231 | 3253, 3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, 232 | 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, 233 | 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 234 | 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 235 | 3623, 3631, 3637, 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 236 | 3719, 3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, 237 | 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911, 3917, 238 | 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989, 4001, 4003, 4007, 4013, 239 | 4019, 4021, 4027, 4049, 4051, 4057, 4073, 4079, 4091, 4093, 4099, 4111, 240 | 4127, 4129, 4133, 4139, 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 241 | 4229, 4231, 4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297, 242 | 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, 4421, 4423, 243 | 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, 244 | 4523, 4547, 4549, 4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, 4639, 245 | 4643, 4649, 4651, 4657, 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 246 | 4733, 4751, 4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831, 247 | 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937, 4943, 4951, 248 | 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, 5009, 5011, 5021, 5023, 249 | 5039, 5051, 5059, 5077, 5081, 5087, 5099, 5101, 5107, 5113, 5119, 5147, 250 | 5153, 5167, 5171, 5179, 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 251 | 5273, 5279, 5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, 252 | 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443, 5449, 5471, 253 | 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, 5563, 254 | 5569, 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 5653, 5657, 5659, 255 | 5669, 5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 256 | 5783, 5791, 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857, 257 | 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, 5953, 5981, 258 | 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, 259 | 6091, 6101, 6113, 6121, 6131, 6133, 6143, 6151, 6163, 6173, 6197, 6199, 260 | 6203, 6211, 6217, 6221, 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 261 | 6299, 6301, 6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367, 262 | 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473, 6481, 6491, 263 | 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, 264 | 6619, 6637, 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, 6703, 6709, 265 | 6719, 6733, 6737, 6761, 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 266 | 6829, 6833, 6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917, 267 | 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, 7001, 7013, 268 | 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, 7121, 7127, 7129, 269 | 7151, 7159, 7177, 7187, 7193, 7207, 7211, 7213, 7219, 7229, 7237, 7243, 270 | 7247, 7253, 7283, 7297, 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 271 | 7393, 7411, 7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499, 272 | 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561, 7573, 7577, 273 | 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, 274 | 7687, 7691, 7699, 7703, 7717, 7723, 7727, 7741, 7753, 7757, 7759, 7789, 275 | 7793, 7817, 7823, 7829, 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 276 | 7907, 7919, 7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017, 277 | 8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111, 8117, 8123, 278 | 8147, 8161, 8167, 8171, 8179, 8191, 8209, 8219, 8221, 8231, 8233, 8237, 279 | 8243, 8263, 8269, 8273, 8287, 8291, 8293, 8297, 8311, 8317, 8329, 8353, 280 | 8363, 8369, 8377, 8387, 8389, 8419, 8423, 8429, 8431, 8443, 8447, 8461, 281 | 8467, 8501, 8513, 8521, 8527, 8537, 8539, 8543, 8563, 8573, 8581, 8597, 282 | 8599, 8609, 8623, 8627, 8629, 8641, 8647, 8663, 8669, 8677, 8681, 8689, 283 | 8693, 8699, 8707, 8713, 8719, 8731, 8737, 8741, 8747, 8753, 8761, 8779, 284 | 8783, 8803, 8807, 8819, 8821, 8831, 8837, 8839, 8849, 8861, 8863, 8867, 285 | 8887, 8893, 8923, 8929, 8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 286 | 9007, 9011, 9013, 9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109, 287 | 9127, 9133, 9137, 9151, 9157, 9161, 9173, 9181, 9187, 9199, 9203, 9209, 288 | 9221, 9227, 9239, 9241, 9257, 9277, 9281, 9283, 9293, 9311, 9319, 9323, 289 | 9337, 9341, 9343, 9349, 9371, 9377, 9391, 9397, 9403, 9413, 9419, 9421, 290 | 9431, 9433, 9437, 9439, 9461, 9463, 9467, 9473, 9479, 9491, 9497, 9511, 291 | 9521, 9533, 9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, 9631, 292 | 9643, 9649, 9661, 9677, 9679, 9689, 9697, 9719, 9721, 9733, 9739, 9743, 293 | 9749, 9767, 9769, 9781, 9787, 9791, 9803, 9811, 9817, 9829, 9833, 9839, 294 | 9851, 9857, 9859, 9871, 9883, 9887, 9901, 9907, 9923, 9929, 9931, 9941, 295 | 9949, 9967, 9973, 10007, 10009, 10037, 10039, 10061, 10067, 10069, 10079, 296 | 10091, 10093, 10099, 10103, 10111, 10133, 10139, 10141, 10151, 10159, 297 | 10163, 10169, 10177, 10181, 10193, 10211, 10223, 10243, 10247, 10253, 298 | 10259, 10267, 10271, 10273, 10289, 10301, 10303, 10313, 10321, 10331, 299 | 10333, 10337, 10343, 10357, 10369, 10391, 10399, 10427, 10429, 10433, 300 | 10453, 10457, 10459, 10463, 10477, 10487, 10499, 10501, 10513, 10529, 301 | 10531, 10559, 10567, 10589, 10597, 10601, 10607, 10613, 10627, 10631, 302 | 10639, 10651, 10657, 10663, 10667, 10687, 10691, 10709, 10711, 10723, 303 | 10729, 10733, 10739, 10753, 10771, 10781, 10789, 10799, 10831, 10837, 304 | 10847, 10853, 10859, 10861, 10867, 10883, 10889, 10891, 10903, 10909, 305 | 10937, 10939, 10949, 10957, 10973, 10979, 10987, 10993, 11003, 11027, 306 | 11047, 11057, 11059, 11069, 11071, 11083, 11087, 11093, 11113, 11117, 307 | 11119, 11131, 11149, 11159, 11161, 11171, 11173, 11177, 11197, 11213, 308 | 11239, 11243, 11251, 11257, 11261, 11273, 11279, 11287, 11299, 11311, 309 | 11317, 11321, 11329, 11351, 11353, 11369, 11383, 11393, 11399, 11411, 310 | 11423, 11437, 11443, 11447, 11467, 11471, 11483, 11489, 11491, 11497, 311 | 11503, 11519, 11527, 11549, 11551, 11579, 11587, 11593, 11597, 11617, 312 | 11621, 11633, 11657, 11677, 11681, 11689, 11699, 11701, 11717, 11719, 313 | 11731, 11743, 11777, 11779, 11783, 11789, 11801, 11807, 11813, 11821, 314 | 11827, 11831, 11833, 11839, 11863, 11867, 11887, 11897, 11903, 11909, 315 | 11923, 11927, 11933, 11939, 11941, 11953, 11959, 11969, 11971, 11981, 316 | 11987, 12007, 12011, 12037, 12041, 12043, 12049, 12071, 12073, 12097, 317 | 12101, 12107, 12109, 12113, 12119, 12143, 12149, 12157, 12161, 12163, 318 | 12197, 12203, 12211, 12227, 12239, 12241, 12251, 12253, 12263, 12269, 319 | 12277, 12281, 12289, 12301, 12323, 12329, 12343, 12347, 12373, 12377, 320 | 12379, 12391, 12401, 12409, 12413, 12421, 12433, 12437, 12451, 12457, 321 | 12473, 12479, 12487, 12491, 12497, 12503, 12511, 12517, 12527, 12539, 322 | 12541, 12547, 12553, 12569, 12577, 12583, 12589, 12601, 12611, 12613, 323 | 12619, 12637, 12641, 12647, 12653, 12659, 12671, 12689, 12697, 12703, 324 | 12713, 12721, 12739, 12743, 12757, 12763, 12781, 12791, 12799, 12809, 325 | 12821, 12823, 12829, 12841, 12853, 12889, 12893, 12899, 12907, 12911, 326 | 12917, 12919, 12923, 12941, 12953, 12959, 12967, 12973, 12979, 12983, 327 | 13001, 13003, 13007, 13009, 13033, 13037, 13043, 13049, 13063, 13093, 328 | 13099, 13103, 13109, 13121, 13127, 13147, 13151, 13159, 13163, 13171, 329 | 13177, 13183, 13187, 13217, 13219, 13229, 13241, 13249, 13259, 13267, 330 | 13291, 13297, 13309, 13313, 13327, 13331, 13337, 13339, 13367, 13381, 331 | 13397, 13399, 13411, 13417, 13421, 13441, 13451, 13457, 13463, 13469, 332 | 13477, 13487, 13499, 13513, 13523, 13537, 13553, 13567, 13577, 13591, 333 | 13597, 13613, 13619, 13627, 13633, 13649, 13669, 13679, 13681, 13687, 334 | 13691, 13693, 13697, 13709, 13711, 13721, 13723, 13729, 13751, 13757, 335 | 13759, 13763, 13781, 13789, 13799, 13807, 13829, 13831, 13841, 13859, 336 | 13873, 13877, 13879, 13883, 13901, 13903, 13907, 13913, 13921, 13931, 337 | 13933, 13963, 13967, 13997, 13999, 14009, 14011, 14029, 14033, 14051, 338 | 14057, 14071, 14081, 14083, 14087, 14107, 14143, 14149, 14153, 14159, 339 | 14173, 14177, 14197, 14207, 14221, 14243, 14249, 14251, 14281, 14293, 340 | 14303, 14321, 14323, 14327, 14341, 14347, 14369, 14387, 14389, 14401, 341 | 14407, 14411, 14419, 14423, 14431, 14437, 14447, 14449, 14461, 14479, 342 | 14489, 14503, 14519, 14533, 14537, 14543, 14549, 14551, 14557, 14561, 343 | 14563, 14591, 14593, 14621, 14627, 14629, 14633, 14639, 14653, 14657, 344 | 14669, 14683, 14699, 14713, 14717, 14723, 14731, 14737, 14741, 14747, 345 | 14753, 14759, 14767, 14771, 14779, 14783, 14797, 14813, 14821, 14827, 346 | 14831, 14843, 14851, 14867, 14869, 14879, 14887, 14891, 14897, 14923, 347 | 14929, 14939, 14947, 14951, 14957, 14969, 14983, 15013, 15017, 15031, 348 | 15053, 15061, 15073, 15077, 15083, 15091, 15101, 15107, 15121, 15131, 349 | 15137, 15139, 15149, 15161, 15173, 15187, 15193, 15199, 15217, 15227, 350 | 15233, 15241, 15259, 15263, 15269, 15271, 15277, 15287, 15289, 15299, 351 | 15307, 15313, 15319, 15329, 15331, 15349, 15359, 15361, 15373, 15377, 352 | 15383, 15391, 15401, 15413, 15427, 15439, 15443, 15451, 15461, 15467, 353 | 15473, 15493, 15497, 15511, 15527, 15541, 15551, 15559, 15569, 15581, 354 | 15583, 15601, 15607, 15619, 15629, 15641, 15643, 15647, 15649, 15661, 355 | 15667, 15671, 15679, 15683, 15727, 15731, 15733, 15737, 15739, 15749, 356 | 15761, 15767, 15773, 15787, 15791, 15797, 15803, 15809, 15817, 15823, 357 | 15859, 15877, 15881, 15887, 15889, 15901, 15907, 15913, 15919, 15923, 358 | 15937, 15959, 15971, 15973, 15991, 16001, 16007, 16033, 16057, 16061, 359 | 16063, 16067, 16069, 16073, 16087, 16091, 16097, 16103, 16111, 16127, 360 | 16139, 16141, 16183, 16187, 16189, 16193, 16217, 16223, 16229, 16231, 361 | 16249, 16253, 16267, 16273, 16301, 16319, 16333, 16339, 16349, 16361, 362 | 16363, 16369, 16381, 16411, 16417, 16421, 16427, 16433, 16447, 16451, 363 | 16453, 16477, 16481, 16487, 16493, 16519, 16529, 16547, 16553, 16561, 364 | 16567, 16573, 16603, 16607, 16619, 16631, 16633, 16649, 16651, 16657, 365 | 16661, 16673, 16691, 16693, 16699, 16703, 16729, 16741, 16747, 16759, 366 | 16763, 16787, 16811, 16823, 16829, 16831, 16843, 16871, 16879, 16883, 367 | 16889, 16901, 16903, 16921, 16927, 16931, 16937, 16943, 16963, 16979, 368 | 16981, 16987, 16993, 17011, 17021, 17027, 17029, 17033, 17041, 17047, 369 | 17053, 17077, 17093, 17099, 17107, 17117, 17123, 17137, 17159, 17167, 370 | 17183, 17189, 17191, 17203, 17207, 17209, 17231, 17239, 17257, 17291, 371 | 17293, 17299, 17317, 17321, 17327, 17333, 17341, 17351, 17359, 17377, 372 | 17383, 17387, 17389, 17393, 17401, 17417, 17419, 17431, 17443, 17449, 373 | 17467, 17471, 17477, 17483, 17489, 17491, 17497, 17509, 17519, 17539, 374 | 17551, 17569, 17573, 17579, 17581, 17597, 17599, 17609, 17623, 17627, 375 | 17657, 17659, 17669, 17681, 17683, 17707, 17713, 17729, 17737, 17747, 376 | 17749, 17761, 17783, 17789, 17791, 17807, 17827, 17837, 17839, 17851, 377 | 17863, 378 | ] 379 | 380 | 381 | def miller_rabin(n, k): 382 | """Run the Miller-Rabin test on n with at most k iterations 383 | 384 | Arguments: 385 | n (int): number whose primality is to be tested 386 | k (int): maximum number of iterations to run 387 | 388 | Returns: 389 | bool: If n is prime, then True is returned. Otherwise, False is 390 | returned, except with probability less than 4**-k. 391 | 392 | See 393 | """ 394 | assert n > 3 395 | 396 | # find r and d such that n-1 = 2^r × d 397 | d = n-1 398 | r = 0 399 | while d % 2 == 0: 400 | d //= 2 401 | r += 1 402 | assert n-1 == d * 2**r 403 | assert d % 2 == 1 404 | 405 | for _ in range(k): # each iteration divides risk of false prime by 4 406 | a = random.randint(2, n-2) # choose a random witness 407 | 408 | x = pow(a, d, n) 409 | if x == 1 or x == n-1: 410 | continue # go to next witness 411 | 412 | for _ in range(1, r): 413 | x = x*x % n 414 | if x == n-1: 415 | break # go to next witness 416 | else: 417 | return False 418 | return True 419 | 420 | 421 | def is_prime(n, mr_rounds=25): 422 | """Test whether n is probably prime 423 | 424 | See 425 | 426 | Arguments: 427 | n (int): the number to be tested 428 | mr_rounds (int, optional): number of Miller-Rabin iterations to run; 429 | defaults to 25 iterations, which is what the GMP library uses 430 | 431 | Returns: 432 | bool: when this function returns False, `n` is composite (not prime); 433 | when it returns True, `n` is prime with overwhelming probability 434 | """ 435 | # as an optimization we quickly detect small primes using the list above 436 | if n <= first_primes[-1]: 437 | return n in first_primes 438 | # for small dividors (relatively frequent), euclidean division is best 439 | for p in first_primes: 440 | if n % p == 0: 441 | return False 442 | # the actual generic test; give a false prime with probability 2⁻⁵⁰ 443 | return miller_rabin(n, mr_rounds) 444 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pycrypto>=2.6.1 2 | gmpy2>=2.0.4 3 | numpy>=1.9.1 4 | nose>=1.3.4 5 | click>=6.7 -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.rst 3 | 4 | [bdist_wheel] 5 | universal=1 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | # This file is part of python-paillier. 3 | # 4 | # python-paillier is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # python-paillier is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with python-paillier. If not, see . 16 | 17 | import os 18 | from setuptools import setup, find_packages 19 | 20 | here = os.path.abspath(os.path.dirname(__file__)) 21 | 22 | about = {} 23 | with open(os.path.join(here, "phe", "__about__.py")) as f: 24 | exec(f.read(), about) 25 | 26 | 27 | setup( 28 | name=about['__title__'], 29 | version=about['__version__'], 30 | description=about['__summary__'], 31 | long_description=open(os.path.join(here, "README.rst")).read(), 32 | url=about['__uri__'], 33 | download_url="https://pypi.python.org/pypi/phe/#downloads", 34 | author=about['__author__'], 35 | author_email=about['__email__'], 36 | license=about['__license__'], 37 | classifiers=[ 38 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 39 | 'Natural Language :: English', 40 | 'Topic :: Scientific/Engineering :: Mathematics', 41 | 'Topic :: Security', 42 | 'Topic :: Security :: Cryptography', 43 | 'Intended Audience :: Science/Research', 44 | 'Programming Language :: Python :: 3', 45 | 'Programming Language :: Python :: 3.7', 46 | 'Programming Language :: Python :: 3.8', 47 | 'Programming Language :: Python :: 3.9', 48 | 'Programming Language :: Python :: 3.10', 49 | 'Programming Language :: Python :: 3.11', 50 | ], 51 | keywords="cryptography encryption homomorphic", 52 | packages=find_packages(exclude=['tests*']), 53 | entry_points={ 54 | 'console_scripts': [ 55 | 'pheutil = phe.command_line:cli [cli]' 56 | ], 57 | }, 58 | extras_require={ 59 | 'cli': ['click'], 60 | 'examples': ['numpy', 'scipy', 'sklearn'] 61 | }, 62 | install_requires=[], 63 | tests_require=['click', 'gmpy2', 'numpy'], 64 | test_suite="phe.tests" 65 | ) 66 | -------------------------------------------------------------------------------- /third_party/gmpy2/README: -------------------------------------------------------------------------------- 1 | Name: General Multiprecision Python 2 | Short Name: gmpy 3 | URL: https://code.google.com/p/gmpy/ 4 | Source Control: https://code.google.com/p/gmpy/ 5 | Version: 2.0.3 6 | License: GNU Lesser GPL -------------------------------------------------------------------------------- /third_party/nose/README: -------------------------------------------------------------------------------- 1 | Name: Nose 2 | Short Name: none 3 | URL: http://readthedocs.org/docs/nose/ 4 | Source Control: https://github.com/nose-devs/nose 5 | Version: 1.3.4 6 | License: GNU Lesser General Public License (LGPL) -------------------------------------------------------------------------------- /third_party/numpy/README: -------------------------------------------------------------------------------- 1 | Name: NumPy 2 | Short Name: numpy 3 | URL: http://numpy.org 4 | Source Control: 5 | Version: 1.9.0 6 | License: BSD License -------------------------------------------------------------------------------- /third_party/numpy/license.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2005-2013, NumPy Developers. 2 | All rights reserved. 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 7 | Neither the name of the NumPy Developers nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. 8 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /third_party/pycrypto/COPYRIGHT.txt: -------------------------------------------------------------------------------- 1 | Copyright and licensing of the Python Cryptography Toolkit ("PyCrypto"): 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | Previously, the copyright and/or licensing status of the Python 5 | Cryptography Toolkit ("PyCrypto") had been somewhat ambiguous. The 6 | original intention of Andrew M. Kuchling and other contributors has 7 | been to dedicate PyCrypto to the public domain, but that intention was 8 | not necessarily made clear in the original disclaimer (see 9 | LEGAL/copy/LICENSE.orig). 10 | 11 | Additionally, some files within PyCrypto had specified their own 12 | licenses that differed from the PyCrypto license itself. For example, 13 | the original RIPEMD.c module simply had a copyright statement and 14 | warranty disclaimer, without clearly specifying any license terms. 15 | (An updated version on the author's website came with a license that 16 | contained a GPL-incompatible advertising clause.) 17 | 18 | To rectify this situation for PyCrypto 2.1, the following steps have 19 | been taken: 20 | 21 | 1. Obtaining explicit permission from the original contributors to 22 | dedicate their contributions to the public domain if they have not 23 | already done so. (See the "LEGAL/copy/stmts" directory for 24 | contributors' statements.) 25 | 26 | 2. Replacing some modules with clearly-licensed code from other 27 | sources (e.g. the DES and DES3 modules were replaced with new ones 28 | based on Tom St. Denis's public-domain LibTomCrypt library.) 29 | 30 | 3. Replacing some modules with code written from scratch (e.g. the 31 | RIPEMD and Blowfish modules were re-implemented from their 32 | respective algorithm specifications without reference to the old 33 | implementations). 34 | 35 | 4. Removing some modules altogether without replacing them. 36 | 37 | To the best of our knowledge, with the exceptions noted below or 38 | within the files themselves, the files that constitute PyCrypto are in 39 | the public domain. Most are distributed with the following notice: 40 | 41 | The contents of this file are dedicated to the public domain. To 42 | the extent that dedication to the public domain is not available, 43 | everyone is granted a worldwide, perpetual, royalty-free, 44 | non-exclusive license to exercise all rights associated with the 45 | contents of this file for any purpose whatsoever. 46 | No rights are reserved. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 49 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 50 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 51 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 52 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 53 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 54 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 55 | SOFTWARE. 56 | 57 | Exceptions: 58 | 59 | - Portions of HMAC.py and setup.py are derived from Python 2.2, and 60 | are therefore Copyright (c) 2001, 2002, 2003 Python Software 61 | Foundation (All Rights Reserved). They are licensed by the PSF 62 | under the terms of the Python 2.2 license. (See the file 63 | LEGAL/copy/LICENSE.python-2.2 for details.) 64 | 65 | - The various GNU autotools (autoconf, automake, aclocal, etc.) are 66 | used during the build process. This includes macros from 67 | autoconf-archive, which are located in the m4/ directory. As is 68 | customary, some files from the GNU autotools are included in the 69 | source tree (in the root directory, and in the build-aux/ 70 | directory). These files are merely part of the build process, and 71 | are not included in binary builds of the software. 72 | 73 | EXPORT RESTRICTIONS: 74 | 75 | Note that the export or re-export of cryptographic software and/or 76 | source code may be subject to regulation in your jurisdiction. 77 | 78 | -------------------------------------------------------------------------------- /third_party/pycrypto/README: -------------------------------------------------------------------------------- 1 | Name: The Python Cryptography Toolkit 2 | Short Name: pycrypto 3 | URL: http://www.pycrypto.org/ 4 | Source Control: https://github.com/dlitz/pycrypto 5 | Version: 2.6.1 6 | License: Public Domain -------------------------------------------------------------------------------- /third_party/sphinx/README: -------------------------------------------------------------------------------- 1 | Name: Sphinx 2 | Short Name: sphinx 3 | URL: http://sphinx-doc.org 4 | Version: 1.2.3 5 | License: BSD License --------------------------------------------------------------------------------