**UPDATE: Instead of the BBS98 library described below, you should use pyUmbral. Thanks to pyUmbral, this blog post is deprecated and should only be used for entertainment purposes.**

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.

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.

## Installation

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 https://github.com/nucypher/nucypher-pre-python.git

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_ae and e_b. When Bob connects, the proxy will hand him e_b. Then Bob can extract sk_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 :-)