mirror of https://github.com/nucypher/pyUmbral.git
210 lines
6.5 KiB
ReStructuredText
210 lines
6.5 KiB
ReStructuredText
==============
|
|
Using pyUmbral
|
|
==============
|
|
|
|
.. image:: .static/PRE_image.png
|
|
|
|
|
|
.. testsetup:: capsule_story
|
|
|
|
import sys
|
|
import os
|
|
sys.path.append(os.path.abspath(os.getcwd()))
|
|
|
|
|
|
Elliptic Curves
|
|
===============
|
|
|
|
The matter of which curve to use is the subject of some debate. If you aren't sure, you might start here:
|
|
https://safecurves.cr.yp.to/
|
|
|
|
A number of curves are available in the Cryptography.io_ library, on which pyUmbral depends.
|
|
You can find them in the ``cryptography.hazmat.primitives.asymmetric.ec`` module.
|
|
|
|
.. _Cryptography.io: https://cryptography.io/en/latest/
|
|
|
|
.. important::
|
|
|
|
Be careful when choosing a curve - the security of your application depends on it.
|
|
|
|
We provide curve ``SECP256K1`` as a default because it is the basis for a number of crypto-blockchain projects;
|
|
we don't otherwise endorse its security.
|
|
We additionally support curves ``SECP256R1`` (also known as "NIST P-256") and ``SECP384R1`` ("NIST P-384"), but they cannot currently be selected via the public API.
|
|
|
|
|
|
Encryption
|
|
==========
|
|
|
|
|
|
Generate an Umbral key pair
|
|
-----------------------------
|
|
First, let's generate two asymmetric key pairs for Alice:
|
|
A delegating key pair and a signing key pair.
|
|
|
|
.. doctest:: capsule_story
|
|
|
|
>>> from umbral import SecretKey, PublicKey, Signer
|
|
|
|
>>> alices_secret_key = SecretKey.random()
|
|
>>> alices_public_key = PublicKey.from_secret_key(alices_secret_key)
|
|
|
|
>>> alices_signing_key = SecretKey.random()
|
|
>>> alices_verifying_key = PublicKey.from_secret_key(alices_signing_key)
|
|
>>> alices_signer = Signer(alices_signing_key)
|
|
|
|
|
|
Encrypt with a public key
|
|
--------------------------
|
|
Now let's encrypt data with Alice's public key.
|
|
Invocation of :py:func:`encrypt` returns both a ``capsule`` and a ``ciphertext``.
|
|
Note that anyone with Alice's public key can perform this operation.
|
|
|
|
|
|
.. doctest:: capsule_story
|
|
|
|
>>> from umbral import encrypt
|
|
>>> plaintext = b'Proxy Re-encryption is cool!'
|
|
>>> capsule, ciphertext = encrypt(alices_public_key, plaintext)
|
|
|
|
|
|
Decrypt with a private key
|
|
---------------------------
|
|
Since data was encrypted with Alice's public key,
|
|
Alice can open the capsule and decrypt the ciphertext with her private key.
|
|
|
|
.. doctest:: capsule_story
|
|
|
|
>>> from umbral import decrypt_original
|
|
>>> cleartext = decrypt_original(alices_secret_key, capsule, ciphertext)
|
|
|
|
|
|
Threshold Re-Encryption
|
|
==================================
|
|
|
|
Bob Exists
|
|
-----------
|
|
|
|
.. doctest:: capsule_story
|
|
|
|
>>> bobs_secret_key = SecretKey.random()
|
|
>>> bobs_public_key = PublicKey.from_secret_key(bobs_secret_key)
|
|
|
|
|
|
Alice grants access to Bob by generating kfrags
|
|
-----------------------------------------------
|
|
When Alice wants to grant Bob access to view her encrypted data,
|
|
she creates *re-encryption key fragments*, or *"kfrags"*,
|
|
which are next sent to N proxies or *Ursulas*.
|
|
|
|
Alice must specify ``num_kfrags`` (the total number of kfrags),
|
|
and a ``threshold`` (the minimum number of kfrags needed to activate a capsule).
|
|
In the following example, Alice creates 20 kfrags,
|
|
but Bob needs to get only 10 re-encryptions to activate the capsule.
|
|
|
|
.. doctest:: capsule_story
|
|
|
|
>>> from umbral import generate_kfrags
|
|
>>> kfrags = generate_kfrags(delegating_sk=alices_secret_key,
|
|
... receiving_pk=bobs_public_key,
|
|
... signer=alices_signer,
|
|
... threshold=10,
|
|
... num_kfrags=20)
|
|
|
|
|
|
Bob receives a capsule
|
|
-----------------------
|
|
Next, let's generate a key pair for Bob, and pretend to send
|
|
him the capsule through a side channel like
|
|
S3, IPFS, Google Cloud, Sneakernet, etc.
|
|
|
|
.. code-block:: python
|
|
|
|
# Bob receives the capsule through a side-channel: IPFS, Sneakernet, etc.
|
|
capsule = <fetch the capsule through a side-channel>
|
|
|
|
|
|
Bob fails to open the capsule
|
|
-------------------------------
|
|
If Bob attempts to open a capsule that was not encrypted for his public key,
|
|
or re-encrypted for him by Ursula, he will not be able to open it.
|
|
|
|
.. doctest:: capsule_story
|
|
|
|
>>> fail = decrypt_original(sk=bobs_secret_key,
|
|
... capsule=capsule,
|
|
... ciphertext=ciphertext)
|
|
Traceback (most recent call last):
|
|
...
|
|
umbral.GenericError
|
|
|
|
|
|
Ursulas perform re-encryption
|
|
------------------------------
|
|
Bob asks several Ursulas to re-encrypt the capsule so he can open it.
|
|
Each Ursula performs re-encryption on the capsule using the ``kfrag``
|
|
provided by Alice, obtaining this way a "capsule fragment", or ``cfrag``.
|
|
Let's mock a network or transport layer by sampling ``threshold`` random kfrags,
|
|
one for each required Ursula.
|
|
|
|
Bob collects the resulting cfrags from several Ursulas.
|
|
Bob must gather at least ``threshold`` cfrags in order to open the capsule.
|
|
|
|
|
|
.. doctest:: capsule_story
|
|
|
|
>>> import random
|
|
>>> kfrags = random.sample(kfrags, # All kfrags from above
|
|
... 10) # M - Threshold
|
|
|
|
>>> from umbral import reencrypt
|
|
>>> cfrags = list() # Bob's cfrag collection
|
|
>>> for kfrag in kfrags:
|
|
... cfrag = reencrypt(capsule=capsule, kfrag=kfrag)
|
|
... cfrags.append(cfrag) # Bob collects a cfrag
|
|
|
|
.. doctest:: capsule_story
|
|
:hide:
|
|
|
|
>>> assert len(cfrags) == 10
|
|
|
|
|
|
Decryption
|
|
==================================
|
|
|
|
Bob checks the capsule fragments
|
|
--------------------------------
|
|
If Bob received the capsule fragments in serialized form,
|
|
he can verify that they are valid and really originate from Alice,
|
|
using Alice's public keys.
|
|
|
|
.. doctest:: capsule_story
|
|
|
|
>>> from umbral import CapsuleFrag
|
|
>>> suspicious_cfrags = [CapsuleFrag.from_bytes(bytes(cfrag)) for cfrag in cfrags]
|
|
>>> cfrags = [cfrag.verify(capsule,
|
|
... verifying_pk=alices_verifying_key,
|
|
... delegating_pk=alices_public_key,
|
|
... receiving_pk=bobs_public_key,
|
|
... )
|
|
... for cfrag in suspicious_cfrags]
|
|
|
|
|
|
Bob opens the capsule
|
|
---------------------
|
|
Finally, Bob decrypts the re-encrypted ciphertext using his key.
|
|
|
|
.. doctest:: capsule_story
|
|
|
|
>>> from umbral import decrypt_reencrypted
|
|
>>> cleartext = decrypt_reencrypted(decrypting_sk=bobs_secret_key,
|
|
... delegating_pk=alices_public_key,
|
|
... capsule=capsule,
|
|
... verified_cfrags=cfrags,
|
|
... ciphertext=ciphertext)
|
|
|
|
|
|
.. doctest:: capsule_story
|
|
:hide:
|
|
|
|
>>> assert cleartext == plaintext
|