2018-02-12 18:51:11 +00:00
|
|
|
from typing import Tuple, Union, List
|
|
|
|
|
2018-03-13 12:08:30 +00:00
|
|
|
from cryptography.hazmat.backends.openssl import backend
|
2018-01-03 17:27:08 +00:00
|
|
|
from cryptography.hazmat.primitives.asymmetric import ec
|
2018-03-13 12:08:30 +00:00
|
|
|
from cryptography.hazmat.primitives import hashes
|
2018-01-10 14:48:33 +00:00
|
|
|
|
2018-05-06 21:33:23 +00:00
|
|
|
from umbral._pre import prove_cfrag_correctness
|
2018-04-23 18:03:18 +00:00
|
|
|
from umbral.curvebn import CurveBN
|
2018-02-08 04:09:47 +00:00
|
|
|
from umbral.config import default_params, default_curve
|
2018-01-23 10:13:08 +00:00
|
|
|
from umbral.dem import UmbralDEM
|
2018-05-06 21:33:23 +00:00
|
|
|
from umbral.fragments import KFrag, CapsuleFrag
|
2018-01-31 08:22:55 +00:00
|
|
|
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
|
2018-02-01 08:47:06 +00:00
|
|
|
from umbral.params import UmbralParameters
|
2018-02-01 17:06:11 +00:00
|
|
|
from umbral.point import Point
|
2018-02-08 21:38:43 +00:00
|
|
|
from umbral.utils import poly_eval, lambda_coeff, kdf, get_curve_keysize_bytes
|
2018-01-03 17:27:08 +00:00
|
|
|
|
2018-02-08 20:15:44 +00:00
|
|
|
from io import BytesIO
|
|
|
|
|
2018-05-07 23:51:17 +00:00
|
|
|
import os
|
|
|
|
|
2018-01-18 07:30:36 +00:00
|
|
|
|
2018-03-08 01:37:33 +00:00
|
|
|
CHACHA20_KEY_SIZE = 32
|
|
|
|
|
|
|
|
|
2018-02-12 22:48:34 +00:00
|
|
|
class GenericUmbralError(Exception):
|
|
|
|
pass
|
|
|
|
|
2018-04-23 18:03:18 +00:00
|
|
|
|
2018-04-18 20:59:27 +00:00
|
|
|
class UmbralCorrectnessError(GenericUmbralError):
|
|
|
|
def __init__(self, message, offending_cfrags):
|
|
|
|
super().__init__(message)
|
|
|
|
self.offending_cfrags = offending_cfrags
|
2018-02-12 22:48:34 +00:00
|
|
|
|
2018-02-21 07:34:58 +00:00
|
|
|
|
2018-04-23 18:03:18 +00:00
|
|
|
class Capsule(object):
|
2018-02-08 22:25:54 +00:00
|
|
|
def __init__(self,
|
2018-04-23 09:33:43 +00:00
|
|
|
point_e=None,
|
|
|
|
point_v=None,
|
2018-02-08 22:25:54 +00:00
|
|
|
bn_sig=None,
|
2018-04-23 09:33:43 +00:00
|
|
|
point_e_prime=None,
|
|
|
|
point_v_prime=None,
|
|
|
|
point_noninteractive=None):
|
|
|
|
|
|
|
|
if isinstance(point_e, Point):
|
2018-04-23 18:03:18 +00:00
|
|
|
if not isinstance(point_v, Point) or not isinstance(bn_sig, CurveBN):
|
2018-04-23 09:33:43 +00:00
|
|
|
raise TypeError("Need point_e, point_v, and bn_sig to make a Capsule.")
|
|
|
|
elif isinstance(point_e_prime, Point):
|
|
|
|
if not isinstance(point_v_prime, Point) or not isinstance(point_noninteractive, Point):
|
|
|
|
raise TypeError("Need e_prime, v_prime, and point_noninteractive to make an activated Capsule.")
|
2018-02-23 00:13:16 +00:00
|
|
|
else:
|
2018-02-23 06:44:26 +00:00
|
|
|
raise TypeError(
|
2018-04-23 18:03:18 +00:00
|
|
|
"Need proper Points and/or CurveBNs to make a Capsule. Pass either Alice's data or Bob's. " \
|
2018-02-23 06:44:26 +00:00
|
|
|
"Passing both is also fine.")
|
2018-02-08 22:25:54 +00:00
|
|
|
|
2018-04-23 09:33:43 +00:00
|
|
|
self._point_e = point_e
|
|
|
|
self._point_v = point_v
|
2018-02-08 22:25:54 +00:00
|
|
|
self._bn_sig = bn_sig
|
|
|
|
|
2018-04-23 09:33:43 +00:00
|
|
|
self._point_e_prime = point_e_prime
|
|
|
|
self._point_v_prime = point_v_prime
|
|
|
|
self._point_noninteractive = point_noninteractive
|
2018-02-08 22:25:54 +00:00
|
|
|
|
2018-04-24 22:20:48 +00:00
|
|
|
self._attached_cfrags = list()
|
2018-02-08 22:25:54 +00:00
|
|
|
|
2018-05-23 10:15:56 +00:00
|
|
|
@classmethod
|
|
|
|
def get_size(cls, curve: ec.EllipticCurve=None):
|
|
|
|
"""
|
|
|
|
Returns the size (in bytes) of a Capsule given the curve.
|
|
|
|
If no curve is provided, it will use the default curve.
|
|
|
|
"""
|
|
|
|
curve = curve if curve is not None else default_curve()
|
|
|
|
bn_size = CurveBN.get_size(curve)
|
|
|
|
point_size = Point.get_size(curve)
|
|
|
|
|
|
|
|
return (bn_size * 1) + (point_size * 2)
|
|
|
|
|
2018-02-08 22:25:54 +00:00
|
|
|
class NotValid(ValueError):
|
|
|
|
"""
|
2018-04-23 09:33:43 +00:00
|
|
|
raised if the capsule does not pass verification.
|
2018-02-08 22:25:54 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_bytes(cls, capsule_bytes: bytes, curve: ec.EllipticCurve = None):
|
|
|
|
"""
|
|
|
|
Instantiates a Capsule object from the serialized data.
|
|
|
|
"""
|
|
|
|
curve = curve if curve is not None else default_curve()
|
|
|
|
key_size = get_curve_keysize_bytes(curve)
|
|
|
|
capsule_buff = BytesIO(capsule_bytes)
|
|
|
|
|
2018-04-23 18:03:18 +00:00
|
|
|
# CurveBNs are the keysize in bytes, Points are compressed and the
|
2018-02-08 22:25:54 +00:00
|
|
|
# keysize + 1 bytes long.
|
|
|
|
if len(capsule_bytes) == 197:
|
2018-04-23 09:33:43 +00:00
|
|
|
e = Point.from_bytes(capsule_buff.read(key_size + 1), curve)
|
|
|
|
v = Point.from_bytes(capsule_buff.read(key_size + 1), curve)
|
2018-04-23 18:03:18 +00:00
|
|
|
sig = CurveBN.from_bytes(capsule_buff.read(key_size), curve)
|
2018-02-08 22:25:54 +00:00
|
|
|
e_prime = Point.from_bytes(capsule_buff.read(key_size + 1), curve)
|
|
|
|
v_prime = Point.from_bytes(capsule_buff.read(key_size + 1), curve)
|
2018-04-23 09:33:43 +00:00
|
|
|
ni = Point.from_bytes(capsule_buff.read(key_size + 1), curve)
|
2018-02-08 22:25:54 +00:00
|
|
|
else:
|
2018-04-23 09:33:43 +00:00
|
|
|
e = Point.from_bytes(capsule_buff.read(key_size + 1), curve)
|
|
|
|
v = Point.from_bytes(capsule_buff.read(key_size + 1), curve)
|
2018-04-23 18:03:18 +00:00
|
|
|
sig = CurveBN.from_bytes(capsule_buff.read(key_size), curve)
|
2018-04-23 09:33:43 +00:00
|
|
|
e_prime = v_prime = ni = None
|
2018-02-08 22:25:54 +00:00
|
|
|
|
2018-04-23 09:33:43 +00:00
|
|
|
return cls(point_e=e, point_v=v, bn_sig=sig,
|
|
|
|
point_e_prime=e_prime, point_v_prime=v_prime,
|
|
|
|
point_noninteractive=ni)
|
2018-02-08 22:25:54 +00:00
|
|
|
|
2018-02-26 09:26:50 +00:00
|
|
|
def _original_to_bytes(self) -> bytes:
|
|
|
|
return bytes().join(c.to_bytes() for c in self.original_components())
|
|
|
|
|
2018-02-12 18:51:11 +00:00
|
|
|
def to_bytes(self) -> bytes:
|
2018-02-08 22:25:54 +00:00
|
|
|
"""
|
|
|
|
Serialize the Capsule into a bytestring.
|
|
|
|
"""
|
2018-02-26 09:26:50 +00:00
|
|
|
bytes_representation = self._original_to_bytes()
|
2018-02-08 22:25:54 +00:00
|
|
|
if all(self.activated_components()):
|
|
|
|
bytes_representation += bytes().join(c.to_bytes() for c in self.activated_components())
|
|
|
|
return bytes_representation
|
|
|
|
|
2018-02-12 18:51:11 +00:00
|
|
|
def verify(self, params: UmbralParameters=None) -> bool:
|
2018-02-08 22:25:54 +00:00
|
|
|
params = params if params is not None else default_params()
|
|
|
|
|
2018-04-23 09:33:43 +00:00
|
|
|
e = self._point_e
|
|
|
|
v = self._point_v
|
2018-02-08 22:25:54 +00:00
|
|
|
s = self._bn_sig
|
2018-05-01 23:14:42 +00:00
|
|
|
h = CurveBN.hash(e, v, params=params)
|
2018-02-08 22:25:54 +00:00
|
|
|
|
|
|
|
return s * params.g == v + (h * e)
|
|
|
|
|
2018-02-12 18:51:11 +00:00
|
|
|
def attach_cfrag(self, cfrag: CapsuleFrag) -> None:
|
2018-04-24 22:20:48 +00:00
|
|
|
self._attached_cfrags.append(cfrag)
|
2018-02-08 22:25:54 +00:00
|
|
|
|
2018-04-23 18:03:18 +00:00
|
|
|
def original_components(self) -> Tuple[Point, Point, CurveBN]:
|
2018-04-23 09:33:43 +00:00
|
|
|
return self._point_e, self._point_v, self._bn_sig
|
2018-02-08 22:25:54 +00:00
|
|
|
|
2018-04-23 18:54:06 +00:00
|
|
|
def activated_components(self) -> Union[Tuple[None, None, None], Tuple[Point, Point, Point]]:
|
2018-04-23 09:33:43 +00:00
|
|
|
return self._point_e_prime, self._point_v_prime, self._point_noninteractive
|
2018-02-08 22:25:54 +00:00
|
|
|
|
2018-03-13 12:08:30 +00:00
|
|
|
def _reconstruct_shamirs_secret(self,
|
2018-04-23 18:03:18 +00:00
|
|
|
priv_b: Union[UmbralPrivateKey, CurveBN],
|
2018-03-31 19:12:33 +00:00
|
|
|
params: UmbralParameters=None) -> None:
|
2018-03-13 12:08:30 +00:00
|
|
|
|
2018-03-31 19:12:33 +00:00
|
|
|
params = params if params is not None else default_params()
|
2018-03-13 12:08:30 +00:00
|
|
|
|
2018-05-08 21:34:11 +00:00
|
|
|
g = params.g
|
|
|
|
|
2018-03-13 12:08:30 +00:00
|
|
|
if isinstance(priv_b, UmbralPrivateKey):
|
2018-05-08 21:34:11 +00:00
|
|
|
pub_b = priv_b.get_pubkey()
|
2018-03-13 12:08:30 +00:00
|
|
|
priv_b = priv_b.bn_key
|
2018-05-08 21:34:11 +00:00
|
|
|
else:
|
|
|
|
pub_b = priv_b * g
|
2018-03-13 12:08:30 +00:00
|
|
|
|
2018-05-08 21:34:11 +00:00
|
|
|
cfrag_0 = self._attached_cfrags[0]
|
|
|
|
id_0 = cfrag_0._kfrag_id
|
|
|
|
ni = cfrag_0._point_noninteractive
|
|
|
|
xcoord = cfrag_0._point_xcoord
|
2018-03-13 12:08:30 +00:00
|
|
|
|
2018-05-08 21:34:11 +00:00
|
|
|
dh_xcoord = priv_b * xcoord
|
2018-03-13 12:08:30 +00:00
|
|
|
|
2018-03-13 14:43:25 +00:00
|
|
|
blake2b = hashes.Hash(hashes.BLAKE2b(64), backend=backend)
|
2018-05-08 21:34:11 +00:00
|
|
|
blake2b.update(xcoord.to_bytes())
|
2018-03-13 14:43:25 +00:00
|
|
|
blake2b.update(pub_b.to_bytes())
|
2018-05-08 21:34:11 +00:00
|
|
|
blake2b.update(dh_xcoord.to_bytes())
|
2018-03-13 14:43:25 +00:00
|
|
|
hashed_dh_tuple = blake2b.finalize()
|
2018-03-13 12:08:30 +00:00
|
|
|
|
2018-05-08 21:34:11 +00:00
|
|
|
|
2018-04-24 22:20:48 +00:00
|
|
|
if len(self._attached_cfrags) > 1:
|
2018-05-07 23:51:17 +00:00
|
|
|
xs = [CurveBN.hash(cfrag._kfrag_id, hashed_dh_tuple, params=params)
|
2018-04-24 22:20:48 +00:00
|
|
|
for cfrag in self._attached_cfrags]
|
2018-05-08 21:34:11 +00:00
|
|
|
x_0 = CurveBN.hash(id_0, hashed_dh_tuple, params=params)
|
2018-03-13 12:08:30 +00:00
|
|
|
lambda_0 = lambda_coeff(x_0, xs)
|
2018-04-23 09:33:43 +00:00
|
|
|
e = lambda_0 * cfrag_0._point_e1
|
|
|
|
v = lambda_0 * cfrag_0._point_v1
|
2018-02-08 22:25:54 +00:00
|
|
|
|
2018-04-24 22:20:48 +00:00
|
|
|
for cfrag in self._attached_cfrags[1:]:
|
2018-05-08 21:34:11 +00:00
|
|
|
if (ni, xcoord) != (cfrag._point_noninteractive, cfrag._point_xcoord):
|
|
|
|
raise ValueError("Attached CFrags are not pairwise consistent")
|
|
|
|
|
2018-05-07 23:51:17 +00:00
|
|
|
x_i = CurveBN.hash(cfrag._kfrag_id, hashed_dh_tuple, params=params)
|
2018-03-13 12:08:30 +00:00
|
|
|
lambda_i = lambda_coeff(x_i, xs)
|
2018-04-23 09:33:43 +00:00
|
|
|
e = e + (lambda_i * cfrag._point_e1)
|
|
|
|
v = v + (lambda_i * cfrag._point_v1)
|
2018-02-08 22:25:54 +00:00
|
|
|
else:
|
2018-04-23 09:33:43 +00:00
|
|
|
e = cfrag_0._point_e1
|
|
|
|
v = cfrag_0._point_v1
|
2018-02-08 22:25:54 +00:00
|
|
|
|
2018-04-23 09:33:43 +00:00
|
|
|
self._point_e_prime = e
|
|
|
|
self._point_v_prime = v
|
2018-05-08 21:34:11 +00:00
|
|
|
self._point_noninteractive = ni
|
2018-02-08 22:25:54 +00:00
|
|
|
|
|
|
|
def __bytes__(self):
|
2018-02-13 20:47:21 +00:00
|
|
|
return self.to_bytes()
|
2018-02-08 22:25:54 +00:00
|
|
|
|
|
|
|
def __eq__(self, other):
|
2018-02-21 07:34:58 +00:00
|
|
|
"""
|
|
|
|
If both Capsules are activated, we compare only the activated components.
|
|
|
|
Otherwise, we compare only original components.
|
2018-04-23 18:03:18 +00:00
|
|
|
Each component is compared to its counterpart in constant time per the __eq__ of Point and CurveBN.
|
2018-02-21 07:34:58 +00:00
|
|
|
"""
|
2018-02-08 22:25:54 +00:00
|
|
|
if all(self.activated_components() + other.activated_components()):
|
2018-02-23 00:13:16 +00:00
|
|
|
activated_match = self.activated_components() == other.activated_components()
|
|
|
|
return activated_match
|
2018-02-08 22:25:54 +00:00
|
|
|
elif all(self.original_components() + other.original_components()):
|
2018-02-23 00:13:16 +00:00
|
|
|
original_match = self.original_components() == other.original_components()
|
|
|
|
return original_match
|
2018-02-08 22:25:54 +00:00
|
|
|
else:
|
2018-02-23 00:13:16 +00:00
|
|
|
# This is not constant time obviously, but it's hard to imagine how this is valuable as
|
|
|
|
# an attacker already knows about her own Capsule. It's possible that a Bob, having
|
|
|
|
# activated a Capsule, will make it available for comparison via an API amidst other
|
|
|
|
# (dormat) Capsules. Then an attacker can, by alternating between activated and dormant
|
|
|
|
# Capsules, determine if a given Capsule is activated. Do we care about this?
|
|
|
|
# Again, it's hard to imagine why.
|
2018-02-08 22:25:54 +00:00
|
|
|
return False
|
|
|
|
|
2018-02-21 07:35:57 +00:00
|
|
|
def __hash__(self):
|
|
|
|
# We only ever want to store in a hash table based on original components;
|
|
|
|
# A Capsule that is part of a dict needs to continue to be lookup-able even
|
|
|
|
# after activation.
|
|
|
|
# Note: In case this isn't obvious, don't use this as a secure hash. Use BLAKE2b or something.
|
2018-02-23 02:53:34 +00:00
|
|
|
component_bytes = tuple(component.to_bytes() for component in self.original_components())
|
2018-02-21 07:35:57 +00:00
|
|
|
return hash(component_bytes)
|
|
|
|
|
2018-02-08 22:25:54 +00:00
|
|
|
|
2018-05-08 21:34:11 +00:00
|
|
|
def split_rekey(privkey_a_bn: Union[UmbralPrivateKey, CurveBN],
|
2018-05-06 05:18:36 +00:00
|
|
|
pubkey_b_point: Union[UmbralPublicKey, Point],
|
2018-02-12 20:44:07 +00:00
|
|
|
threshold: int, N: int,
|
2018-02-26 14:36:56 +00:00
|
|
|
params: UmbralParameters=None) -> List[KFrag]:
|
2018-02-08 09:30:56 +00:00
|
|
|
"""
|
2018-02-26 14:36:56 +00:00
|
|
|
Creates a re-encryption key from Alice to Bob and splits it in KFrags,
|
|
|
|
using Shamir's Secret Sharing. Requires a threshold number of KFrags
|
|
|
|
out of N to guarantee correctness of re-encryption.
|
2018-02-08 05:26:38 +00:00
|
|
|
|
2018-02-26 14:36:56 +00:00
|
|
|
Returns a list of KFrags.
|
2018-02-08 09:30:56 +00:00
|
|
|
"""
|
|
|
|
params = params if params is not None else default_params()
|
2018-01-23 10:13:08 +00:00
|
|
|
|
2018-05-08 21:34:11 +00:00
|
|
|
g = params.g
|
|
|
|
|
|
|
|
if isinstance(privkey_a_bn, UmbralPrivateKey):
|
|
|
|
pubkey_a_point = privkey_a_bn.get_pubkey().point_key
|
|
|
|
privkey_a_bn = privkey_a_bn.bn_key
|
|
|
|
else:
|
|
|
|
pubkey_a_point = privkey_a_bn * g
|
2018-01-23 10:13:08 +00:00
|
|
|
|
2018-05-06 05:18:36 +00:00
|
|
|
if isinstance(pubkey_b_point, UmbralPublicKey):
|
|
|
|
pubkey_b_point = pubkey_b_point.point_key
|
2018-01-17 16:54:37 +00:00
|
|
|
|
2018-05-08 21:34:11 +00:00
|
|
|
# 'ni' stands for 'Non Interactive'.
|
|
|
|
# This point is used as an ephemeral public key in a DH key exchange,
|
|
|
|
# and the resulting shared secret 'd' allows to make Umbral non-interactive
|
|
|
|
priv_ni = CurveBN.gen_rand(params.curve)
|
|
|
|
ni = priv_ni * g
|
|
|
|
d = CurveBN.hash(ni, pubkey_b_point, pubkey_b_point * priv_ni, params=params)
|
2017-12-28 14:02:53 +00:00
|
|
|
|
2018-05-08 21:34:11 +00:00
|
|
|
coeffs = [privkey_a_bn * (~d)]
|
2018-04-23 18:03:18 +00:00
|
|
|
coeffs += [CurveBN.gen_rand(params.curve) for _ in range(threshold - 1)]
|
2017-12-28 14:02:53 +00:00
|
|
|
|
2018-02-08 09:30:56 +00:00
|
|
|
u = params.u
|
2017-12-28 14:02:53 +00:00
|
|
|
|
2018-05-08 21:34:11 +00:00
|
|
|
# 'xcoord' stands for 'X coordinate'.
|
|
|
|
# This point is used as an ephemeral public key in a DH key exchange,
|
|
|
|
# and the resulting shared secret 'dh_xcoord' contributes to prevent
|
|
|
|
# reconstruction of the re-encryption key without Bob's intervention
|
|
|
|
priv_xcoord = CurveBN.gen_rand(params.curve)
|
|
|
|
xcoord = priv_xcoord * g
|
|
|
|
|
|
|
|
dh_xcoord = priv_xcoord * pubkey_b_point
|
2018-03-13 12:08:30 +00:00
|
|
|
|
2018-03-13 14:43:25 +00:00
|
|
|
blake2b = hashes.Hash(hashes.BLAKE2b(64), backend=backend)
|
2018-05-08 21:34:11 +00:00
|
|
|
blake2b.update(xcoord.to_bytes())
|
2018-05-06 05:18:36 +00:00
|
|
|
blake2b.update(pubkey_b_point.to_bytes())
|
2018-05-08 21:34:11 +00:00
|
|
|
blake2b.update(dh_xcoord.to_bytes())
|
2018-03-13 14:43:25 +00:00
|
|
|
hashed_dh_tuple = blake2b.finalize()
|
2018-03-13 12:08:30 +00:00
|
|
|
|
2018-05-07 23:51:17 +00:00
|
|
|
bn_size = CurveBN.get_size(params.curve)
|
|
|
|
|
2018-02-26 14:36:56 +00:00
|
|
|
kfrags = []
|
2018-02-08 09:30:56 +00:00
|
|
|
for _ in range(N):
|
2018-05-07 23:51:17 +00:00
|
|
|
id = os.urandom(bn_size)
|
2018-03-13 12:08:30 +00:00
|
|
|
|
2018-05-07 23:51:17 +00:00
|
|
|
share_x = CurveBN.hash(id, hashed_dh_tuple, params=params)
|
2018-03-13 12:08:30 +00:00
|
|
|
|
|
|
|
rk = poly_eval(coeffs, share_x)
|
2017-12-28 14:02:53 +00:00
|
|
|
|
2018-02-08 09:30:56 +00:00
|
|
|
u1 = rk * u
|
2017-12-28 14:02:53 +00:00
|
|
|
|
2018-05-08 21:34:11 +00:00
|
|
|
# TODO: change this Schnorr signature for Ed25519 or ECDSA (#97)
|
|
|
|
y = CurveBN.gen_rand(params.curve)
|
|
|
|
g_y = y * g
|
2018-05-08 23:57:52 +00:00
|
|
|
signature_input = (g_y, id, pubkey_a_point, pubkey_b_point, u1, ni, xcoord)
|
2018-05-06 02:03:53 +00:00
|
|
|
z1 = CurveBN.hash(*signature_input, params=params)
|
2018-05-08 21:34:11 +00:00
|
|
|
z2 = y - privkey_a_bn * z1
|
2017-12-28 14:02:53 +00:00
|
|
|
|
2018-05-07 23:51:17 +00:00
|
|
|
kfrag = KFrag(id=id, bn_key=rk,
|
2018-05-08 21:34:11 +00:00
|
|
|
point_noninteractive=ni, point_commitment=u1,
|
|
|
|
point_xcoord=xcoord, bn_sig1=z1, bn_sig2=z2)
|
2018-02-26 14:36:56 +00:00
|
|
|
kfrags.append(kfrag)
|
2017-12-29 00:13:16 +00:00
|
|
|
|
2018-02-26 14:36:56 +00:00
|
|
|
return kfrags
|
2018-01-18 07:30:36 +00:00
|
|
|
|
2017-12-29 00:13:16 +00:00
|
|
|
|
2018-04-22 21:43:39 +00:00
|
|
|
def reencrypt(kfrag: KFrag, capsule: Capsule, params: UmbralParameters=None,
|
|
|
|
provide_proof=True, metadata: bytes=None) -> CapsuleFrag:
|
2018-04-17 10:17:14 +00:00
|
|
|
if params is None:
|
|
|
|
params = default_params()
|
2017-12-29 00:13:16 +00:00
|
|
|
|
2018-02-08 09:30:56 +00:00
|
|
|
if not capsule.verify(params):
|
|
|
|
raise capsule.NotValid
|
2017-12-29 00:13:16 +00:00
|
|
|
|
2018-05-08 21:34:11 +00:00
|
|
|
rk = kfrag._bn_key
|
|
|
|
e1 = rk * capsule._point_e
|
|
|
|
v1 = rk * capsule._point_v
|
2017-12-29 00:13:16 +00:00
|
|
|
|
2018-05-07 23:51:17 +00:00
|
|
|
cfrag = CapsuleFrag(point_e1=e1, point_v1=v1, kfrag_id=kfrag._id,
|
2018-05-08 21:34:11 +00:00
|
|
|
point_noninteractive=kfrag._point_noninteractive,
|
|
|
|
point_xcoord=kfrag._point_xcoord)
|
2018-04-17 10:17:14 +00:00
|
|
|
|
2018-04-22 21:43:39 +00:00
|
|
|
if provide_proof:
|
2018-05-06 21:33:23 +00:00
|
|
|
prove_cfrag_correctness(cfrag, kfrag, capsule, metadata, params)
|
2018-04-17 10:17:14 +00:00
|
|
|
|
2018-03-29 03:59:28 +00:00
|
|
|
return cfrag
|
2017-12-29 00:13:16 +00:00
|
|
|
|
|
|
|
|
2018-02-12 20:44:07 +00:00
|
|
|
def _encapsulate(alice_pub_key: Point, key_length=32,
|
2018-02-12 18:51:11 +00:00
|
|
|
params: UmbralParameters=None) -> Tuple[bytes, Capsule]:
|
2018-02-08 09:30:56 +00:00
|
|
|
"""Generates a symmetric key and its associated KEM ciphertext"""
|
|
|
|
params = params if params is not None else default_params()
|
2017-12-28 01:07:37 +00:00
|
|
|
|
2018-02-08 09:30:56 +00:00
|
|
|
g = params.g
|
2017-12-28 01:07:37 +00:00
|
|
|
|
2018-04-23 18:03:18 +00:00
|
|
|
priv_r = CurveBN.gen_rand(params.curve)
|
2018-02-08 09:30:56 +00:00
|
|
|
pub_r = priv_r * g
|
2017-12-28 01:07:37 +00:00
|
|
|
|
2018-04-23 18:03:18 +00:00
|
|
|
priv_u = CurveBN.gen_rand(params.curve)
|
2018-02-08 09:30:56 +00:00
|
|
|
pub_u = priv_u * g
|
2018-01-03 17:27:27 +00:00
|
|
|
|
2018-05-01 23:14:42 +00:00
|
|
|
h = CurveBN.hash(pub_r, pub_u, params=params)
|
2018-02-08 09:30:56 +00:00
|
|
|
s = priv_u + (priv_r * h)
|
2018-01-19 23:48:49 +00:00
|
|
|
|
2018-02-08 09:30:56 +00:00
|
|
|
shared_key = (priv_r + priv_u) * alice_pub_key
|
2017-12-29 00:13:16 +00:00
|
|
|
|
2018-02-08 09:30:56 +00:00
|
|
|
# Key to be used for symmetric encryption
|
|
|
|
key = kdf(shared_key, key_length)
|
2017-12-29 00:13:16 +00:00
|
|
|
|
2018-04-23 09:33:43 +00:00
|
|
|
return key, Capsule(point_e=pub_r, point_v=pub_u, bn_sig=s)
|
2017-12-29 00:13:16 +00:00
|
|
|
|
2018-01-23 10:13:08 +00:00
|
|
|
|
2018-04-23 18:03:18 +00:00
|
|
|
def _decapsulate_original(priv_key: CurveBN, capsule: Capsule, key_length=32,
|
2018-02-12 18:51:11 +00:00
|
|
|
params: UmbralParameters=None) -> bytes:
|
2018-02-08 09:30:56 +00:00
|
|
|
"""Derive the same symmetric key"""
|
|
|
|
params = params if params is not None else default_params()
|
2018-01-23 10:13:08 +00:00
|
|
|
|
2018-04-23 09:33:43 +00:00
|
|
|
shared_key = priv_key * (capsule._point_e+capsule._point_v)
|
2018-02-08 09:30:56 +00:00
|
|
|
key = kdf(shared_key, key_length)
|
2018-01-23 10:13:08 +00:00
|
|
|
|
2018-02-12 22:48:34 +00:00
|
|
|
if not capsule.verify(params):
|
|
|
|
# Check correctness of original ciphertext
|
|
|
|
# (check nº 2) at the end to avoid timing oracles
|
|
|
|
raise capsule.NotValid("Capsule verification failed.")
|
|
|
|
|
2018-02-08 09:30:56 +00:00
|
|
|
return key
|
2018-01-23 10:13:08 +00:00
|
|
|
|
|
|
|
|
2018-04-23 18:03:18 +00:00
|
|
|
def _decapsulate_reencrypted(pub_key: Point, priv_key: CurveBN,
|
2018-02-12 20:44:07 +00:00
|
|
|
orig_pub_key: Point, capsule: Capsule,
|
|
|
|
key_length=32, params: UmbralParameters=None) -> bytes:
|
2018-02-08 09:30:56 +00:00
|
|
|
"""Derive the same symmetric key"""
|
|
|
|
params = params if params is not None else default_params()
|
2018-01-23 10:13:08 +00:00
|
|
|
|
2018-05-08 21:34:11 +00:00
|
|
|
ni = capsule._point_noninteractive
|
|
|
|
d = CurveBN.hash(ni, pub_key, priv_key * ni, params=params)
|
2018-01-23 10:13:08 +00:00
|
|
|
|
2018-04-23 09:33:43 +00:00
|
|
|
e_prime = capsule._point_e_prime
|
|
|
|
v_prime = capsule._point_v_prime
|
2018-01-23 10:13:08 +00:00
|
|
|
|
2018-02-08 09:30:56 +00:00
|
|
|
shared_key = d * (e_prime + v_prime)
|
2018-01-23 10:13:08 +00:00
|
|
|
|
2018-02-08 09:30:56 +00:00
|
|
|
key = kdf(shared_key, key_length)
|
|
|
|
|
2018-04-23 09:33:43 +00:00
|
|
|
e = capsule._point_e
|
|
|
|
v = capsule._point_v
|
2018-02-08 09:30:56 +00:00
|
|
|
s = capsule._bn_sig
|
2018-05-01 23:14:42 +00:00
|
|
|
h = CurveBN.hash(e, v, params=params)
|
2018-02-08 09:30:56 +00:00
|
|
|
inv_d = ~d
|
|
|
|
|
2018-02-12 22:48:34 +00:00
|
|
|
if not (s*inv_d) * orig_pub_key == (h*e_prime) + v_prime:
|
|
|
|
raise GenericUmbralError()
|
2018-02-08 09:30:56 +00:00
|
|
|
return key
|
|
|
|
|
|
|
|
|
2018-03-31 19:12:33 +00:00
|
|
|
def encrypt(alice_pubkey: UmbralPublicKey, plaintext: bytes,
|
|
|
|
params: UmbralParameters=None) -> Tuple[bytes, Capsule]:
|
2018-02-08 09:30:56 +00:00
|
|
|
"""
|
|
|
|
Performs an encryption using the UmbralDEM object and encapsulates a key
|
|
|
|
for the sender using the public key provided.
|
|
|
|
|
|
|
|
Returns the ciphertext and the KEM Capsule.
|
|
|
|
"""
|
2018-03-31 19:12:33 +00:00
|
|
|
params = params if params is not None else default_params()
|
|
|
|
|
|
|
|
key, capsule = _encapsulate(alice_pubkey.point_key, CHACHA20_KEY_SIZE, params=params)
|
2018-02-08 09:30:56 +00:00
|
|
|
|
2018-02-27 08:08:27 +00:00
|
|
|
capsule_bytes = bytes(capsule)
|
2018-02-26 09:29:02 +00:00
|
|
|
|
2018-02-08 09:30:56 +00:00
|
|
|
dem = UmbralDEM(key)
|
2018-02-27 08:08:27 +00:00
|
|
|
ciphertext = dem.encrypt(plaintext, authenticated_data=capsule_bytes)
|
2018-02-08 09:30:56 +00:00
|
|
|
|
2018-02-12 18:51:11 +00:00
|
|
|
return ciphertext, capsule
|
2018-02-08 09:30:56 +00:00
|
|
|
|
|
|
|
|
2018-04-18 13:14:10 +00:00
|
|
|
def _open_capsule(capsule: Capsule, bob_privkey: UmbralPrivateKey,
|
2018-04-24 08:21:43 +00:00
|
|
|
alice_pubkey: UmbralPublicKey, params: UmbralParameters=None,
|
|
|
|
check_proof=True) -> bytes:
|
2018-02-08 09:30:56 +00:00
|
|
|
"""
|
|
|
|
Activates the Capsule from the attached CFrags,
|
|
|
|
opens the Capsule and returns what is inside.
|
|
|
|
|
|
|
|
This will often be a symmetric key.
|
|
|
|
"""
|
2018-03-31 19:12:33 +00:00
|
|
|
params = params if params is not None else default_params()
|
2018-03-13 12:08:30 +00:00
|
|
|
|
2018-04-18 13:14:10 +00:00
|
|
|
priv_b = bob_privkey.bn_key
|
2018-05-06 00:02:05 +00:00
|
|
|
bob_pubkey = bob_privkey.get_pubkey()
|
2018-04-18 13:14:10 +00:00
|
|
|
|
|
|
|
# TODO: Change dict for a list if issue #116 goes through
|
2018-04-24 08:21:43 +00:00
|
|
|
if check_proof:
|
|
|
|
offending_cfrags = []
|
2018-04-24 22:20:48 +00:00
|
|
|
for cfrag in capsule._attached_cfrags:
|
2018-05-06 00:02:05 +00:00
|
|
|
if not cfrag.verify_correctness(capsule, alice_pubkey,
|
|
|
|
bob_pubkey, params):
|
2018-04-24 08:21:43 +00:00
|
|
|
offending_cfrags.append(cfrag)
|
2018-04-18 20:59:27 +00:00
|
|
|
|
2018-04-24 08:21:43 +00:00
|
|
|
if offending_cfrags:
|
2018-04-23 09:33:43 +00:00
|
|
|
error_msg = "Decryption error: Some CFrags are not correct"
|
|
|
|
raise UmbralCorrectnessError(error_msg, offending_cfrags)
|
2018-03-13 12:08:30 +00:00
|
|
|
|
2018-05-08 21:34:11 +00:00
|
|
|
capsule._reconstruct_shamirs_secret(priv_b, params=params)
|
2018-03-13 12:08:30 +00:00
|
|
|
|
2018-05-06 00:02:05 +00:00
|
|
|
key = _decapsulate_reencrypted(bob_pubkey.point_key, priv_b, alice_pubkey.point_key, capsule, params=params)
|
2018-02-08 09:30:56 +00:00
|
|
|
return key
|
|
|
|
|
|
|
|
|
2018-04-24 08:21:43 +00:00
|
|
|
def decrypt(ciphertext: bytes, capsule: Capsule,
|
|
|
|
priv_key: UmbralPrivateKey, alice_pub_key: UmbralPublicKey=None,
|
|
|
|
params: UmbralParameters=None, check_proof=True) -> bytes:
|
2018-02-08 09:30:56 +00:00
|
|
|
"""
|
|
|
|
Opens the capsule and gets what's inside.
|
|
|
|
|
|
|
|
We hope that's a symmetric key, which we use to decrypt the ciphertext
|
|
|
|
and return the resulting cleartext.
|
|
|
|
"""
|
2018-03-31 19:12:33 +00:00
|
|
|
params = params if params is not None else default_params()
|
|
|
|
|
2018-02-08 09:30:56 +00:00
|
|
|
if capsule._attached_cfrags:
|
|
|
|
# Since there are cfrags attached, we assume this is Bob opening the Capsule.
|
2018-04-18 09:37:40 +00:00
|
|
|
# (i.e., this is a re-encrypted capsule)
|
2018-04-18 13:14:10 +00:00
|
|
|
|
2018-02-08 09:30:56 +00:00
|
|
|
bob_priv_key = priv_key
|
2018-04-18 13:14:10 +00:00
|
|
|
|
2018-04-24 08:21:43 +00:00
|
|
|
encapsulated_key = _open_capsule(capsule, bob_priv_key, alice_pub_key,
|
|
|
|
params=params, check_proof=check_proof)
|
2018-04-18 13:14:10 +00:00
|
|
|
dem = UmbralDEM(encapsulated_key)
|
2018-02-26 09:29:02 +00:00
|
|
|
|
2018-02-27 08:08:27 +00:00
|
|
|
original_capsule_bytes = capsule._original_to_bytes()
|
|
|
|
cleartext = dem.decrypt(ciphertext, authenticated_data=original_capsule_bytes)
|
2018-02-08 09:30:56 +00:00
|
|
|
else:
|
2018-03-14 11:00:04 +00:00
|
|
|
# Since there aren't cfrags attached, we assume this is Alice opening the Capsule.
|
2018-04-18 09:37:40 +00:00
|
|
|
# (i.e., this is an original capsule)
|
2018-04-18 13:14:10 +00:00
|
|
|
encapsulated_key = _decapsulate_original(priv_key.bn_key, capsule, params=params)
|
|
|
|
dem = UmbralDEM(encapsulated_key)
|
2018-02-26 09:29:02 +00:00
|
|
|
|
2018-02-27 08:08:27 +00:00
|
|
|
capsule_bytes = bytes(capsule)
|
|
|
|
cleartext = dem.decrypt(ciphertext, authenticated_data=capsule_bytes)
|
2018-02-12 18:51:11 +00:00
|
|
|
|
|
|
|
return cleartext
|