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_cleartext:
|
||||
signature = self.keyring.sign(cleartext)
|
||||
signature = self.seal(cleartext)
|
||||
else:
|
||||
signature = self.keyring.sign(ciphertext)
|
||||
signature = self.seal(ciphertext)
|
||||
else:
|
||||
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.utils import random
|
||||
|
||||
from nkms.crypto.keypairs import EncryptingKeypair
|
||||
from nkms.crypto.powers import EncryptingKeypair
|
||||
from npre import umbral
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
from random import SystemRandom
|
||||
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.keypairs import EncryptingKeypair
|
||||
from npre import umbral
|
||||
|
||||
|
||||
class PowerUpError(TypeError):
|
||||
|
@ -36,21 +36,25 @@ class CryptoPower(object):
|
|||
power_up_class = power_up
|
||||
power_up_instance = power_up()
|
||||
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
|
||||
|
||||
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):
|
||||
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:
|
||||
raise NoSigningPower
|
||||
|
||||
def pubkey_sig_tuple(self):
|
||||
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:
|
||||
raise NoSigningPower
|
||||
|
||||
|
@ -83,10 +87,10 @@ class CryptoPowerUp(object):
|
|||
"""
|
||||
Gives you MORE CryptoPower!
|
||||
"""
|
||||
confers_public_key = False
|
||||
|
||||
|
||||
class SigningKeypair(CryptoPowerUp):
|
||||
|
||||
confers_public_key = True
|
||||
|
||||
def __init__(self, privkey_bytes=None):
|
||||
|
@ -119,3 +123,103 @@ class SigningKeypair(CryptoPowerUp):
|
|||
|
||||
def public_key(self):
|
||||
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.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():
|
||||
|
@ -68,6 +74,11 @@ def test_anybody_can_verify():
|
|||
assert verification is True
|
||||
|
||||
|
||||
"""
|
||||
ENCRYPTION
|
||||
"""
|
||||
|
||||
|
||||
def test_signing_only_power_cannot_encrypt():
|
||||
"""
|
||||
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.
|
||||
ursula = Ursula()
|
||||
ursula.pubkey_collection = {'signing': "some_privkey_sig"}
|
||||
|
||||
# They meet.
|
||||
can_sign_but_not_encrypt.learn_about_actor(ursula)
|
||||
|
||||
|
||||
# The Character has the message ready...
|
||||
cleartext = "This is Officer Rod Farva. Come in, Ursula! Come in Ursula!"
|
||||
|
||||
# But without the proper PowerUp, no encryption happens.
|
||||
with pytest.raises(NoEncryptingPower) as e_info:
|
||||
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 unittest
|
||||
|
||||
import sha3
|
||||
|
||||
from nkms.crypto import api
|
||||
from nkms.crypto.keypairs import EncryptingKeypair
|
||||
from nkms.crypto.powers import SigningKeypair
|
||||
from nkms.crypto.powers import SigningKeypair, EncryptingKeypair
|
||||
|
||||
|
||||
class TestSigningKeypair(unittest.TestCase):
|
||||
|
@ -20,21 +19,21 @@ class TestSigningKeypair(unittest.TestCase):
|
|||
|
||||
sig = api.ecdsa_load_sig(signature)
|
||||
self.assertEqual(tuple, type(sig))
|
||||
self.assertEqual(int, type(sig[0])) # Check v
|
||||
self.assertEqual(int, type(sig[1])) # Check r
|
||||
self.assertEqual(int, type(sig[2])) # Check s
|
||||
self.assertEqual(int, type(sig[0])) # Check v
|
||||
self.assertEqual(int, type(sig[1])) # Check r
|
||||
self.assertEqual(int, type(sig[2])) # Check s
|
||||
|
||||
def test_verification(self):
|
||||
msg_digest = sha3.keccak_256(self.msg).digest()
|
||||
signature = self.keypair_a.sign(msg_digest)
|
||||
|
||||
sig = api.ecdsa_load_sig(signature)
|
||||
self.assertEqual(int, type(sig[0])) # Check v
|
||||
self.assertEqual(int, type(sig[1])) # Check r
|
||||
self.assertEqual(int, type(sig[2])) # Check s
|
||||
self.assertEqual(int, type(sig[0])) # Check v
|
||||
self.assertEqual(int, type(sig[1])) # Check r
|
||||
self.assertEqual(int, type(sig[2])) # Check s
|
||||
|
||||
verify_sig = api.ecdsa_verify(*sig, msg_digest,
|
||||
self.keypair_a.pub_key)
|
||||
self.keypair_a.pub_key)
|
||||
self.assertTrue(verify_sig)
|
||||
|
||||
def test_digest(self):
|
||||
|
|
Loading…
Reference in New Issue