Update docs

pull/263/head
Bogdan Opanchuk 2021-03-20 18:19:36 -07:00
parent c401c52e92
commit bcb0071f9e
7 changed files with 231 additions and 338 deletions

View File

@ -62,19 +62,18 @@ Additionally, users that delegate access to their data (like Alice, in this exam
.. code-block:: python
from umbral import pre, keys, signing
from umbral import SecretKey, PublicKey
# Generate Umbral keys for Alice.
alices_private_key = keys.UmbralPrivateKey.gen_key()
alices_public_key = alices_private_key.get_pubkey()
alices_secret_key = SecretKey.random()
alices_public_key = PublicKey.from_secret_key(alices_secret_key)
alices_signing_key = keys.UmbralPrivateKey.gen_key()
alices_verifying_key = alices_signing_key.get_pubkey()
alices_signer = signing.Signer(private_key=alices_signing_key)
alices_signing_key = SecretKey.random()
alices_verifying_key = PublicKey.from_secret_key(alices_signing_key)
# Generate Umbral keys for Bob.
bobs_private_key = keys.UmbralPrivateKey.gen_key()
bobs_public_key = bobs_private_key.get_pubkey()
bobs_secret_key = SecretKey.random()
bobs_public_key = PublicKey.from_secret_key(bobs_secret_key)
**Encryption**
@ -89,14 +88,14 @@ Alice can open the capsule and decrypt the ciphertext with her private key.
.. code-block:: python
from umbral import encrypt, decrypt_original
# Encrypt data with Alice's public key.
plaintext = b'Proxy Re-Encryption is cool!'
ciphertext, capsule = pre.encrypt(alices_public_key, plaintext)
capsule, ciphertext = encrypt(alices_public_key, plaintext)
# Decrypt data with Alice's private key.
cleartext = pre.decrypt(ciphertext=ciphertext,
capsule=capsule,
decrypting_key=alices_private_key)
cleartext = decrypt_original(alices_secret_key, capsule, ciphertext)
**Re-Encryption Key Fragments**
@ -107,13 +106,15 @@ which are next sent to N proxies or *Ursulas*.
.. code-block:: python
from umbral import generate_kfrags
# Alice generates "M of N" re-encryption key fragments (or "KFrags") for Bob.
# In this example, 10 out of 20.
kfrags = pre.generate_kfrags(delegating_privkey=alices_private_key,
signer=alices_signer,
receiving_pubkey=bobs_public_key,
threshold=10,
N=20)
kfrags = generate_kfrags(delegating_sk=alices_secret_key,
receiving_pk=bobs_public_key,
signing_sk=alices_signing_key,
threshold=10,
num_kfrags=20)
**Re-Encryption**
@ -127,17 +128,13 @@ Bob must gather at least ``threshold`` cfrags in order to activate the capsule.
.. code-block:: python
# Several Ursulas perform re-encryption, and Bob collects the resulting `cfrags`.
# He must gather at least `threshold` `cfrags` in order to activate the capsule.
from umbral import reencrypt
capsule.set_correctness_keys(delegating=alices_public_key,
receiving=bobs_public_key,
verifying=alices_verifying_key)
cfrags = list() # Bob's cfrag collection
for kfrag in kfrags[:10]:
cfrag = pre.reencrypt(kfrag=kfrag, capsule=capsule)
cfrags.append(cfrag) # Bob collects a cfrag
# Several Ursulas perform re-encryption, and Bob collects the resulting `cfrags`.
cfrags = list() # Bob's cfrag collection
for kfrag in kfrags[:10]:
cfrag = pre.reencrypt(capsule=capsule, kfrag=kfrag)
cfrags.append(cfrag) # Bob collects a cfrag
**Decryption by Bob**
@ -147,14 +144,14 @@ and then decrypts the re-encrypted ciphertext.
.. code-block:: python
# Bob activates and opens the capsule
for cfrag in cfrags:
capsule.attach_cfrag(cfrag)
from umbral import decrypt_reencrypted
bob_cleartext = pre.decrypt(ciphertext=ciphertext,
capsule=capsule,
decrypting_key=bobs_private_key)
assert bob_cleartext == plaintext
bob_cleartext = pre.decrypt_reencrypted(decrypting_sk=bobs_secret_key,
delegating_pk=alices_public_key,
capsule=capsule,
cfrags=cfrags,
ciphertext=ciphertext)
assert bob_cleartext == plaintext
See more detailed usage examples in the docs_ directory.
@ -171,7 +168,7 @@ To install pyUmbral, simply use ``pip``:
$ pip3 install umbral
Alternatively, you can checkout the repo and install it from there.
Alternatively, you can checkout the repo and install it from there.
The NuCypher team uses ``pipenv`` for managing pyUmbral's dependencies.
The recommended installation procedure is as follows:

View File

@ -1,41 +1,20 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
#1
# Sets a default curve (secp256k1)
import random
from umbral import pre, keys, config, signing
from umbral import (
SecretKey, PublicKey,
encrypt, generate_kfrags, reencrypt, decrypt_original, decrypt_reencrypted)
from umbral.dem import ErrorInvalidTag
config.set_default_curve()
#2
# Generate an Umbral key pair
# ---------------------------
# First, Let's generate two asymmetric key pairs for Alice:
# A delegating key pair and a Signing key pair.
alices_private_key = keys.UmbralPrivateKey.gen_key()
alices_public_key = alices_private_key.get_pubkey()
alices_secret_key = SecretKey.random()
alices_public_key = PublicKey.from_secret_key(alices_secret_key)
alices_signing_key = keys.UmbralPrivateKey.gen_key()
alices_verifying_key = alices_signing_key.get_pubkey()
alices_signer = signing.Signer(private_key=alices_signing_key)
alices_signing_key = SecretKey.random()
alices_verifying_key = PublicKey.from_secret_key(alices_signing_key)
#3
# Encrypt some data for Alice
# ---------------------------
# Now let's encrypt data with Alice's public key.
@ -44,98 +23,86 @@ alices_signer = signing.Signer(private_key=alices_signing_key)
# this operation.
plaintext = b'Proxy Re-encryption is cool!'
ciphertext, capsule = pre.encrypt(alices_public_key, plaintext)
capsule, ciphertext = encrypt(alices_public_key, plaintext)
print(ciphertext)
#4
# Decrypt data for Alice
# ----------------------
# Since data was encrypted with Alice's public key,
# Alice can open the capsule and decrypt the ciphertext with her private key.
cleartext = pre.decrypt(ciphertext=ciphertext,
capsule=capsule,
decrypting_key=alices_private_key)
cleartext = decrypt_original(alices_secret_key, capsule, ciphertext)
print(cleartext)
#5
# Bob Exists
# -----------
bobs_private_key = keys.UmbralPrivateKey.gen_key()
bobs_public_key = bobs_private_key.get_pubkey()
bobs_secret_key = SecretKey.random()
bobs_public_key = PublicKey.from_secret_key(bobs_secret_key)
#6
# Bob receives a capsule through a side channel (s3, ipfs, Google cloud, etc)
bob_capsule = capsule
#7
# Attempt Bob's decryption (fail)
try:
fail_decrypted_data = pre.decrypt(ciphertext=ciphertext,
capsule=bob_capsule,
decrypting_key=bobs_private_key)
except pre.UmbralDecryptionError:
fail_decrypted_data = decrypt_original(bobs_secret_key, bob_capsule, ciphertext)
except ErrorInvalidTag:
print("Decryption failed! Bob doesn't has access granted yet.")
#8
# Alice grants access to Bob by generating kfrags
# Alice grants access to Bob by generating kfrags
# -----------------------------------------------
# When Alice wants to grant Bob access to open her encrypted messages,
# she creates *threshold split re-encryption keys*, or *"kfrags"*,
# which are next sent to N proxies or *Ursulas*.
# She uses her private key, and Bob's public key, and she sets a minimum
# When Alice wants to grant Bob access to open her encrypted messages,
# she creates *threshold split re-encryption keys*, or *"kfrags"*,
# which are next sent to N proxies or *Ursulas*.
# She uses her private key, and Bob's public key, and she sets a minimum
# threshold of 10, for 20 total shares
kfrags = pre.generate_kfrags(delegating_privkey=alices_private_key,
signer=alices_signer,
receiving_pubkey=bobs_public_key,
threshold=10,
N=20)
kfrags = generate_kfrags(delegating_sk=alices_secret_key,
receiving_pk=bobs_public_key,
signing_sk=alices_signing_key,
threshold=10,
num_kfrags=20)
#9
# 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`
# 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.
import random
kfrags = random.sample(kfrags, # All kfrags from above
10) # M - Threshold
# Bob collects the resulting `cfrags` from several Ursulas.
# Bob must gather at least `threshold` `cfrags` in order to activate the capsule.
bob_capsule.set_correctness_keys(delegating=alices_public_key,
receiving=bobs_public_key,
verifying=alices_verifying_key)
# Bob collects the resulting `cfrags` from several Ursulas.
# Bob must gather at least `threshold` `cfrags` in order to open the capsule.
cfrags = list() # Bob's cfrag collection
for kfrag in kfrags:
cfrag = pre.reencrypt(kfrag=kfrag, capsule=bob_capsule)
cfrag = reencrypt(capsule=capsule, kfrag=kfrag)
cfrags.append(cfrag) # Bob collects a cfrag
assert len(cfrags) == 10
#10
# Bob attaches cfrags to the capsule
# ----------------------------------
# Bob attaches at least `threshold` `cfrags` to the capsule;
# then it can become *activated*.
# Bob checks the capsule fragments
# --------------------------------
# Bob can verify that the capsule fragments are valid and really originate from Alice,
# using Alice's public keys.
for cfrag in cfrags:
bob_capsule.attach_cfrag(cfrag)
assert all(cfrag.verify(capsule,
delegating_pk=alices_public_key,
receiving_pk=bobs_public_key,
signing_pk=alices_verifying_key)
for cfrag in cfrags)
#11
# Bob activates and opens the capsule
# Bob opens the capsule
# ------------------------------------
# Finally, Bob activates and opens the capsule,
# then decrypts the re-encrypted ciphertext.
# Finally, Bob decrypts the re-encrypted ciphertext using his key.
bob_cleartext = pre.decrypt(ciphertext=ciphertext, capsule=bob_capsule, decrypting_key=bobs_private_key)
bob_cleartext = decrypt_reencrypted(decrypting_sk=bobs_secret_key,
delegating_pk=alices_public_key,
capsule=bob_capsule,
cfrags=cfrags,
ciphertext=ciphertext)
print(bob_cleartext)
assert bob_cleartext == plaintext

View File

@ -7,30 +7,6 @@
"# pyUmbral Python API"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Setting the default curve\n",
"\n",
"The first time you use umbral, you may want to specify an elliptic curve to use. If you do not specify a curve, secp256k1 will be used for all operations, with a slight performace hit for the lookup.\n",
"\n",
"To set the default curve use `umbral.config.set_default_curve()`\n",
"\n",
"Note: you can only set the dafault once, or `UmbralConfigurationError` will be raised."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from umbral.config import set_default_curve\n",
"\n",
"set_default_curve()"
]
},
{
"cell_type": "markdown",
"metadata": {},
@ -46,16 +22,15 @@
"metadata": {},
"outputs": [],
"source": [
"from umbral import keys, signing\n",
"from umbral import SecretKey, PublicKey\n",
"\n",
"\n",
"# Alice's Keys\n",
"alices_private_key = keys.UmbralPrivateKey.gen_key()\n",
"alices_public_key = alices_private_key.get_pubkey()\n",
"alices_private_key = SecretKey.random()\n",
"alices_public_key = PublicKey.from_secret_key(alices_private_key)\n",
"\n",
"alices_signing_key = keys.UmbralPrivateKey.gen_key()\n",
"alices_verifying_key = alices_signing_key.get_pubkey()\n",
"alices_signer = signing.Signer(private_key=alices_signing_key)"
"alices_signing_key = SecretKey.random()\n",
"alices_verifying_key = PublicKey.from_secret_key(alices_signing_key)"
]
},
{
@ -80,16 +55,16 @@
"name": "stdout",
"output_type": "stream",
"text": [
"b'#\\xebQ\\xd4\\xad\\x8ah,9\\x8f\\xc9\\x18\\x84[\\x95M\\x8e\\xb1\\x85\\xf9\\xbe\\x97\\x07\\xf3\\x80@\\x11\\xab\\x82\\xac\\xa1\\xbf\\xc0\\x00e\\xecpTq\\xef\\x94\\xd94\\x94\\x1a\\xdf\\xf0\\x04)\\xf5\\r\\xc4\\xbd/:\\x8c'\n"
"b'\\x1c\\xa0\\xa83\\x0cv\\x97\\x02d\\xe9\\xe9\\xc5_\\x9d5NRGRx\\xd4\\xc9\\x17%\\x9b\\xb4\\x05\\xd1\\xc2\\x1e\\x9d\\x0b\\xbf\\xb4g\\xf0n\\xfe\\x9eM\\x93\\xe0\\xbf#l\\xf9\\x033\\xb00\\xf5\\r\\xff\\xc9\\x133C\\xf0\\xa3\\xc0\\xd1e\\xdb~.E$%'\n"
]
}
],
"source": [
"from umbral import pre\n",
"from umbral import encrypt\n",
"\n",
"\n",
"plaintext = b'Proxy Re-encryption is cool!'\n",
"ciphertext, capsule = pre.encrypt(alices_public_key, plaintext)\n",
"capsule, ciphertext = encrypt(alices_public_key, plaintext)\n",
"print(ciphertext)"
]
},
@ -115,10 +90,13 @@
}
],
"source": [
"cleartext = pre.decrypt(ciphertext=ciphertext, \n",
" capsule=capsule, \n",
" decrypting_key=alices_private_key)\n",
"print(cleartext)\n"
"from umbral import decrypt_original\n",
"\n",
"\n",
"cleartext = decrypt_original(sk=alices_private_key,\n",
" capsule=capsule,\n",
" ciphertext=ciphertext)\n",
"print(cleartext)"
]
},
{
@ -135,8 +113,8 @@
"metadata": {},
"outputs": [],
"source": [
"bobs_private_key = keys.UmbralPrivateKey.gen_key()\n",
"bobs_public_key = bobs_private_key.get_pubkey()\n",
"bobs_private_key = SecretKey.random()\n",
"bobs_public_key = PublicKey.from_secret_key(bobs_private_key)\n",
"\n",
"bob_capsule = capsule"
]
@ -162,11 +140,13 @@
}
],
"source": [
"from umbral.dem import ErrorInvalidTag\n",
"\n",
"try:\n",
" fail_decrypted_data = pre.decrypt(ciphertext=ciphertext, \n",
" capsule=capsule, \n",
" decrypting_key=bobs_private_key)\n",
"except pre.UmbralDecryptionError:\n",
" fail_decrypted_data = decrypt_original(sk=bobs_private_key,\n",
" capsule=capsule,\n",
" ciphertext=ciphertext)\n",
"except ErrorInvalidTag:\n",
" print(\"Decryption failed! Bob doesn't has access granted yet.\")\n"
]
},
@ -184,30 +164,7 @@
"metadata": {},
"source": [
"## Alice grants access to Bob by generating KFrags \n",
"When Alice wants to grant Bob access to open her encrypted messages, she creates *re-encryption key fragments*, or *\"kfrags\"*, which are next sent to N proxies or *Ursulas*. She uses her private key, and Bob's public key, and she sets a minimum threshold of 10, for 20 total shares\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"M, N = 10, 20\n",
"kfrags = pre.generate_kfrags(delegating_privkey=alices_private_key, \n",
" receiving_pubkey=bobs_public_key, \n",
" signer=alices_signer,\n",
" threshold=M, \n",
" N=N)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"## Ursulas Re-encrypt; Bob attaches fragments to `capsule`\n",
"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 `M` random `kfrags`, one for each required Ursula. Note that each Ursula must prepare the received capsule before re-encryption by setting the proper correctness keys. Bob collects the resulting `cfrags` from several Ursulas. He must gather at least `M` `cfrags` in order to activate the capsule.\n"
"When Alice wants to grant Bob access to open her encrypted messages, she creates *re-encryption key fragments*, or \"kfrags\", which are next sent to N proxies or *Ursulas*. She uses her private key, and Bob's public key, and she sets a minimum threshold of 10, for 20 total shares\n"
]
},
{
@ -215,18 +172,44 @@
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"from umbral import generate_kfrags\n",
"\n",
"\n",
"M, N = 10, 20\n",
"kfrags = generate_kfrags(delegating_sk=alices_private_key,\n",
" receiving_pk=bobs_public_key,\n",
" signing_sk=alices_signing_key,\n",
" threshold=10,\n",
" num_kfrags=20)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"## Ursulas Re-encrypt; Bob attaches fragments to `capsule`\n",
"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 `M` random `kfrags`, one for each required Ursula. Bob collects the resulting `cfrags` from several Ursulas. He must gather at least `M` `cfrags` in order to activate the capsule.\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"import random\n",
"kfrags = random.sample(kfrags, # All kfrags from above\n",
" 10) # M - Threshold\n",
"\n",
"bob_capsule.set_correctness_keys(delegating=alices_public_key,\n",
" receiving=bobs_public_key,\n",
" verifying=alices_verifying_key)\n",
"\n",
"from umbral import reencrypt\n",
"\n",
"\n",
"cfrags = list() # Bob's cfrag collection\n",
"for kfrag in kfrags:\n",
" cfrag = pre.reencrypt(kfrag=kfrag, capsule=bob_capsule)\n",
" cfrag = reencrypt(capsule=capsule, kfrag=kfrag)\n",
" cfrags.append(cfrag) # Bob collects a cfrag\n",
"\n",
"assert len(cfrags) == 10\n"
@ -236,15 +219,36 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Bob activates and opens the capsule; Decrypts data from Alice.\n",
"The `capsule` can become *activated* once Bob attaches at least `M` `cfrags` to it. Note that it has to be prepared in advance with the necessary `correctness_keys` (specifically, Alice's public key, Alice's signature verification key and his own public key). \n",
"\n",
"Finally, Bob activates and opens the capsule, then decrypts the re-encrypted ciphertext."
"## Bob checks the capsule fragments\n",
"Bob can verify that the capsule fragments are valid and really originate from Alice, using Alice's public keys."
]
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": 11,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"assert all(cfrag.verify(capsule,\n",
" delegating_pk=alices_public_key,\n",
" receiving_pk=bobs_public_key,\n",
" signing_pk=alices_verifying_key)\n",
" for cfrag in cfrags)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Bob opens the capsule; Decrypts data from Alice.\n",
"Finally, Bob decrypts the re-encrypted ciphertext using his key."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
@ -256,17 +260,16 @@
}
],
"source": [
"bob_capsule.set_correctness_keys(delegating=alices_public_key,\n",
" receiving=bobs_public_key,\n",
" verifying=alices_verifying_key)\n",
"from umbral import decrypt_reencrypted\n",
"\n",
"bob_cleartext = decrypt_reencrypted(decrypting_sk=bobs_private_key,\n",
" delegating_pk=alices_public_key,\n",
" capsule=capsule,\n",
" cfrags=cfrags,\n",
" ciphertext=ciphertext)\n",
"\n",
"for cfrag in cfrags:\n",
" bob_capsule.attach_cfrag(cfrag)\n",
" \n",
"bob_cleartext = pre.decrypt(ciphertext=ciphertext, capsule=capsule, decrypting_key=bobs_private_key)\n",
"print(bob_cleartext)\n",
"assert bob_cleartext == plaintext\n",
"\n"
"assert bob_cleartext == plaintext"
]
},
{
@ -293,7 +296,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.0"
"version": "3.8.5"
}
},
"nbformat": 4,

