def _open_capsule(capsule: Capsule, bob_private_key: UmbralPrivateKey, alice_pub_key: UmbralPublicKey, params: UmbralParameters = None) -> bytes: """ Activates the Capsule from the attached CFrags, opens the Capsule and returns what is inside. This will often be a symmetric key. """ params = params if params is not None else default_params() priv_b = bob_private_key.bn_key pub_b = priv_b * params.g pub_a = alice_pub_key.point_key capsule._reconstruct_shamirs_secret(pub_a, priv_b, params=params) key = _decapsulate_reencrypted(pub_b, priv_b, pub_a, capsule, params=params) return key
def decrypt(): if request.content_type.lower() != "application/json": abort(415) data = request.get_json() if "receiverPrivateKey" in data and "ciphertext" in data and "capsule" in data: try: start = time.perf_counter() privateKey = keys.UmbralPrivateKey.from_bytes( bytes.fromhex(data["receiverPrivateKey"])) ciphertext = bytes.fromhex(data["ciphertext"]) capsule = pre.Capsule.from_bytes(bytes.fromhex(data["capsule"]), config.default_params()) plaintext = pre.decrypt(ciphertext=ciphertext, capsule=capsule, decrypting_key=privateKey) end = time.perf_counter() time_stats_endpoints["decrypt"].append(end - start) return {"status": "ok", "plaintext": plaintext.decode("utf-8")} except Exception as e: return {"status": "error", "error": str(e)} abort(400)
def _decapsulate_reencrypted(pub_key: Point, priv_key: CurveBN, orig_pub_key: Point, capsule: Capsule, key_length=32, params: UmbralParameters=None) -> bytes: """Derive the same symmetric key""" params = params if params is not None else default_params() xcomp = capsule._point_noninteractive d = CurveBN.hash_to_bn(xcomp, pub_key, priv_key * xcomp, params=params) e_prime = capsule._point_e_prime v_prime = capsule._point_v_prime shared_key = d * (e_prime + v_prime) key = kdf(shared_key, key_length) e = capsule._point_e v = capsule._point_v s = capsule._bn_sig h = CurveBN.hash_to_bn(e, v, params=params) inv_d = ~d if not (s*inv_d) * orig_pub_key == (h*e_prime) + v_prime: raise GenericUmbralError() return key
def from_bytes(cls, key_data: bytes, params: UmbralParameters=None, password: bytes=None, _scrypt_cost: int=20): """ Loads an Umbral private key from a urlsafe base64 encoded string. Optionally, if a password is provided it will decrypt the key using nacl's Salsa20-Poly1305 and Scrypt key derivation. WARNING: RFC7914 recommends that you use a 2^20 cost value for sensitive files. Unless you changed this when you called `to_bytes`, you should not change it here. It is NOT recommended to change the `_scrypt_cost` value unless you know what you're doing. """ if params is None: params = default_params() key_bytes = base64.urlsafe_b64decode(key_data) if password: salt = key_bytes[-16:] key_bytes = key_bytes[:-16] key = Scrypt( salt=salt, length=SecretBox.KEY_SIZE, n=2**_scrypt_cost, r=8, p=1, backend=default_backend() ).derive(password) key_bytes = SecretBox(key).decrypt(key_bytes) bn_key = BigNum.from_bytes(key_bytes, params.curve) return cls(bn_key, params)
def from_rest_payload(cls, arrangement_id, rest_payload): # TODO: Use JSON instead? This is a mess. payload_splitter = BytestringSplitter(Signature) + key_splitter signature, bob_pubkey_sig, remainder = payload_splitter( rest_payload, msgpack_remainder=True) receipt_bytes, *remainder = remainder packed_capsules, packed_signatures, *remainder = remainder alice_address, alice_address_signature = remainder alice_address_signature = Signature.from_bytes(alice_address_signature) if not alice_address_signature.verify(alice_address, bob_pubkey_sig): raise cls.NotFromBob() capsules, capsule_signatures = list(), list() for capsule_bytes, capsule_signature in zip( msgpack.loads(packed_capsules), msgpack.loads(packed_signatures)): capsules.append( Capsule.from_bytes(capsule_bytes, params=default_params())) capsule_signature = Signature.from_bytes(capsule_signature) capsule_signatures.append(capsule_signature) if not capsule_signature.verify(capsule_bytes, bob_pubkey_sig): raise cls.NotFromBob() verified = signature.verify(receipt_bytes, bob_pubkey_sig) if not verified: raise cls.NotFromBob() bob = Bob.from_public_keys({SigningPower: bob_pubkey_sig}) return cls(bob, arrangement_id, capsules, capsule_signatures, alice_address, alice_address_signature, receipt_bytes, signature)
def decrypt(ciphertext: bytes, capsule: Capsule, priv_key: UmbralPrivateKey, alice_pub_key: UmbralPublicKey=None, params: UmbralParameters=None, check_proof=True) -> bytes: """ 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. """ params = params if params is not None else default_params() if capsule._attached_cfrags: # Since there are cfrags attached, we assume this is Bob opening the Capsule. # (i.e., this is a re-encrypted capsule) bob_priv_key = priv_key encapsulated_key = _open_capsule(capsule, bob_priv_key, alice_pub_key, params=params, check_proof=check_proof) dem = UmbralDEM(encapsulated_key) original_capsule_bytes = capsule._original_to_bytes() cleartext = dem.decrypt(ciphertext, authenticated_data=original_capsule_bytes) else: # Since there aren't cfrags attached, we assume this is Alice opening the Capsule. # (i.e., this is an original capsule) encapsulated_key = _decapsulate_original(priv_key.bn_key, capsule, params=params) dem = UmbralDEM(encapsulated_key) capsule_bytes = bytes(capsule) cleartext = dem.decrypt(ciphertext, authenticated_data=capsule_bytes) return cleartext
def verify_kfrag(kfrag, pubkey_a_point, pubkey_b_point, params: UmbralParameters = None): params = params if params is not None else default_params() u = params.u id = kfrag._id key = kfrag._bn_key u1 = kfrag._point_commitment z1 = kfrag._bn_sig1 z2 = kfrag._bn_sig2 ni = kfrag._point_noninteractive xcoord = kfrag._point_xcoord # We check that the commitment u1 is well-formed correct_commitment = u1 == key * u # We check the Schnorr signature over the kfrag components g_y = (z2 * params.g) + (z1 * pubkey_a_point) signature_input = (g_y, id, pubkey_a_point, pubkey_b_point, u1, ni, xcoord) valid_kfrag_signature = z1 == CurveBN.hash(*signature_input, params=params) return correct_commitment & valid_kfrag_signature
def split_rekey(priv_a: Union[UmbralPrivateKey, CurveBN], pubkey_b_point: Union[UmbralPublicKey, Point], threshold: int, N: int, params: UmbralParameters=None) -> List[KFrag]: """ 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. Returns a list of KFrags. """ params = params if params is not None else default_params() if isinstance(priv_a, UmbralPrivateKey): priv_a = priv_a.bn_key if isinstance(pubkey_b_point, UmbralPublicKey): pubkey_b_point = pubkey_b_point.point_key g = params.g pubkey_a_point = priv_a * g x = CurveBN.gen_rand(params.curve) xcomp = x * g d = CurveBN.hash(xcomp, pubkey_b_point, pubkey_b_point * x, params=params) coeffs = [priv_a * (~d)] coeffs += [CurveBN.gen_rand(params.curve) for _ in range(threshold - 1)] u = params.u g_ab = priv_a * pubkey_b_point blake2b = hashes.Hash(hashes.BLAKE2b(64), backend=backend) blake2b.update(pubkey_a_point.to_bytes()) blake2b.update(pubkey_b_point.to_bytes()) blake2b.update(g_ab.to_bytes()) hashed_dh_tuple = blake2b.finalize() kfrags = [] for _ in range(N): id_kfrag = CurveBN.gen_rand(params.curve) share_x = CurveBN.hash(id_kfrag, hashed_dh_tuple, params=params) rk = poly_eval(coeffs, share_x) u1 = rk * u y = CurveBN.gen_rand(params.curve) signature_input = [y * g, id_kfrag, pubkey_a_point, pubkey_b_point, u1, xcomp] z1 = CurveBN.hash(*signature_input, params=params) z2 = y - priv_a * z1 kfrag = KFrag(bn_id=id_kfrag, bn_key=rk, point_noninteractive=xcomp, point_commitment=u1, bn_sig1=z1, bn_sig2=z2) kfrags.append(kfrag) return kfrags
def test_cannot_attach_cfrag_without_proof(): """ However, even when properly attaching keys, we can't attach the CFrag if it is unproven. """ params = default_params() capsule = Capsule(params, point_e=Point.gen_rand(), point_v=Point.gen_rand(), bn_sig=CurveBN.gen_rand()) cfrag = CapsuleFrag( point_e1=Point.gen_rand(), point_v1=Point.gen_rand(), kfrag_id=os.urandom(10), point_precursor=Point.gen_rand(), ) key_details = capsule.set_correctness_keys( UmbralPrivateKey.gen_key().get_pubkey(), UmbralPrivateKey.gen_key().get_pubkey(), UmbralPrivateKey.gen_key().get_pubkey()) delegating_details, receiving_details, verifying_details = key_details assert all((delegating_details, receiving_details, verifying_details)) with pytest.raises(cfrag.NoProofProvided): capsule.attach_cfrag(cfrag)
def reencrypt(kfrag: KFrag, capsule: Capsule, params: UmbralParameters = None, provide_proof=True, metadata: bytes = None) -> CapsuleFrag: if params is None: params = default_params() if not capsule.verify(): raise capsule.NotValid rk = kfrag._bn_key e1 = rk * capsule._point_e v1 = rk * capsule._point_v cfrag = CapsuleFrag(point_e1=e1, point_v1=v1, kfrag_id=kfrag._id, point_noninteractive=kfrag._point_noninteractive, point_xcoord=kfrag._point_xcoord) if provide_proof: prove_cfrag_correctness(cfrag, kfrag, capsule, metadata, params) return cfrag
def verify_kfrag(kfrag, pubkey_a_point, pubkey_b_point, params: UmbralParameters = None): params = params if params is not None else default_params() u = params.u u1 = kfrag._point_commitment z1 = kfrag._bn_sig1 z2 = kfrag._bn_sig2 x = kfrag._point_noninteractive key = kfrag._bn_key # We check that the commitment u1 is well-formed correct_commitment = u1 == key * u # We check the Schnorr signature over the kfrag components g_y = (z2 * params.g) + (z1 * pubkey_a_point) kfrag_components = [ g_y, kfrag._bn_id, pubkey_a_point, pubkey_b_point, u1, x ] valid_kfrag_signature = z1 == CurveBN.hash(*kfrag_components, params=params) return correct_commitment & valid_kfrag_signature
class NotAPrivateKey: params = default_params() fake_signature = Signature.from_bytes( b'@\xbfS&\x97\xb3\x9e\x9e\xd3\\j\x9f\x0e\x8fY\x0c\xbeS\x08d\x0b%s\xf6\x17\xe2\xb6\xcd\x95u\xaapON\xd9E\xb3\x10M\xe1\xf4u\x0bL\x99q\xd6\r\x8e_\xe5I\x1e\xe5\xa2\xcf\xe5\x8be_\x077Gz' ) def public_key(self): return NotAPublicKey() def get_pubkey(self, *args, **kwargs): return self.public_key() def to_cryptography_privkey(self, *args, **kwargs): return self def sign(self, *args, **kwargs): return b'0D\x02 @\xbfS&\x97\xb3\x9e\x9e\xd3\\j\x9f\x0e\x8fY\x0c\xbeS\x08d\x0b%s\xf6\x17\xe2\xb6\xcd\x95u\xaap\x02 ON\xd9E\xb3\x10M\xe1\xf4u\x0bL\x99q\xd6\r\x8e_\xe5I\x1e\xe5\xa2\xcf\xe5\x8be_\x077Gz' @classmethod def stamp(cls, *args, **kwargs): return cls.fake_signature @classmethod def signature_bytes(cls, *args, **kwargs): return b'@\xbfS&\x97\xb3\x9e\x9e\xd3\\j\x9f\x0e\x8fY\x0c\xbeS\x08d\x0b%s\xf6\x17\xe2\xb6\xcd\x95u\xaapON\xd9E\xb3\x10M\xe1\xf4u\x0bL\x99q\xd6\r\x8e_\xe5I\x1e\xe5\xa2\xcf\xe5\x8be_\x077Gz'
def verify_kfrag(kfrag, delegating_point, signing_pubkey, receiving_point, params: UmbralParameters = None): params = params if params is not None else default_params() u = params.u id = kfrag._id key = kfrag._bn_key u1 = kfrag._point_commitment ni = kfrag._point_noninteractive xcoord = kfrag._point_xcoord # We check that the commitment u1 is well-formed correct_commitment = u1 == key * u kfrag_validity_message = bytes().join( bytes(material) for material in (id, delegating_point, receiving_point, u1, ni, xcoord)) valid_kfrag_signature = kfrag.signature.verify(kfrag_validity_message, signing_pubkey) return correct_commitment & valid_kfrag_signature
def test_extended_keccak_to_bn(testerchain, reencryption_validator): test_data = os.urandom(40) h = hash_to_curvebn(test_data, params=default_params(), hash_class=ExtendedKeccak) assert int(h) == reencryption_validator.functions.extendedKeccakToBN( test_data).call()
def from_bytes(cls, key_bytes: bytes, wrapping_key: Optional[bytes] = None, password: Optional[bytes] = None, params: Optional[UmbralParameters] = None, decoder: Optional[Callable] = None, **kwargs) -> 'UmbralPrivateKey': """ Loads an Umbral private key from bytes. Optionally, allows a decoder function to be passed as a param to decode the data provided before converting to an Umbral key. Optionally, uses a wrapping key to unwrap an encrypted Umbral private key. Alternatively, if a password is provided it will derive the wrapping key from it. """ if params is None: params = default_params() if decoder: key_bytes = decoder(key_bytes) if any((wrapping_key, password)): key_bytes = unwrap_key(wrapped_key=key_bytes, wrapping_key=wrapping_key, password=password, **kwargs) bn_key = CurveBN.from_bytes(key_bytes, params.curve) return cls(bn_key, params)
def hash_to_bn(cls, *crypto_items, params=None): params = params if params is not None else default_params() # TODO: Clean this in an upcoming cleanup of pyUmbral blake2b = hashes.Hash(hashes.BLAKE2b(64), backend=backend) for item in crypto_items: try: item_bytes = item.to_bytes() except AttributeError: if not isinstance(item, bytes): raise TypeError( "{} is not acceptable type, received {}".format( item, type(item))) item_bytes = item blake2b.update(item_bytes) i = 0 h = 0 while h < params.CURVE_MINVAL_HASH_512: blake2b_i = blake2b.copy() blake2b_i.update(i.to_bytes(params.CURVE_KEY_SIZE_BYTES, 'big')) hash_digest = blake2b_i.finalize() h = int.from_bytes(hash_digest, byteorder='big', signed=False) i += 1 hash_bn = h % int(params.order) res = cls.from_int(hash_bn, params.curve) return res
def test_cannot_set_different_keys(): """ Once a key is set on a Capsule, it can't be changed to a different key. """ params = default_params() capsule = Capsule(params, point_e=Point.gen_rand(), point_v=Point.gen_rand(), bn_sig=CurveBN.gen_rand()) capsule.set_correctness_keys( delegating=UmbralPrivateKey.gen_key().get_pubkey(), receiving=UmbralPrivateKey.gen_key().get_pubkey(), verifying=UmbralPrivateKey.gen_key().get_pubkey()) with pytest.raises(ValueError): capsule.set_correctness_keys( delegating=UmbralPrivateKey.gen_key().get_pubkey()) with pytest.raises(ValueError): capsule.set_correctness_keys( receiving=UmbralPrivateKey.gen_key().get_pubkey()) with pytest.raises(ValueError): capsule.set_correctness_keys( verifying=UmbralPrivateKey.gen_key().get_pubkey())
def _open_capsule(capsule: Capsule, bob_privkey: UmbralPrivateKey, alice_pubkey: UmbralPublicKey, params: UmbralParameters=None, check_proof=True) -> bytes: """ Activates the Capsule from the attached CFrags, opens the Capsule and returns what is inside. This will often be a symmetric key. """ params = params if params is not None else default_params() priv_b = bob_privkey.bn_key pub_b = bob_privkey.get_pubkey().point_key pub_a = alice_pubkey.point_key # TODO: Change dict for a list if issue #116 goes through if check_proof: offending_cfrags = [] for cfrag in capsule._attached_cfrags: if not _verify_correctness(capsule, cfrag, pub_a, pub_b, params): offending_cfrags.append(cfrag) if offending_cfrags: error_msg = "Decryption error: Some CFrags are not correct" raise UmbralCorrectnessError(error_msg, offending_cfrags) capsule._reconstruct_shamirs_secret(pub_a, priv_b, params=params) key = _decapsulate_reencrypted(pub_b, priv_b, pub_a, capsule, params=params) return key
def re_encrypt(): if request.content_type.lower() != "application/json": abort(415) data = request.get_json() if ("delegatorPublicKey" in data and "delegatorVerifyingKey" in data and "receiverPublicKey" in data and "capsule" in data and "reencKey" in data): try: start = time.perf_counter() delegatorPubKey = keys.UmbralPublicKey.from_hex( data["delegatorPublicKey"]) delegatorVerifKey = keys.UmbralPublicKey.from_hex( data["delegatorVerifyingKey"]) receiverPublicKey = keys.UmbralPublicKey.from_hex( data["receiverPublicKey"]) reencKey = kfrags.KFrag.from_bytes(bytes.fromhex(data["reencKey"])) capsule = pre.Capsule.from_bytes(bytes.fromhex(data["capsule"]), config.default_params()) capsule.set_correctness_keys(delegating=delegatorPubKey, receiving=receiverPublicKey, verifying=delegatorVerifKey) cfrag = pre.reencrypt(kfrag=reencKey, capsule=capsule) end = time.perf_counter() time_stats_endpoints["re_encrypt"].append(end - start) return {"status": "ok", "cfrag": cfrag.to_bytes().hex()} except Exception as e: return {"status": "error", "error": str(e)} abort(400)
def test_public_key_serialization(random_ec_curvebn1): umbral_key = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey() pub_point = umbral_key.point_key encoded_key = umbral_key.to_bytes() decoded_key = UmbralPublicKey.from_bytes(encoded_key) assert pub_point == decoded_key.point_key
def test_private_key_serialization(random_ec_curvebn1): priv_key = random_ec_curvebn1 umbral_key = UmbralPrivateKey(priv_key, default_params()) encoded_key = umbral_key.to_bytes() decoded_key = UmbralPrivateKey.from_bytes(encoded_key) assert priv_key == decoded_key.bn_key
def split_rekey(priv_a: Union[UmbralPrivateKey, BigNum], pub_b: Union[UmbralPublicKey, Point], threshold: int, N: int, params: UmbralParameters = None) -> List[KFrag]: """ 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. Returns a list of KFrags. """ params = params if params is not None else default_params() if isinstance(priv_a, UmbralPrivateKey): priv_a = priv_a.bn_key if isinstance(pub_b, UmbralPublicKey): pub_b = pub_b.point_key g = params.g pub_a = priv_a * g x = BigNum.gen_rand(params.curve) xcomp = x * g d = hash_to_bn([xcomp, pub_b, pub_b * x], params) coeffs = [priv_a * (~d)] coeffs += [BigNum.gen_rand(params.curve) for _ in range(threshold - 1)] u = params.u g_ab = priv_a * pub_b blake2b = hashes.Hash(hashes.BLAKE2b(64), backend=backend) blake2b.update(pub_a.to_bytes()) blake2b.update(pub_b.to_bytes()) blake2b.update(g_ab.to_bytes()) hashed_dh_tuple = blake2b.finalize() kfrags = [] for _ in range(N): id_kfrag = BigNum.gen_rand(params.curve) share_x = hash_to_bn([id_kfrag, hashed_dh_tuple], params) rk = poly_eval(coeffs, share_x) u1 = rk * u y = BigNum.gen_rand(params.curve) z1 = hash_to_bn([y * g, id_kfrag, pub_a, pub_b, u1, xcomp], params) z2 = y - priv_a * z1 kfrag = KFrag(id_=id_kfrag, key=rk, x=xcomp, u1=u1, z1=z1, z2=z2) kfrags.append(kfrag) return kfrags
def gen_key(cls, params: UmbralParameters=None): """ Generates a private key and returns it. """ if params is None: params = default_params() bn_key = BigNum.gen_rand(params.curve) return cls(bn_key, params)
def test_key_encoder_decoder(random_ec_curvebn1): priv_key = random_ec_curvebn1 umbral_key = UmbralPrivateKey(priv_key, default_params()) encoded_key = umbral_key.to_bytes(encoder=base64.urlsafe_b64encode) decoded_key = UmbralPrivateKey.from_bytes(encoded_key, decoder=base64.urlsafe_b64decode) assert decoded_key.to_bytes() == umbral_key.to_bytes()
def test_public_key_to_uncompressed_bytes(random_ec_curvebn1): priv_key = random_ec_curvebn1 params = default_params() pub_key = priv_key * params.g umbral_key = UmbralPublicKey(pub_key, params) key_bytes = umbral_key.to_bytes(is_compressed=False) assert len(key_bytes) == Point.expected_bytes_length(is_compressed=False)
def verify(self, params: UmbralParameters=None) -> bool: params = params if params is not None else default_params() e = self._point_e v = self._point_v s = self._bn_sig h = CurveBN.hash_to_bn(e, v, params=params) return s * params.g == v + (h * e)
def __init__(self, bn_key: BigNum, params: UmbralParameters=None): """ Initializes an Umbral private key. """ if params is None: params = default_params() self.params = params self.bn_key = bn_key
def assess_cfrag_correctness(cfrag, capsule: "Capsule", delegating_point, signing_pubkey, receiving_point, params: UmbralParameters = None): params = params if params is not None else default_params() #### ## Here are the formulaic constituents shared with `prove_cfrag_correctness`. #### e = capsule._point_e v = capsule._point_v e1 = cfrag._point_e1 v1 = cfrag._point_v1 u = params.u try: u1 = cfrag.proof._point_kfrag_commitment e2 = cfrag.proof._point_e2 v2 = cfrag.proof._point_v2 u2 = cfrag.proof._point_kfrag_pok except AttributeError: if cfrag.proof is None: raise cfrag.NoProofProvided else: raise hash_input = (e, e1, e2, v, v1, v2, u, u1, u2) if cfrag.proof.metadata is not None: hash_input += (cfrag.proof.metadata, ) h = CurveBN.hash(*hash_input, params=params) ######## ni = cfrag._point_noninteractive xcoord = cfrag._point_xcoord kfrag_id = cfrag._kfrag_id kfrag_validity_message = bytes().join( bytes(material) for material in (kfrag_id, delegating_point, receiving_point, u1, ni, xcoord)) valid_kfrag_signature = cfrag.proof.kfrag_signature.verify( kfrag_validity_message, signing_pubkey) z3 = cfrag.proof.bn_sig correct_reencryption_of_e = z3 * e == e2 + (h * e1) correct_reencryption_of_v = z3 * v == v2 + (h * v1) correct_rk_commitment = z3 * u == u2 + (h * u1) return valid_kfrag_signature \ & correct_reencryption_of_e \ & correct_reencryption_of_v \ & correct_rk_commitment
def gen_key(cls, params: Optional[UmbralParameters] = None) -> 'UmbralPrivateKey': """ Generates a private key and returns it. """ if params is None: params = default_params() bn_key = CurveBN.gen_rand(params.curve) return cls(bn_key, params)
def test_private_key_serialization_with_encryption(random_ec_curvebn1): priv_key = random_ec_curvebn1 umbral_key = keys.UmbralPrivateKey(priv_key, default_params()) encoded_key = umbral_key.to_bytes(password=b'test') decoded_key = keys.UmbralPrivateKey.from_bytes(encoded_key, password=b'test') assert priv_key == decoded_key.bn_key