Proxy Re-Encryption Playground in Python

Author profile picture


Proxy re-encryption is a set of algorithms which allows an untrusted proxy to transform ciphertext from being encrypted under one key to another, without learning anything about the underlying plaintext. Proxy re-encryption algorithms usually work as public-key encryption, in which a public-private key-pair is used to encrypt and decrypt the data, respectively.

As a class, proxy re-encryption is well-suited for use cases in which you want to share encrypted data with multiple parties. Rather than naively sharing your private key with recipients (insecure) or encrypting the entire message N times for each recipient, proxy re-encryption allows you to encrypt the data once and then delegate access to it based on the recipients’ public keys. This removes the requirement for the data owner to be online and also facilitates revocation of access.

Main actors and interactions in a PRE environment

Proxy re-encryption algorithms come in two primary flavors: interactive and non-interactive. Interactive versions delegate access to a private key, while non-interactive ones delegate access to a public key.

Today, we’re releasing an implementation of one of the simpler algorithms (and the first one invented) on our GitHub. It’s based on elliptic curves. For encryption and decryption, it’s similar to ElGamal operating on elliptic curve prime field and it’s an interactive algorithm that requires knowledge of the recipient’s private key.


The library is written in Python. It requires `libssl-dev` and `libgmp-dev`. It is a slightly refined version of the same algorithm in the charm crypto library, with a simplified installation process, thread safe, and more appropriate for practical uses.

To install in Linux (Debian, Ubuntu, Mint):

sudo apt-get install build-essential # Compilers etc
sudo apt-get install python3 # Written for Python3.x
sudo apt-get install python3-dev libssl-dev libgmp-dev # Dependencies to compile C extensions

On Mac:

brew install python3
brew install gmp

Now that we have the dependencies out of the way, you can install the `nucypher-pre-python` library. I’d recommend doing it in `virtualenv`.

git clone
cd nucypher-pre-python
pip3 install -e .

It’s also convenient to try it in an ipython terminal rather than standard python.

Now, let’s start playing with the library!

In [1]: from npre import bbs98

We need to initialize the re-encryption object:

In [2]: pre = bbs98.PRE()

In the BBS98 algorithm, there is a shared non-secret random number, which defines the parameters of the re-encryption algorithm. This number is generated and saved in the class instance, along with the curve name (`secp256k1` by default).

Both the sender and recipient must initialize using the same parameters. For that, we can serialize and de-serialize the `pre` object (de-serialization happens on a different machine). If you’re running this tutorial (and both the sender and recipient functions) locally, it’s not necessary:

In [3]: backup = pre.serialize()
In [4]: backup
Out[4]: b’\x82\xa5curve\xcd\x02\xca\xa1g\xda\x00.1:A8LQod5lJgbCnh3vQd+RGSe3qDrhw8n8Ju8uZ7dpzKB+’
In [5]: pre = bbs98.PRE.deserialize(backup)

Now, let’s generate key pairs `(sk, pk)` for Alice, the owner of the data, and Bob, the recipient:

In [6]: sk_a = pre.gen_priv(dtype=bytes)
In [7]: pk_a = pre.priv2pub(sk_a)
In [8]: sk_b = pre.gen_priv(dtype=bytes)
In [9]: pk_b = pre.priv2pub(sk_b)

Here `sk` means “secret key”, and `pk` means “public key”.

Let’s see what these keys actually are:

In [10]: sk_a
Out[10]: b’0:JKwy+rxGu+BvaZ0cKgV/alsfYfhZoVNm63CrCvL/faI=’
In [11]: pk_a
Out[11]: b’1:A5NZVIEGQgao/knbwDbR4cNaxHQwkJCRWUJDCm8vQCJe’
In [12]: sk_b
Out[12]: b’0:otOz8XATKx1rX+Ypp+/2NDw2ej7n+TUx8rBTKRm7KtI=’
In [13]: pk_b
Out[13]: b’1:A9gOmE39Kwuhpbafylfm3d+3y7FLRnupV8pcTYDg0xTl’

Now, let’s encrypt something:

In [14]: msg = b’Hello world’
In [15]: emsg = pre.encrypt(pk_a, msg)
In [16]: emsg
Out[16]: b’\x92\xda\x00.1:Aw3nUfn9bNTfCtG0KPkaugzDGKlN3a/Gq0MH3uaAMBkp\xda\x00.1:A3qkvzG47dt5xiODccILFPbLw8CaTZsPlUbJoKbegl/T’

Let’s see if we can decrypt this message:

In [17]: pre.decrypt(sk_a, emsg)
Out[17]: b’Hello world’

What if we try to decrypt using Bob’s key?

In [18]: pre.decrypt(sk_b, emsg)
Out[18]: b’\x94_\xf3W\xb5aX8'

It doesn’t work, of course!

Now, let’s transform data to be encrypted for Bob. For that, we generate a re-encryption key:

In [19]: re_ab = pre.rekey(sk_a, sk_b)

And transform the message. The transformation can be done by a remote proxy which knows neither `sk_a`, nor `sk_b`:

In [20]: emsg_b = pre.reencrypt(re_ab, emsg)
In [21]: emsg_b
Out[21]: b’\x92\xda\x00.1:AyafJONHL/ZgrkwKFzFQtEnr8Em5k9hxP/ICzAGCNm4I\xda\x00.1:A3qkvzG47dt5xiODccILFPbLw8CaTZsPlUbJoKbegl/T’

Can we decrypt the re-encrypted message?

In [22]: pre.decrypt(sk_b, emsg_b)
Out[22]: b’Hello world’

So, we re-encrypted a message originally encrypted for Alice to Bob. But to delegate access to Bob, Alice needed to know his secret key `sk_b`. Definitely not ideal! Is it possible to avoid this requirement and use Bob’s public key instead?

One solution to this would be to use a different proxy re-encryption algorithm, such as AFGH.

But there’s a way to do it with the current algorithm. When Alice delegates access to Bob, she can generate an ephemeral key `sk_e` and produce a re-encryption key `rk_ae`. Then she encrypts `sk_e` with Bob’s public key `pk_b` yielding `e_b`.

The proxy will be given both `rk_e` and `e_b`. When Bob connects, the proxy will hand him `e_b`. Then Bob can extract `pk_e` out of it and use that to decrypt encrypted messages coming from the proxy.

Feel free to play with the library, but be safe: this particular implementation hasn’t undergone a security audit and is currently for educational use only :-)


The Noonification banner

Subscribe to get your daily round-up of top tech stories!