def _decapsulate_reencrypted(receiving_privkey: UmbralPrivateKey, capsule: Capsule, key_length: int = DEM_KEYSIZE) -> bytes: """Derive the same symmetric key""" params = capsule._umbral_params pub_key = receiving_privkey.get_pubkey().point_key priv_key = receiving_privkey.bn_key ni = capsule._point_noninteractive d = CurveBN.hash(ni, pub_key, priv_key * ni, 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(e, v, params=params) inv_d = ~d orig_pub_key = capsule.get_correctness_keys()['delegating'].point_key if not (s * inv_d) * orig_pub_key == (h * e_prime) + v_prime: raise GenericUmbralError() return key
def _open_capsule(capsule: Capsule, receiving_privkey: UmbralPrivateKey, check_proof: bool = True) -> bytes: """ Activates the Capsule from the attached CFrags, opens the Capsule and returns what is inside. This will often be a symmetric key. """ receiving_pubkey = receiving_privkey.get_pubkey() if check_proof: offending_cfrags = [] for cfrag in capsule._attached_cfrags: if not cfrag.verify_correctness(capsule): 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(receiving_privkey) key = _decapsulate_reencrypted(receiving_privkey, capsule) return key
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 _decapsulate_reencrypted(receiving_privkey: UmbralPrivateKey, capsule: Capsule, key_length: int = DEM_KEYSIZE) -> bytes: """Derive the same symmetric encapsulated_key""" params = capsule.params pub_key = receiving_privkey.get_pubkey().point_key priv_key = receiving_privkey.bn_key precursor = capsule._attached_cfrags[0]._point_precursor dh_point = priv_key * precursor from constant_sorrow import constants # Combination of CFrags via Shamir's Secret Sharing reconstruction if len(capsule._attached_cfrags) > 1: xs = [ CurveBN.hash(precursor, pub_key, dh_point, bytes(constants.X_COORDINATE), cfrag._kfrag_id, params=params) for cfrag in capsule._attached_cfrags ] e_summands, v_summands = list(), list() for cfrag, x in zip(capsule._attached_cfrags, xs): if precursor != cfrag._point_precursor: raise ValueError("Attached CFrags are not pairwise consistent") lambda_i = lambda_coeff(x, xs) e_summands.append(lambda_i * cfrag._point_e1) v_summands.append(lambda_i * cfrag._point_v1) e_prime = sum(e_summands[1:], e_summands[0]) v_prime = sum(v_summands[1:], v_summands[0]) else: e_prime = capsule._attached_cfrags[0]._point_e1 v_prime = capsule._attached_cfrags[0]._point_v1 # Secret value 'd' allows to make Umbral non-interactive d = CurveBN.hash(precursor, pub_key, dh_point, bytes(constants.NON_INTERACTIVE), params=params) e, v, s = capsule.components() h = CurveBN.hash(e, v, params=params) orig_pub_key = capsule.get_correctness_keys( )['delegating'].point_key # type: ignore if not (s / d) * orig_pub_key == (h * e_prime) + v_prime: raise GenericUmbralError() shared_key = d * (e_prime + v_prime) encapsulated_key = kdf(shared_key, key_length) return encapsulated_key
def create_policy(self, label: bytes, alice_privkey: UmbralPrivateKey, bob_pubkey: UmbralPublicKey, policy_expiration, m: int, n: int): """ Create a Policy with Alice granting Bob access to `label` DataSource :param label: A label to represent the policies data :param alice_privkey: Alice's private key :param bob_pubkey: Bob's public key :param policy_expiration: Datetime of policy expiration duration :param m: Minimum number of KFrags needed to rebuild ciphertext :param n: Total number of rekey shares to generate :return: The policy granted to Bob """ # This is not how this should be implemented, but I am still figuring out # the keying material and why it is randomly generated when a character is # initialized, instead of being derived from the keys like the other powers # or explained how it should be stored. d = DelegatingPower() d.umbral_keying_material = UmbralKeyingMaterial.from_bytes( alice_privkey.to_bytes() + alice_privkey.get_pubkey().to_bytes()) # Initialize Alice ALICE = Alice( crypto_power_ups=[ SigningPower(keypair=SigningKeypair(alice_privkey)), EncryptingPower(keypair=EncryptingKeypair(alice_privkey)), # DelegatingPower d ], network_middleware=RestMiddleware(), known_nodes=(self.ursula, ), federated_only=True, always_be_learning=True) # Initialize Bob BOB = Bob(crypto_power_ups=[ SigningPower(pubkey=bob_pubkey), EncryptingPower(pubkey=bob_pubkey) ], known_nodes=(self.ursula, ), federated_only=True, always_be_learning=True) # Alice grants a policy for Bob policy = ALICE.grant(BOB, label, m=m, n=n, expiration=policy_expiration) return policy
def _reconstruct_shamirs_secret(self, priv_b: UmbralPrivateKey) -> None: params = self._umbral_params g = params.g pub_b = priv_b.get_pubkey() priv_b = priv_b.bn_key cfrag_0 = self._attached_cfrags[0] id_0 = cfrag_0._kfrag_id ni = cfrag_0._point_noninteractive xcoord = cfrag_0._point_xcoord dh_xcoord = priv_b * xcoord blake2b = hashes.Hash(hashes.BLAKE2b(64), backend=backend) blake2b.update(xcoord.to_bytes()) blake2b.update(pub_b.to_bytes()) blake2b.update(dh_xcoord.to_bytes()) hashed_dh_tuple = blake2b.finalize() if len(self._attached_cfrags) > 1: xs = [ CurveBN.hash(cfrag._kfrag_id, hashed_dh_tuple, params=params) for cfrag in self._attached_cfrags ] x_0 = CurveBN.hash(id_0, hashed_dh_tuple, params=params) lambda_0 = lambda_coeff(x_0, xs) e = lambda_0 * cfrag_0._point_e1 v = lambda_0 * cfrag_0._point_v1 for cfrag in self._attached_cfrags[1:]: if (ni, xcoord) != (cfrag._point_noninteractive, cfrag._point_xcoord): raise ValueError( "Attached CFrags are not pairwise consistent") x_i = CurveBN.hash(cfrag._kfrag_id, hashed_dh_tuple, params=params) lambda_i = lambda_coeff(x_i, xs) e = e + (lambda_i * cfrag._point_e1) v = v + (lambda_i * cfrag._point_v1) else: e = cfrag_0._point_e1 v = cfrag_0._point_v1 self._point_e_prime = e self._point_v_prime = v self._point_noninteractive = ni
def _open_capsule(capsule: Capsule, bob_private_key: UmbralPrivateKey, alice_pub_key: UmbralPublicKey) -> bytes: """ Activates the Capsule from the attached CFrags, opens the Capsule and returns what is inside. This will often be a symmetric key. """ recp_pub_key = bob_private_key.get_pubkey() capsule._reconstruct_shamirs_secret() key = decapsulate_reencrypted(recp_pub_key.point_key, bob_private_key.bn_key, alice_pub_key.point_key, capsule) return key
def decrypt_by_sk(cls, sk: keys.UmbralPrivateKey, d_pk: keys.UmbralPublicKey, v_pk: keys.UmbralPublicKey, encrypt_text: bytes, capsule: pre.Capsule, k_frags): capsule.set_correctness_keys(delegating=d_pk, receiving=sk.get_pubkey(), verifying=v_pk) c_frags = list() for kFrag in k_frags: c_frags.append(pre.reencrypt(kfrag=kFrag, capsule=capsule)) for cfrag in c_frags: capsule.attach_cfrag(cfrag) decrypt_text = pre.decrypt(ciphertext=encrypt_text, capsule=capsule, decrypting_key=sk) return decrypt_text.decode("utf-8")
def split_rekey(delegating_privkey: UmbralPrivateKey, signer: Signer, receiving_pubkey: UmbralPublicKey, threshold: int, N: int) -> 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. """ if threshold <= 0 or threshold > N: raise ValueError( 'Arguments threshold and N must satisfy 0 < threshold <= N') if delegating_privkey.params != receiving_pubkey.params: raise ValueError("Keys must have the same parameter set.") params = delegating_privkey.params g = params.g pubkey_a_point = delegating_privkey.get_pubkey().point_key privkey_a_bn = delegating_privkey.bn_key pubkey_b_point = receiving_pubkey.point_key # 'ni' stands for 'Non Interactive'. # This point is used as an ephemeral public key in a DH key exchange, # and the resulting shared secret 'd' allows to make Umbral non-interactive priv_ni = CurveBN.gen_rand(params.curve) ni = priv_ni * g d = CurveBN.hash(ni, pubkey_b_point, pubkey_b_point * priv_ni, params=params) coeffs = [privkey_a_bn * (~d)] coeffs += [CurveBN.gen_rand(params.curve) for _ in range(threshold - 1)] u = params.u # 'xcoord' stands for 'X coordinate'. # This point is used as an ephemeral public key in a DH key exchange, # and the resulting shared secret 'dh_xcoord' contributes to prevent # reconstruction of the re-encryption key without Bob's intervention priv_xcoord = CurveBN.gen_rand(params.curve) xcoord = priv_xcoord * g dh_xcoord = priv_xcoord * pubkey_b_point blake2b = hashes.Hash(hashes.BLAKE2b(64), backend=backend) blake2b.update(xcoord.to_bytes()) blake2b.update(pubkey_b_point.to_bytes()) blake2b.update(dh_xcoord.to_bytes()) hashed_dh_tuple = blake2b.finalize() bn_size = CurveBN.expected_bytes_length(params.curve) kfrags = [] for _ in range(N): id = os.urandom(bn_size) share_x = CurveBN.hash(id, hashed_dh_tuple, params=params) rk = poly_eval(coeffs, share_x) u1 = rk * u kfrag_validity_message = bytes().join( bytes(material) for material in (id, pubkey_a_point, pubkey_b_point, u1, ni, xcoord)) signature = signer(kfrag_validity_message) kfrag = KFrag(id=id, bn_key=rk, point_noninteractive=ni, point_commitment=u1, point_xcoord=xcoord, signature=signature) kfrags.append(kfrag) return kfrags
def generate_kfrags( delegating_privkey: UmbralPrivateKey, receiving_pubkey: UmbralPublicKey, threshold: int, N: int, signer: Signer, sign_delegating_key: Optional[bool] = True, sign_receiving_key: Optional[bool] = True, ) -> List[KFrag]: """ Creates a re-encryption key from Alice's delegating public key to Bob's receiving public key, and splits it in KFrags, using Shamir's Secret Sharing. Requires a threshold number of KFrags out of N. Returns a list of N KFrags """ if threshold <= 0 or threshold > N: raise ValueError( 'Arguments threshold and N must satisfy 0 < threshold <= N') if delegating_privkey.params != receiving_pubkey.params: raise ValueError("Keys must have the same parameter set.") params = delegating_privkey.params g = params.g delegating_pubkey = delegating_privkey.get_pubkey() bob_pubkey_point = receiving_pubkey.point_key # The precursor point is used as an ephemeral public key in a DH key exchange, # and the resulting shared secret 'dh_point' is used to derive other secret values private_precursor = CurveBN.gen_rand(params.curve) precursor = private_precursor * g # type: Any dh_point = private_precursor * bob_pubkey_point # Secret value 'd' allows to make Umbral non-interactive d = hash_to_curvebn(precursor, bob_pubkey_point, dh_point, bytes(constants.NON_INTERACTIVE), params=params) # Coefficients of the generating polynomial coefficients = [delegating_privkey.bn_key * (~d)] coefficients += [ CurveBN.gen_rand(params.curve) for _ in range(threshold - 1) ] bn_size = CurveBN.expected_bytes_length(params.curve) kfrags = list() for _ in range(N): kfrag_id = os.urandom(bn_size) # The index of the re-encryption key share (which in Shamir's Secret # Sharing corresponds to x in the tuple (x, f(x)), with f being the # generating polynomial), is used to prevent reconstruction of the # re-encryption key without Bob's intervention share_index = hash_to_curvebn(precursor, bob_pubkey_point, dh_point, bytes(constants.X_COORDINATE), kfrag_id, params=params) # The re-encryption key share is the result of evaluating the generating # polynomial for the index value rk = poly_eval(coefficients, share_index) commitment = rk * params.u # type: Any validity_message_for_bob = ( kfrag_id, delegating_pubkey, receiving_pubkey, commitment, precursor, ) # type: Any validity_message_for_bob = bytes().join( bytes(item) for item in validity_message_for_bob) signature_for_bob = signer(validity_message_for_bob) if sign_delegating_key and sign_receiving_key: mode = DELEGATING_AND_RECEIVING elif sign_delegating_key: mode = DELEGATING_ONLY elif sign_receiving_key: mode = RECEIVING_ONLY else: mode = NO_KEY validity_message_for_proxy = [kfrag_id, commitment, precursor, mode] # type: Any if sign_delegating_key: validity_message_for_proxy.append(delegating_pubkey) if sign_receiving_key: validity_message_for_proxy.append(receiving_pubkey) validity_message_for_proxy = bytes().join( bytes(item) for item in validity_message_for_proxy) signature_for_proxy = signer(validity_message_for_proxy) kfrag = KFrag( identifier=kfrag_id, bn_key=rk, point_commitment=commitment, point_precursor=precursor, signature_for_proxy=signature_for_proxy, signature_for_bob=signature_for_bob, keys_in_signature=mode, ) kfrags.append(kfrag) return kfrags