View File

@ -1,63 +0,0 @@
=========================
Choosing and Using 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/
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").
Setting a default curve
--------------------------
Before you perform any ECC operations, you can set a default curve.
.. code-block:: python
>>> from umbral.curve import SECP256K1
>>> config.set_default_curve(SECP256K1)
If you don't set a default curve, then SECP256K1 will be set for you when you perform the first ECC
operation. This causes a small one-time performance penalty.
.. code-block:: python
>>> from umbral import keys
>>> private_key = keys.UmbralPrivateKey.gen_key()
RuntimeWarning: No default curve has been set. Using SECP256K1.
A slight performance penalty has been incurred for only this call.
Set a default curve with umbral.config.set_default_curve().
To use SECP256K1 and avoid this penalty, you can simply call ``set_default_curve()`` with no argument:
.. code-block:: python
>>> config.set_default_curve()
Attempting to set the default curve twice in the same runtime will raise
a ``UmbralConfigurationError``.
.. code-block:: python
>>> from umbral import config
>>> config.set_default_curve()
>>> config.set_default_curve()
Traceback (most recent call last):
...
umbral.config._CONFIG.UmbralConfigurationError

View File

@ -31,7 +31,7 @@ pyUmbral
.. end-badges
pyUmbral is the reference implementation of the Umbral_ threshold proxy re-encryption scheme.
It is open-source, built with Python, and uses OpenSSL_ and Cryptography.io_.
It is open-source, built with Python, and uses OpenSSL_ via Cryptography.io_ and libsodium_ via PyNaCl_.
Using Umbral, Alice (the data owner) can *delegate decryption rights* to Bob for
any ciphertext intended to her, through a re-encryption process performed by a
@ -50,6 +50,8 @@ a proxy re-encryption network to empower privacy in decentralized systems.
.. _Cryptography.io: https://cryptography.io/en/latest/
.. _OpenSSL: https://www.openssl.org/
.. _nucypher: https://github.com/nucypher/nucypher
.. _libsodium: https://github.com/jedisct1/libsodium
.. _PyNaCl: https://pynacl.readthedocs.io/en/latest/
.. toctree::
:maxdepth: 3

