mirror of https://github.com/nucypher/nucypher.git
Merge branch 'character-crypto' of github.com:jmyles/nucypher-kms into encrypting-power
commit
a14f83fba5
|
@ -88,9 +88,9 @@ class Character(object):
|
||||||
|
|
||||||
if sign:
|
if sign:
|
||||||
if sign_cleartext:
|
if sign_cleartext:
|
||||||
signature = self.keyring.sign(cleartext)
|
signature = self.seal(cleartext)
|
||||||
else:
|
else:
|
||||||
signature = self.keyring.sign(ciphertext)
|
signature = self.seal(ciphertext)
|
||||||
else:
|
else:
|
||||||
signature = NOT_SIGNED
|
signature = NOT_SIGNED
|
||||||
|
|
||||||
|
|
|
@ -1,98 +0,0 @@
|
||||||
from npre import umbral
|
|
||||||
|
|
||||||
|
|
||||||
class EncryptingKeypair(object):
|
|
||||||
KEYSIZE = 32
|
|
||||||
|
|
||||||
def __init__(self, privkey=None):
|
|
||||||
self.pre = umbral.PRE()
|
|
||||||
|
|
||||||
if not privkey:
|
|
||||||
self.priv_key = self.pre.gen_priv()
|
|
||||||
else:
|
|
||||||
self.priv_key = privkey
|
|
||||||
self._pub_key = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pub_key(self):
|
|
||||||
if self._pub_key is None:
|
|
||||||
self._pub_key = self.pre.priv2pub(self.priv_key)
|
|
||||||
return self._pub_key
|
|
||||||
|
|
||||||
def generate_key(self, pubkey=None):
|
|
||||||
"""
|
|
||||||
Generate a raw symmetric key and its encrypted counterpart.
|
|
||||||
|
|
||||||
:rtype: Tuple(bytes, bytes)
|
|
||||||
:return: Tuple of the raw encrypted key and the encrypted key
|
|
||||||
"""
|
|
||||||
pubkey = pubkey or self.pub_key
|
|
||||||
symm_key, enc_symm_key = self.pre.encapsulate(pubkey)
|
|
||||||
return (symm_key, enc_symm_key)
|
|
||||||
|
|
||||||
def decrypt_key(self, enc_key, privkey=None):
|
|
||||||
"""
|
|
||||||
Decrypts an ECIES encrypted symmetric key.
|
|
||||||
|
|
||||||
:param int enc_key: The ECIES encrypted key as an integer
|
|
||||||
:param bytes privkey: The privkey to decapsulate from
|
|
||||||
|
|
||||||
:rtype: int
|
|
||||||
:return: Decrypted key as an integer
|
|
||||||
"""
|
|
||||||
priv_key = privkey or self.priv_key
|
|
||||||
return self.pre.decapsulate(priv_key, enc_key)
|
|
||||||
|
|
||||||
def rekey(self, privkey_a, privkey_b):
|
|
||||||
"""
|
|
||||||
Generates a re-encryption key in interactive mode.
|
|
||||||
|
|
||||||
:param bytes privkey_a: Alice's private key
|
|
||||||
:param bytes privkey_b: Bob's private key (or an ephemeral privkey)
|
|
||||||
|
|
||||||
:rtype: bytes
|
|
||||||
:return: Bytestring of a re-encryption key
|
|
||||||
"""
|
|
||||||
return self.pre.rekey(privkey_a, privkey_b)
|
|
||||||
|
|
||||||
def split_rekey(self, privkey_a, privkey_b, min_shares, num_shares):
|
|
||||||
"""
|
|
||||||
Generates key shares that can be used to re-encrypt data. Requires
|
|
||||||
`min_shares` to be able to successfully combine data for full key.
|
|
||||||
|
|
||||||
:param int privkey_a: Alice's private key
|
|
||||||
:param int privkey_b: Bob's private key (or an ephemeral privkey)
|
|
||||||
:param int min_shares: Threshold of shares needed to reconstruct key
|
|
||||||
:param int num_shares: Total number of shares to generate
|
|
||||||
|
|
||||||
:rtype: List(RekeyFrag)
|
|
||||||
:return: List of `num_shares` RekeyFrags
|
|
||||||
"""
|
|
||||||
return self.pre.split_rekey(privkey_a, privkey_b, min_shares,
|
|
||||||
num_shares)
|
|
||||||
|
|
||||||
def combine(self, shares):
|
|
||||||
"""
|
|
||||||
Reconstructs a secret from the given shares.
|
|
||||||
|
|
||||||
:param list shares: List of secret share fragments.
|
|
||||||
|
|
||||||
:rtype: EncryptedKey
|
|
||||||
:return: EncryptedKey from `shares`
|
|
||||||
"""
|
|
||||||
# TODO: What to do if not enough shares, or invalid?
|
|
||||||
return self.pre.combine(shares)
|
|
||||||
|
|
||||||
def reencrypt(self, reenc_key, ciphertext):
|
|
||||||
"""
|
|
||||||
Re-encrypts the provided ciphertext for the recipient of the generated
|
|
||||||
re-encryption key.
|
|
||||||
|
|
||||||
:param bytes reenc_key: The re-encryption key from the proxy to Bob
|
|
||||||
:param bytes ciphertext: The ciphertext to re-encrypt to Bob
|
|
||||||
|
|
||||||
:rtype: bytes
|
|
||||||
:return: Re-encrypted ciphertext
|
|
||||||
"""
|
|
||||||
return self.pre.reencrypt(reenc_key, ciphertext)
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import sha3
|
||||||
from nacl.secret import SecretBox
|
from nacl.secret import SecretBox
|
||||||
from nacl.utils import random
|
from nacl.utils import random
|
||||||
|
|
||||||
from nkms.crypto.keypairs import EncryptingKeypair
|
from nkms.crypto.powers import EncryptingKeypair
|
||||||
from npre import umbral
|
from npre import umbral
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from random import SystemRandom
|
from random import SystemRandom
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
from py_ecc.secp256k1 import N, privtopub, ecdsa_raw_sign, ecdsa_raw_recover
|
from py_ecc.secp256k1 import N, privtopub
|
||||||
|
|
||||||
from nkms.crypto import api
|
from nkms.crypto import api
|
||||||
from nkms.crypto.keypairs import EncryptingKeypair
|
from npre import umbral
|
||||||
|
|
||||||
|
|
||||||
class PowerUpError(TypeError):
|
class PowerUpError(TypeError):
|
||||||
|
@ -36,21 +36,25 @@ class CryptoPower(object):
|
||||||
power_up_class = power_up
|
power_up_class = power_up
|
||||||
power_up_instance = power_up()
|
power_up_instance = power_up()
|
||||||
else:
|
else:
|
||||||
raise TypeError("power_up must be a subclass of CryptoPowerUp or an instance of a subclass of CryptoPowerUp.")
|
raise TypeError(
|
||||||
|
"power_up must be a subclass of CryptoPowerUp or an instance of a subclass of CryptoPowerUp.")
|
||||||
self._power_ups[power_up_class] = power_up_instance
|
self._power_ups[power_up_class] = power_up_instance
|
||||||
|
|
||||||
if power_up.confers_public_key:
|
if power_up.confers_public_key:
|
||||||
self.public_keys[power_up_class] = power_up_instance.public_key() # TODO: Make this an ID for later lookup on a KeyStore.
|
self.public_keys[
|
||||||
|
power_up_class] = power_up_instance.public_key() # TODO: Make this an ID for later lookup on a KeyStore.
|
||||||
|
|
||||||
def pubkey_sig_bytes(self):
|
def pubkey_sig_bytes(self):
|
||||||
try:
|
try:
|
||||||
return self._power_ups[SigningKeypair].pubkey_bytes() # TODO: Turn this into an ID lookup on a KeyStore.
|
return self._power_ups[
|
||||||
|
SigningKeypair].pubkey_bytes() # TODO: Turn this into an ID lookup on a KeyStore.
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise NoSigningPower
|
raise NoSigningPower
|
||||||
|
|
||||||
def pubkey_sig_tuple(self):
|
def pubkey_sig_tuple(self):
|
||||||
try:
|
try:
|
||||||
return self._power_ups[SigningKeypair].pub_key # TODO: Turn this into an ID lookup on a KeyStore.
|
return self._power_ups[
|
||||||
|
SigningKeypair].pub_key # TODO: Turn this into an ID lookup on a KeyStore.
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise NoSigningPower
|
raise NoSigningPower
|
||||||
|
|
||||||
|
@ -83,10 +87,10 @@ class CryptoPowerUp(object):
|
||||||
"""
|
"""
|
||||||
Gives you MORE CryptoPower!
|
Gives you MORE CryptoPower!
|
||||||
"""
|
"""
|
||||||
|
confers_public_key = False
|
||||||
|
|
||||||
|
|
||||||
class SigningKeypair(CryptoPowerUp):
|
class SigningKeypair(CryptoPowerUp):
|
||||||
|
|
||||||
confers_public_key = True
|
confers_public_key = True
|
||||||
|
|
||||||
def __init__(self, privkey_bytes=None):
|
def __init__(self, privkey_bytes=None):
|
||||||
|
@ -119,3 +123,103 @@ class SigningKeypair(CryptoPowerUp):
|
||||||
|
|
||||||
def public_key(self):
|
def public_key(self):
|
||||||
return self.pub_key
|
return self.pub_key
|
||||||
|
|
||||||
|
|
||||||
|
class EncryptingKeypair(CryptoPowerUp):
|
||||||
|
KEYSIZE = 32
|
||||||
|
confers_public_key = True
|
||||||
|
|
||||||
|
def __init__(self, privkey=None):
|
||||||
|
self.pre = umbral.PRE()
|
||||||
|
|
||||||
|
if not privkey:
|
||||||
|
self.priv_key = self.pre.gen_priv()
|
||||||
|
else:
|
||||||
|
self.priv_key = privkey
|
||||||
|
self._pub_key = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pub_key(self):
|
||||||
|
if self._pub_key is None:
|
||||||
|
self._pub_key = self.pre.priv2pub(self.priv_key)
|
||||||
|
return self._pub_key
|
||||||
|
|
||||||
|
def generate_key(self, pubkey=None):
|
||||||
|
"""
|
||||||
|
Generate a raw symmetric key and its encrypted counterpart.
|
||||||
|
|
||||||
|
:rtype: Tuple(bytes, bytes)
|
||||||
|
:return: Tuple of the raw encrypted key and the encrypted key
|
||||||
|
"""
|
||||||
|
pubkey = pubkey or self.pub_key
|
||||||
|
symm_key, enc_symm_key = self.pre.encapsulate(pubkey)
|
||||||
|
return (symm_key, enc_symm_key)
|
||||||
|
|
||||||
|
def decrypt_key(self, enc_key, privkey=None):
|
||||||
|
"""
|
||||||
|
Decrypts an ECIES encrypted symmetric key.
|
||||||
|
|
||||||
|
:param int enc_key: The ECIES encrypted key as an integer
|
||||||
|
:param bytes privkey: The privkey to decapsulate from
|
||||||
|
|
||||||
|
:rtype: int
|
||||||
|
:return: Decrypted key as an integer
|
||||||
|
"""
|
||||||
|
priv_key = privkey or self.priv_key
|
||||||
|
return self.pre.decapsulate(priv_key, enc_key)
|
||||||
|
|
||||||
|
def rekey(self, privkey_a, privkey_b):
|
||||||
|
"""
|
||||||
|
Generates a re-encryption key in interactive mode.
|
||||||
|
|
||||||
|
:param bytes privkey_a: Alice's private key
|
||||||
|
:param bytes privkey_b: Bob's private key (or an ephemeral privkey)
|
||||||
|
|
||||||
|
:rtype: bytes
|
||||||
|
:return: Bytestring of a re-encryption key
|
||||||
|
"""
|
||||||
|
return self.pre.rekey(privkey_a, privkey_b)
|
||||||
|
|
||||||
|
def split_rekey(self, privkey_a, privkey_b, min_shares, num_shares):
|
||||||
|
"""
|
||||||
|
Generates key shares that can be used to re-encrypt data. Requires
|
||||||
|
`min_shares` to be able to successfully combine data for full key.
|
||||||
|
|
||||||
|
:param int privkey_a: Alice's private key
|
||||||
|
:param int privkey_b: Bob's private key (or an ephemeral privkey)
|
||||||
|
:param int min_shares: Threshold of shares needed to reconstruct key
|
||||||
|
:param int num_shares: Total number of shares to generate
|
||||||
|
|
||||||
|
:rtype: List(RekeyFrag)
|
||||||
|
:return: List of `num_shares` RekeyFrags
|
||||||
|
"""
|
||||||
|
return self.pre.split_rekey(privkey_a, privkey_b, min_shares,
|
||||||
|
num_shares)
|
||||||
|
|
||||||
|
def combine(self, shares):
|
||||||
|
"""
|
||||||
|
Reconstructs a secret from the given shares.
|
||||||
|
|
||||||
|
:param list shares: List of secret share fragments.
|
||||||
|
|
||||||
|
:rtype: EncryptedKey
|
||||||
|
:return: EncryptedKey from `shares`
|
||||||
|
"""
|
||||||
|
# TODO: What to do if not enough shares, or invalid?
|
||||||
|
return self.pre.combine(shares)
|
||||||
|
|
||||||
|
def reencrypt(self, reenc_key, ciphertext):
|
||||||
|
"""
|
||||||
|
Re-encrypts the provided ciphertext for the recipient of the generated
|
||||||
|
re-encryption key.
|
||||||
|
|
||||||
|
:param bytes reenc_key: The re-encryption key from the proxy to Bob
|
||||||
|
:param bytes ciphertext: The ciphertext to re-encrypt to Bob
|
||||||
|
|
||||||
|
:rtype: bytes
|
||||||
|
:return: Re-encrypted ciphertext
|
||||||
|
"""
|
||||||
|
return self.pre.reencrypt(reenc_key, ciphertext)
|
||||||
|
|
||||||
|
def public_key(self):
|
||||||
|
return self.pub_key
|
||||||
|
|
|
@ -2,7 +2,13 @@ import pytest
|
||||||
|
|
||||||
from nkms.characters import Alice, Ursula, Character
|
from nkms.characters import Alice, Ursula, Character
|
||||||
from nkms.crypto import api
|
from nkms.crypto import api
|
||||||
from nkms.crypto.powers import CryptoPower, SigningKeypair, NoSigningPower, NoEncryptingPower
|
from nkms.crypto.constants import NOT_SIGNED
|
||||||
|
from nkms.crypto.powers import CryptoPower, SigningKeypair, NoSigningPower, NoEncryptingPower, \
|
||||||
|
EncryptingKeypair
|
||||||
|
|
||||||
|
"""
|
||||||
|
SIGNING
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def test_actor_without_signing_power_cannot_sign():
|
def test_actor_without_signing_power_cannot_sign():
|
||||||
|
@ -68,6 +74,11 @@ def test_anybody_can_verify():
|
||||||
assert verification is True
|
assert verification is True
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
ENCRYPTION
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def test_signing_only_power_cannot_encrypt():
|
def test_signing_only_power_cannot_encrypt():
|
||||||
"""
|
"""
|
||||||
Similar to the above with signing, here we show that a Character without the EncryptingKeypair
|
Similar to the above with signing, here we show that a Character without the EncryptingKeypair
|
||||||
|
@ -79,15 +90,30 @@ def test_signing_only_power_cannot_encrypt():
|
||||||
|
|
||||||
# ..and here's Ursula, for whom our Character above wants to encrypt.
|
# ..and here's Ursula, for whom our Character above wants to encrypt.
|
||||||
ursula = Ursula()
|
ursula = Ursula()
|
||||||
ursula.pubkey_collection = {'signing': "some_privkey_sig"}
|
|
||||||
|
|
||||||
# They meet.
|
# They meet.
|
||||||
can_sign_but_not_encrypt.learn_about_actor(ursula)
|
can_sign_but_not_encrypt.learn_about_actor(ursula)
|
||||||
|
|
||||||
|
|
||||||
# The Character has the message ready...
|
# The Character has the message ready...
|
||||||
cleartext = "This is Officer Rod Farva. Come in, Ursula! Come in Ursula!"
|
cleartext = "This is Officer Rod Farva. Come in, Ursula! Come in Ursula!"
|
||||||
|
|
||||||
# But without the proper PowerUp, no encryption happens.
|
# But without the proper PowerUp, no encryption happens.
|
||||||
with pytest.raises(NoEncryptingPower) as e_info:
|
with pytest.raises(NoEncryptingPower) as e_info:
|
||||||
can_sign_but_not_encrypt.encrypt_for(ursula, cleartext)
|
can_sign_but_not_encrypt.encrypt_for(ursula, cleartext)
|
||||||
|
|
||||||
|
|
||||||
|
def test_character_with_encrypting_power_can_encrypt():
|
||||||
|
"""
|
||||||
|
Now, a Character *with* EncryptingKeyPair can encrypt.
|
||||||
|
"""
|
||||||
|
can_sign_and_encrypt = Character(crypto_power_ups=[SigningKeypair, EncryptingKeypair])
|
||||||
|
ursula = Ursula()
|
||||||
|
can_sign_and_encrypt.learn_about_actor(ursula)
|
||||||
|
|
||||||
|
cleartext = b"This is Officer Rod Farva. Come in, Ursula! Come in Ursula!"
|
||||||
|
|
||||||
|
# TODO: Make encrypt_for actually encrypt.
|
||||||
|
ciphertext, signature = can_sign_and_encrypt.encrypt_for(ursula, cleartext, sign=False)
|
||||||
|
assert signature == NOT_SIGNED
|
||||||
|
|
||||||
|
assert ciphertext is not None # annnd fail.
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import unittest
|
|
||||||
import sha3
|
|
||||||
import msgpack
|
|
||||||
import random
|
import random
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import sha3
|
||||||
|
|
||||||
from nkms.crypto import api
|
from nkms.crypto import api
|
||||||
from nkms.crypto.keypairs import EncryptingKeypair
|
from nkms.crypto.powers import SigningKeypair, EncryptingKeypair
|
||||||
from nkms.crypto.powers import SigningKeypair
|
|
||||||
|
|
||||||
|
|
||||||
class TestSigningKeypair(unittest.TestCase):
|
class TestSigningKeypair(unittest.TestCase):
|
||||||
|
@ -20,21 +19,21 @@ class TestSigningKeypair(unittest.TestCase):
|
||||||
|
|
||||||
sig = api.ecdsa_load_sig(signature)
|
sig = api.ecdsa_load_sig(signature)
|
||||||
self.assertEqual(tuple, type(sig))
|
self.assertEqual(tuple, type(sig))
|
||||||
self.assertEqual(int, type(sig[0])) # Check v
|
self.assertEqual(int, type(sig[0])) # Check v
|
||||||
self.assertEqual(int, type(sig[1])) # Check r
|
self.assertEqual(int, type(sig[1])) # Check r
|
||||||
self.assertEqual(int, type(sig[2])) # Check s
|
self.assertEqual(int, type(sig[2])) # Check s
|
||||||
|
|
||||||
def test_verification(self):
|
def test_verification(self):
|
||||||
msg_digest = sha3.keccak_256(self.msg).digest()
|
msg_digest = sha3.keccak_256(self.msg).digest()
|
||||||
signature = self.keypair_a.sign(msg_digest)
|
signature = self.keypair_a.sign(msg_digest)
|
||||||
|
|
||||||
sig = api.ecdsa_load_sig(signature)
|
sig = api.ecdsa_load_sig(signature)
|
||||||
self.assertEqual(int, type(sig[0])) # Check v
|
self.assertEqual(int, type(sig[0])) # Check v
|
||||||
self.assertEqual(int, type(sig[1])) # Check r
|
self.assertEqual(int, type(sig[1])) # Check r
|
||||||
self.assertEqual(int, type(sig[2])) # Check s
|
self.assertEqual(int, type(sig[2])) # Check s
|
||||||
|
|
||||||
verify_sig = api.ecdsa_verify(*sig, msg_digest,
|
verify_sig = api.ecdsa_verify(*sig, msg_digest,
|
||||||
self.keypair_a.pub_key)
|
self.keypair_a.pub_key)
|
||||||
self.assertTrue(verify_sig)
|
self.assertTrue(verify_sig)
|
||||||
|
|
||||||
def test_digest(self):
|
def test_digest(self):
|
||||||
|
|
Loading…
Reference in New Issue