Share @ Google LinkedIn Facebook  cryptography, paillier, homomorphic-encryption

Overview

Paillier is a public key homomorphic encryption scheme. Python library paillier provides implementation of paillier cryptosystem.

Below are list of homomorphic properties :

  • Encrypted numbers can be multiplied by non-encrypted scalar number.
  • Two encrypted numbers can be added.
  • Non-encrypted scalar can be added to Encrypted numbers.

Installation:

Paillier does not come with default python package or conda package that's why we'll need to install it.

  • pip install phe
In [1]:
!pip install --upgrade pip
!pip install phe
Collecting pip
  Downloading https://files.pythonhosted.org/packages/d7/41/34dd96bd33958e52cb4da2f1bf0818e396514fd4f4725a79199564cd0c20/pip-19.0.2-py2.py3-none-any.whl (1.4MB)
    100% |████████████████████████████████| 1.4MB 13.6MB/s
Installing collected packages: pip
  Found existing installation: pip 18.1
    Uninstalling pip-18.1:
      Successfully uninstalled pip-18.1
Successfully installed pip-19.0.2
Collecting phe
  Downloading https://files.pythonhosted.org/packages/32/0e/568e97b014eb14e794a1258a341361e9da351dc6240c63b89e1541e3341c/phe-1.4.0.tar.gz
Building wheels for collected packages: phe
  Building wheel for phe (setup.py) ... - done
  Stored in directory: /tmp/.cache/pip/wheels/f8/dc/36/dcb6bf0f1b9907e7b710ace63e64d08e7022340909315fdea4
Successfully built phe
Installing collected packages: phe
Successfully installed phe-1.4.0
In [2]:
import phe
from phe import paillier
import json
  • paillier.generate_paillier_keypair(n_length=2048) - It generates public/private key pair of default length 2048 bits. Developer can change this length according their need. It takes bit time to generate intial pairs.
In [3]:
%%time
pub_key,priv_key = paillier.generate_paillier_keypair() ## Generating public/private key pair
## %%time is magic command to find out time taken to execute this cell
CPU times: user 100 ms, sys: 0 ns, total: 100 ms
Wall time: 99.2 ms
  • public_key.encrypt(value, precision=None, r_value=None) - It's used to encrypt value with precision provided as precision for float numbers. User can supply random value to be used during encryption as r_value. Returns object of class EncryptedNumber.
  • private_key.decrypt() - It's used to decrypt encrypted value.
In [4]:
enc1 = pub_key.encrypt(5)
enc2 = pub_key.encrypt(5.649)
enc3 = pub_key.encrypt(5.5397,precision=1e-2)
priv_key.decrypt(enc1), priv_key.decrypt(enc2), priv_key.decrypt(enc3)
Out[4]:
(5, 5.649, 5.5390625)

If developers are planning to use more that one public/private key sets then they can main list of private keys through keyring.

  • paillier.PaillierPrivateKeyring(private_keys=None) - Lets developer generate keyrin which will main list of private keys give as input to it. Developers can supply all private keys as list initially or can add later as well using add() method.

Benefit of using ring is that developer does not need to loop through all private keys to decrypt any encrypted number.

In [5]:
keyring = paillier.PaillierPrivateKeyring()
pub_keys = []
for i in range(5):
    pub,priv = paillier.generate_paillier_keypair()
    pub_keys.append(pub)
    keyring.add(priv)
enc1= pub_keys[0].encrypt(5.5)
enc2= pub_keys[2].encrypt(13.6)
enc3= pub_keys[3].encrypt(3.14)
## Notice below keyring will findout right private key for decrypting number without developer manually keeping track of it..
keyring.decrypt(enc1), keyring.decrypt(enc2), keyring.decrypt(enc3)
Out[5]:
(5.5, 13.6, 3.14)

Homorphic Properties:

Below we'll verify homomorphic properties using various examples.

In [6]:
enc1 = pub_key.encrypt(5.5)
enc2 = pub_key.encrypt(8.3)
enc3 = pub_key.encrypt(12.6)
print(priv_key.decrypt(enc1))
enc1 = enc1 + 3.3
print(priv_key.decrypt(enc1))
enc1 = enc1 - 3.3
print(priv_key.decrypt(enc1))
enc4 = enc2 + enc3
print(priv_key.decrypt(enc4))
enc5 = enc3 - enc2
print(priv_key.decrypt(enc5))
enc6 = -5 + enc5
print(priv_key.decrypt(enc6))
5.5
8.8
5.5
20.9
4.299999999999999
-0.7000000000000011
In [7]:
enc1 = enc1 * 2.2
print(priv_key.decrypt(enc1))
enc1 = enc1 / 10
print(priv_key.decrypt(enc1))
enc7 = enc1 * -2
print(priv_key.decrypt(enc7))
enc8 = enc1 / -2
print(priv_key.decrypt(enc8))
12.100000000000001
1.2100000000000002
-2.4200000000000004
-0.6050000000000001

Serialisation of encrypted numbers to pass it over network/store on disk:

Below steps explain serialisation of encrypted number along with public key and then deserializing & reconstructing encrypted number and public key.

In [8]:
print(priv_key.decrypt(enc1))
enc_with_pub_key = {}
enc_with_pub_key['public_key'] = { 'g':pub_key.g, 'n':pub_key.n}
enc_with_pub_key['enc_value'] = (str(enc1.ciphertext()),enc1.exponent)
serialised = json.dumps(enc_with_pub_key)
1.2100000000000002
In [9]:
received_dict = json.loads(serialised)
pk = received_dict['public_key']
public_key_rec = paillier.PaillierPublicKey(n=int(pk['n']))
enc_nums_rec = paillier.EncryptedNumber(public_key_rec, int(received_dict['enc_value'][0]), int(received_dict['enc_value'][1]))
priv_key.decrypt(enc_nums_rec)
Out[9]:
1.2100000000000002

Security Caveats:

TBD

Other API functions:

TBD

Other Python libraries providing implementations:

TBD


Let other learners know about this article @ Google LinkedIn Facebook
Sunny Solanki  Sunny Solanki