View File

@ -12,30 +12,22 @@ Using pyUmbral
sys.path.append(os.path.abspath(os.getcwd()))
.. testcleanup:: capsule_story
Elliptic Curves
===============
from umbral import config
config._CONFIG.___CONFIG__curve = None
config._CONFIG.___CONFIG__params = None
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.
Configuration
==============
.. _Cryptography.io: https://cryptography.io/en/latest/
Be careful when choosing a curve - the security of your application depends on it.
Setting the default curve
--------------------------
The best way to start using pyUmbral is to decide on an elliptic curve to use and set it as your default.
.. doctest:: capsule_story
>>> from umbral import config
>>> from umbral.curve import SECP256K1
>>> config.set_default_curve(SECP256K1)
For more information on curves, see :doc:`choosing_and_using_curves`.
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
@ -49,28 +41,27 @@ A delegating key pair and a signing key pair.
.. doctest:: capsule_story
>>> from umbral import keys, signing
>>> from umbral import SecretKey, PublicKey
>>> alices_private_key = keys.UmbralPrivateKey.gen_key()
>>> alices_public_key = alices_private_key.get_pubkey()
>>> alices_secret_key = SecretKey.random()
>>> alices_public_key = PublicKey.from_secret_key(alices_secret_key)
>>> alices_signing_key = keys.UmbralPrivateKey.gen_key()
>>> alices_verifying_key = alices_signing_key.get_pubkey()
>>> alices_signer = signing.Signer(private_key=alices_signing_key)
>>> alices_signing_key = SecretKey.random()
>>> alices_verifying_key = PublicKey.from_secret_key(alices_signing_key)
Encrypt with a public key
--------------------------
Now let's encrypt data with Alice's public key.
Invocation of ``pre.encrypt`` returns both the ``ciphertext`` and a ``capsule``.
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 pre
>>> from umbral import encrypt
>>> plaintext = b'Proxy Re-encryption is cool!'
>>> ciphertext, capsule = pre.encrypt(alices_public_key, plaintext)
>>> capsule, ciphertext = encrypt(alices_public_key, plaintext)
Decrypt with a private key
@ -80,9 +71,8 @@ Alice can open the capsule and decrypt the ciphertext with her private key.
.. doctest:: capsule_story
>>> cleartext = pre.decrypt(ciphertext=ciphertext,
... capsule=capsule,
... decrypting_key=alices_private_key)
>>> from umbral import decrypt_original
>>> cleartext = decrypt_original(alices_secret_key, capsule, ciphertext)
Threshold Re-Encryption
@ -93,29 +83,29 @@ Bob Exists
.. doctest:: capsule_story
>>> from umbral import keys
>>> bobs_private_key = keys.UmbralPrivateKey.gen_key()
>>> bobs_public_key = bobs_private_key.get_pubkey()
>>> bobs_secret_key = SecretKey.random()
>>> bobs_public_key = PublicKey.from_secret_key(bobs_secret_key)
Alice grants access to Bob by generating kfrags
Alice grants access to Bob by generating kfrags
-----------------------------------------------
When Alice wants to grant Bob access to open her encrypted messages,
When Alice wants to grant Bob access to open her encrypted messages,
she creates *re-encryption key fragments*, or *"kfrags"*,
which are next sent to N proxies or *Ursulas*.
Alice must specify ``N`` (the total number of kfrags),
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
>>> kfrags = pre.generate_kfrags(delegating_privkey=alices_private_key,
... signer=alices_signer,
... receiving_pubkey=bobs_public_key,
... threshold=10,
... N=20)
>>> from umbral import generate_kfrags
>>> kfrags = generate_kfrags(delegating_sk=alices_secret_key,
... receiving_pk=bobs_public_key,
... signing_sk=alices_signing_key,
... threshold=10,
... num_kfrags=20)
Bob receives a capsule
@ -137,25 +127,24 @@ or re-encrypted for him by Ursula, he will not be able to open it.
.. doctest:: capsule_story
>>> fail = pre.decrypt(ciphertext=ciphertext,
... capsule=capsule,
... decrypting_key=bobs_private_key)
>>> fail = decrypt_original(sk=bobs_secret_key,
... capsule=capsule,
... ciphertext=ciphertext)
Traceback (most recent call last):
...
umbral.pre.UmbralDecryptionError
umbral.dem.ErrorInvalidTag
Ursulas perform re-encryption
------------------------------
Bob asks several Ursulas to re-encrypt the capsule so he can open it.
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. Note that each Ursula must prepare the received
capsule before re-encryption by setting the proper correctness keys.
one for each required Ursula.
Bob collects the resulting cfrags from several Ursulas.
Bob must gather at least ``threshold`` cfrags in order to activate the capsule.
Bob must gather at least ``threshold`` cfrags in order to open the capsule.
.. doctest:: capsule_story
@ -164,14 +153,10 @@ Bob must gather at least ``threshold`` cfrags in order to activate the capsule.
>>> kfrags = random.sample(kfrags, # All kfrags from above
... 10) # M - Threshold
>>> capsule.set_correctness_keys(delegating=alices_public_key,
... receiving=bobs_public_key,
... verifying=alices_verifying_key)
(True, True, True)
>>> from umbral import reencrypt
>>> cfrags = list() # Bob's cfrag collection
>>> for kfrag in kfrags:
... cfrag = pre.reencrypt(kfrag=kfrag, capsule=capsule)
... cfrag = reencrypt(capsule=capsule, kfrag=kfrag)
... cfrags.append(cfrag) # Bob collects a cfrag
.. doctest:: capsule_story
@ -183,32 +168,34 @@ Bob must gather at least ``threshold`` cfrags in order to activate the capsule.
Decryption
==================================
Bob attaches cfrags to the capsule
----------------------------------
Bob attaches at least ``threshold`` cfrags to the capsule,
which has to be prepared in advance with the necessary correctness keys.
Only then it can become *activated*.
Bob checks the capsule fragments
--------------------------------
Bob can verify that the capsule fragments are valid and really originate from Alice,
using Alice's public keys.
.. doctest:: capsule_story
>>> capsule.set_correctness_keys(delegating=alices_public_key,
... receiving=bobs_public_key,
... verifying=alices_verifying_key)
(False, False, False)
>>> for cfrag in cfrags:
... capsule.attach_cfrag(cfrag)
>>> all(cfrag.verify(capsule,
... delegating_pk=alices_public_key,
... receiving_pk=bobs_public_key,
... signing_pk=alices_verifying_key)
... for cfrag in cfrags)
True
Bob activates and opens the capsule
------------------------------------
Finally, Bob decrypts the re-encrypted ciphertext using the activated capsule.
Bob opens the capsule
---------------------
Finally, Bob decrypts the re-encrypted ciphertext using his key.
.. doctest:: capsule_story
>>> cleartext = pre.decrypt(ciphertext=ciphertext,
... capsule=capsule,
... decrypting_key=bobs_private_key)
>>> from umbral import decrypt_reencrypted
>>> cleartext = decrypt_reencrypted(decrypting_sk=bobs_secret_key,
... delegating_pk=alices_public_key,
... capsule=capsule,
... cfrags=cfrags,
... ciphertext=ciphertext)
.. doctest:: capsule_story
:hide:

View File

@ -79,7 +79,7 @@ DEV_INSTALL_REQUIRES = [
EXTRAS_REQUIRE = {
'testing': DEV_INSTALL_REQUIRES,
'docs': ['sphinx', 'sphinx-autobuild'],
'docs': ['sphinx', 'sphinx-autobuild', 'sphinx_rtd_theme'],
'benchmarks': ['pytest-benchmark'],
}