Merge branch 'character-crypto' of github.com:jmyles/nucypher-kms into encrypting-power

pull/79/head
tuxxy 2017-10-11 14:08:42 -06:00
commit a14f83fba5
No known key found for this signature in database
GPG Key ID: 7BB971A0E89144D9
6 changed files with 155 additions and 124 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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):