├── .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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------