mirror of https://github.com/nucypher/pyUmbral.git
Merge pull request #272 from fjarri/api-updates
Api updates corresponding to post-0.2 `rust-umbral` PRspull/273/head
commit
5c9fb53ede
|
@ -50,6 +50,7 @@ Intermediate objects
|
|||
:show-inheritance:
|
||||
|
||||
.. autoclass:: VerifiedCapsuleFrag()
|
||||
:members:
|
||||
:special-members: __eq__, __hash__
|
||||
:show-inheritance:
|
||||
|
||||
|
@ -73,12 +74,16 @@ Utilities
|
|||
:show-inheritance:
|
||||
|
||||
.. autoclass:: umbral.serializable.HasSerializedSize
|
||||
:members: serialized_size
|
||||
:members:
|
||||
|
||||
.. autoclass:: umbral.serializable.Serializable
|
||||
:special-members: __bytes__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: umbral.serializable.Deserializable
|
||||
:members: from_bytes
|
||||
.. autoclass:: umbral.serializable.SerializableSecret
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: umbral.serializable.Deserializable
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import pytest
|
||||
|
||||
from umbral import encrypt, reencrypt, CapsuleFrag, Capsule, VerificationError
|
||||
from umbral import encrypt, reencrypt, CapsuleFrag, VerifiedCapsuleFrag, Capsule, VerificationError
|
||||
from umbral.curve_point import CurvePoint
|
||||
|
||||
|
||||
|
@ -116,6 +116,13 @@ def test_cfrag_str(capsule, kfrags):
|
|||
assert "CapsuleFrag" in s
|
||||
|
||||
|
||||
def test_from_verified_bytes(capsule, kfrags):
|
||||
verified_cfrag = reencrypt(capsule, kfrags[0])
|
||||
cfrag_bytes = bytes(verified_cfrag)
|
||||
verified_cfrag_back = VerifiedCapsuleFrag.from_verified_bytes(cfrag_bytes)
|
||||
assert verified_cfrag == verified_cfrag_back
|
||||
|
||||
|
||||
def test_serialized_size(capsule, kfrags):
|
||||
verified_cfrag = reencrypt(capsule, kfrags[0])
|
||||
cfrag = CapsuleFrag.from_bytes(bytes(verified_cfrag))
|
||||
|
|
|
@ -22,7 +22,7 @@ def pytest_generate_tests(metafunc):
|
|||
def _create_keypair(umbral):
|
||||
sk = umbral.SecretKey.random()
|
||||
pk = sk.public_key()
|
||||
return bytes(sk), bytes(pk)
|
||||
return sk.to_secret_bytes(), bytes(pk)
|
||||
|
||||
|
||||
def _restore_keys(umbral, sk_bytes, pk_bytes):
|
||||
|
@ -42,25 +42,32 @@ def test_keys(implementations):
|
|||
_restore_keys(umbral2, sk_bytes, pk_bytes)
|
||||
|
||||
|
||||
def _create_sk_factory_and_sk(umbral, label):
|
||||
def _create_sk_factory_and_sk(umbral, skf_label, key_label):
|
||||
skf = umbral.SecretKeyFactory.random()
|
||||
sk = skf.secret_key_by_label(label)
|
||||
return bytes(skf), bytes(sk)
|
||||
derived_skf = skf.secret_key_factory_by_label(skf_label)
|
||||
sk = derived_skf.secret_key_by_label(key_label)
|
||||
return skf.to_secret_bytes(), derived_skf.to_secret_bytes(), sk.to_secret_bytes()
|
||||
|
||||
|
||||
def _check_sk_is_same(umbral, label, skf_bytes, sk_bytes):
|
||||
def _check_sk_is_same(umbral, skf_label, key_label, skf_bytes, derived_skf_bytes, sk_bytes):
|
||||
skf = umbral.SecretKeyFactory.from_bytes(skf_bytes)
|
||||
|
||||
derived_skf_restored = umbral.SecretKeyFactory.from_bytes(derived_skf_bytes)
|
||||
derived_skf_generated = skf.secret_key_factory_by_label(skf_label)
|
||||
assert derived_skf_generated.to_secret_bytes() == derived_skf_restored.to_secret_bytes()
|
||||
|
||||
sk_restored = umbral.SecretKey.from_bytes(sk_bytes)
|
||||
sk_generated = skf.secret_key_by_label(label)
|
||||
assert sk_restored == sk_generated
|
||||
sk_generated = derived_skf_generated.secret_key_by_label(key_label)
|
||||
assert sk_restored.to_secret_bytes() == sk_generated.to_secret_bytes()
|
||||
|
||||
|
||||
def test_secret_key_factory(implementations):
|
||||
umbral1, umbral2 = implementations
|
||||
label = b'label'
|
||||
skf_label = b'skf label'
|
||||
key_label = b'key label'
|
||||
|
||||
skf_bytes, sk_bytes = _create_sk_factory_and_sk(umbral1, label)
|
||||
_check_sk_is_same(umbral2, label, skf_bytes, sk_bytes)
|
||||
skf_bytes, derived_skf_bytes, sk_bytes = _create_sk_factory_and_sk(umbral1, skf_label, key_label)
|
||||
_check_sk_is_same(umbral2, skf_label, key_label, skf_bytes, derived_skf_bytes, sk_bytes)
|
||||
|
||||
|
||||
def _encrypt(umbral, plaintext, pk_bytes):
|
||||
|
|
|
@ -36,7 +36,7 @@ def test_derive_key_from_label():
|
|||
# Check that key derivation is reproducible
|
||||
sk2 = factory.secret_key_by_label(label)
|
||||
pk2 = sk2.public_key()
|
||||
assert sk1 == sk2
|
||||
assert sk1.to_secret_bytes() == sk2.to_secret_bytes()
|
||||
assert pk1 == pk2
|
||||
|
||||
# Different labels on the same master secret create different keys
|
||||
|
@ -46,11 +46,51 @@ def test_derive_key_from_label():
|
|||
assert sk1 != sk3
|
||||
|
||||
|
||||
def test_derive_skf_from_label():
|
||||
root = SecretKeyFactory.random()
|
||||
|
||||
skf_label = b"Alice"
|
||||
|
||||
skf = root.secret_key_factory_by_label(skf_label)
|
||||
assert type(skf) == SecretKeyFactory
|
||||
|
||||
skf_same = root.secret_key_factory_by_label(skf_label)
|
||||
assert skf.to_secret_bytes() == skf_same.to_secret_bytes()
|
||||
|
||||
# Just in case, check that they produce the same secret keys too.
|
||||
key_label = b"my_healthcare_information"
|
||||
key = skf.secret_key_by_label(key_label)
|
||||
key_same = skf_same.secret_key_by_label(key_label)
|
||||
assert key.to_secret_bytes() == key_same.to_secret_bytes()
|
||||
|
||||
# Different label produces a different factory
|
||||
skf_different = root.secret_key_factory_by_label(b"Bob")
|
||||
assert skf.to_secret_bytes() != skf_different.to_secret_bytes()
|
||||
|
||||
|
||||
def test_from_secure_randomness():
|
||||
|
||||
seed = os.urandom(SecretKeyFactory.seed_size())
|
||||
skf = SecretKeyFactory.from_secure_randomness(seed)
|
||||
assert type(skf) == SecretKeyFactory
|
||||
|
||||
# Check that it can produce keys
|
||||
sk = skf.secret_key_by_label(b"key label")
|
||||
|
||||
# Wrong seed size
|
||||
|
||||
with pytest.raises(ValueError, match=f"Expected {len(seed)} bytes, got {len(seed) + 1}"):
|
||||
SecretKeyFactory.from_secure_randomness(seed + b'a')
|
||||
|
||||
with pytest.raises(ValueError, match=f"Expected {len(seed)} bytes, got {len(seed) - 1}"):
|
||||
SecretKeyFactory.from_secure_randomness(seed[:-1])
|
||||
|
||||
|
||||
def test_secret_key_serialization():
|
||||
sk = SecretKey.random()
|
||||
encoded_key = bytes(sk)
|
||||
encoded_key = sk.to_secret_bytes()
|
||||
decoded_key = SecretKey.from_bytes(encoded_key)
|
||||
assert sk == decoded_key
|
||||
assert sk.to_secret_bytes() == decoded_key.to_secret_bytes()
|
||||
|
||||
|
||||
def test_secret_key_str():
|
||||
|
@ -102,13 +142,13 @@ def test_public_key_str():
|
|||
def test_secret_key_factory_serialization():
|
||||
factory = SecretKeyFactory.random()
|
||||
|
||||
encoded_factory = bytes(factory)
|
||||
encoded_factory = factory.to_secret_bytes()
|
||||
decoded_factory = SecretKeyFactory.from_bytes(encoded_factory)
|
||||
|
||||
label = os.urandom(32)
|
||||
sk1 = factory.secret_key_by_label(label)
|
||||
sk2 = decoded_factory.secret_key_by_label(label)
|
||||
assert sk1 == sk2
|
||||
assert sk1.to_secret_bytes() == sk2.to_secret_bytes()
|
||||
|
||||
|
||||
def test_public_key_is_hashable():
|
||||
|
|
|
@ -232,6 +232,18 @@ class VerifiedCapsuleFrag(Serializable):
|
|||
def serialized_size(cls):
|
||||
return CapsuleFrag.serialized_size()
|
||||
|
||||
@classmethod
|
||||
def from_verified_bytes(cls, data) -> 'VerifiedCapsuleFrag':
|
||||
"""
|
||||
Restores a verified capsule frag directly from serialized bytes,
|
||||
skipping :py:meth:`CapsuleFrag.verify` call.
|
||||
|
||||
Intended for internal storage;
|
||||
make sure that the bytes come from a trusted source.
|
||||
"""
|
||||
cfrag = CapsuleFrag.from_bytes(data)
|
||||
return cls(cfrag)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.cfrag == other.cfrag
|
||||
|
||||
|
|
|
@ -5,10 +5,10 @@ from .curve_scalar import CurveScalar
|
|||
from .curve_point import CurvePoint
|
||||
from .dem import kdf
|
||||
from .hashing import Hash
|
||||
from .serializable import Serializable, Deserializable
|
||||
from .serializable import Serializable, SerializableSecret, Deserializable
|
||||
|
||||
|
||||
class SecretKey(Serializable, Deserializable):
|
||||
class SecretKey(SerializableSecret, Deserializable):
|
||||
"""
|
||||
Umbral secret (private) key.
|
||||
"""
|
||||
|
@ -33,9 +33,6 @@ class SecretKey(Serializable, Deserializable):
|
|||
"""
|
||||
return self._public_key
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._scalar_key == other._scalar_key
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.__class__.__name__}:..."
|
||||
|
||||
|
@ -53,7 +50,7 @@ class SecretKey(Serializable, Deserializable):
|
|||
def _from_exact_bytes(cls, data: bytes):
|
||||
return cls(CurveScalar._from_exact_bytes(data))
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
def to_secret_bytes(self) -> bytes:
|
||||
return bytes(self._scalar_key)
|
||||
|
||||
|
||||
|
@ -91,7 +88,7 @@ class PublicKey(Serializable, Deserializable):
|
|||
return hash((self.__class__, bytes(self)))
|
||||
|
||||
|
||||
class SecretKeyFactory(Serializable, Deserializable):
|
||||
class SecretKeyFactory(SerializableSecret, Deserializable):
|
||||
"""
|
||||
This class handles keyring material for Umbral, by allowing deterministic
|
||||
derivation of :py:class:`SecretKey` objects based on labels.
|
||||
|
@ -99,7 +96,7 @@ class SecretKeyFactory(Serializable, Deserializable):
|
|||
Don't use this key material directly as a key.
|
||||
"""
|
||||
|
||||
_KEY_SEED_SIZE = 64
|
||||
_KEY_SEED_SIZE = 32
|
||||
_DERIVED_KEY_SIZE = 64
|
||||
|
||||
def __init__(self, key_seed: bytes):
|
||||
|
@ -112,9 +109,32 @@ class SecretKeyFactory(Serializable, Deserializable):
|
|||
"""
|
||||
return cls(os.urandom(cls._KEY_SEED_SIZE))
|
||||
|
||||
@classmethod
|
||||
def seed_size(cls):
|
||||
"""
|
||||
Returns the seed size required by
|
||||
:py:meth:`~SecretKeyFactory.from_secure_randomness`.
|
||||
"""
|
||||
return cls._KEY_SEED_SIZE
|
||||
|
||||
@classmethod
|
||||
def from_secure_randomness(cls, seed: bytes) -> 'SecretKeyFactory':
|
||||
"""
|
||||
Creates a secret key factory using the given random bytes
|
||||
(of size :py:meth:`~SecretKeyFactory.seed_size`).
|
||||
|
||||
.. warning::
|
||||
|
||||
Make sure the given seed has been obtained
|
||||
from a cryptographically secure source of randomness!
|
||||
"""
|
||||
if len(seed) != cls.seed_size():
|
||||
raise ValueError(f"Expected {cls.seed_size()} bytes, got {len(seed)}")
|
||||
return cls(seed)
|
||||
|
||||
def secret_key_by_label(self, label: bytes) -> SecretKey:
|
||||
"""
|
||||
Creates a :py:class:`SecretKey` from the given label.
|
||||
Creates a :py:class:`SecretKey` deterministically from the given label.
|
||||
"""
|
||||
tag = b"KEY_DERIVATION/" + label
|
||||
key = kdf(self.__key_seed, self._DERIVED_KEY_SIZE, info=tag)
|
||||
|
@ -125,6 +145,14 @@ class SecretKeyFactory(Serializable, Deserializable):
|
|||
|
||||
return SecretKey(scalar_key)
|
||||
|
||||
def secret_key_factory_by_label(self, label: bytes) -> 'SecretKeyFactory':
|
||||
"""
|
||||
Creates a :py:class:`SecretKeyFactory` deterministically from the given label.
|
||||
"""
|
||||
tag = b"FACTORY_DERIVATION/" + label
|
||||
key_seed = kdf(self.__key_seed, self._KEY_SEED_SIZE, info=tag)
|
||||
return SecretKeyFactory(key_seed)
|
||||
|
||||
@classmethod
|
||||
def serialized_size(cls):
|
||||
return cls._KEY_SEED_SIZE
|
||||
|
@ -133,7 +161,7 @@ class SecretKeyFactory(Serializable, Deserializable):
|
|||
def _from_exact_bytes(cls, data: bytes):
|
||||
return cls(data)
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
def to_secret_bytes(self) -> bytes:
|
||||
return bytes(self.__key_seed)
|
||||
|
||||
def __str__(self):
|
||||
|
|
|
@ -12,7 +12,7 @@ class HasSerializedSize(ABC):
|
|||
def serialized_size(cls) -> int:
|
||||
"""
|
||||
Returns the size in bytes of the serialized representation of this object
|
||||
(obtained with ``bytes()``).
|
||||
(obtained with ``bytes()`` or ``to_secret_bytes()``).
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -85,6 +85,20 @@ class Serializable(HasSerializedSize):
|
|||
raise NotImplementedError
|
||||
|
||||
|
||||
class SerializableSecret(HasSerializedSize):
|
||||
"""
|
||||
A mixin for composable serialization of objects containing secret data.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def to_secret_bytes(self):
|
||||
"""
|
||||
Serializes the object into bytes.
|
||||
This bytestring is secret, handle with care!
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def bool_serialized_size() -> int:
|
||||
return 1
|
||||
|
||||
|
|
Loading…
Reference in New Issue