Usage¶
There are two roles that use this library. In the first, you control the private keys. In the second, you don’t. This guide shows you how to play either role.
In either case, you of course begin by importing the library:
from phe import paillier
Role #1¶
This party holds the private keys and typically will generate the keys and do the decryption.
Key generation¶
First, you’re going to have to generate a public and private key pair:
>>> public_key, private_key = paillier.generate_paillier_keypair()
If you’re going to have lots of private keys lying around, then perhaps you should invest in
a keyring on which to store your PaillierPrivateKey
instances:
>>> keyring = paillier.PaillierPrivateKeyring()
>>> keyring.add(private_key)
>>> public_key1, private_key1 = paillier.generate_paillier_keypair(keyring)
>>> public_key2, private_key2 = paillier.generate_paillier_keypair(keyring)
In any event, you can then start encrypting numbers:
>>> secret_number_list = [3.141592653, 300, -4.6e-12]
>>> encrypted_number_list = [public_key.encrypt(x) for x in secret_number_list]
Presumably, you would now share the ciphertext with whoever is playing Role 2 (see Serialisation and Compatibility with other libraries).
Decryption¶
To decrypt an EncryptedNumber
, use the relevant
PaillierPrivateKey
:
>>> [private_key.decrypt(x) for x in encrypted_number_list]
[3.141592653, 300, -4.6e-12]
If you have multiple key pairs stored in a PaillierPrivateKeyring
,
then you don’t need to manually find the relevant PaillierPrivateKey
:
>>> [keyring.decrypt(x) for x in encrypted_number_list]
[3.141592653, 300, -4.6e-12]
Role #2¶
This party does not have access to the private keys, and typically performs operations on supplied encrypted data with their own, unencrypted data.
Once this party has received some EncryptedNumber
instances (e.g. see
Serialisation), it can perform basic mathematical operations supported by the Paillier
encryption:
- Addition of an
EncryptedNumber
to a scalar - Addition of two
EncryptedNumber
instances - Multiplication of an
EncryptedNumber
by a scalar
>>> a, b, c = encrypted_number_list
>>> a
<phe.paillier.EncryptedNumber at 0x7f60a28c90b8>
>>> a_plus_5 = a + 5
>>> a_plus_b = a + b
>>> a_times_3_5 = a * 3.5
as well as some simple extensions:
>>> a_minus_1_3 = a - 1 # = a + (-1)
>>> a_div_minus_3_1 = a / -3.1 # = a * (-1 / 3.1)
>>> a_minus_b = a - b # = a + (b * -1)
Numpy operations that rely only on these operations are allowed:
>>> import numpy as np
>>> enc_mean = np.mean(encrypted_number_list)
>>> enc_dot = np.dot(encrypted_number_list, [2, -400.1, 5318008])
Operations that aren’t supported by Paillier’s partially homomorphic scheme raise an error:
>>> a * b
NotImplementedError: Good luck with that...
>>> 1 / a
TypeError: unsupported operand type(s) for /: 'int' and 'EncryptedNumber'
Once the necessary computations have been done, this party would send the resulting
EncryptedNumber
instances back to the holder of the private keys for
decryption.
In some cases it might be possible to boost performance by reducing the precision of floating point numbers:
>>> a_times_3_5_lp = a * paillier.EncodedNumber.encode(a.public_key, 3.5, 1e-2)