def from_bytes(cls, capsule_bytes: bytes, curve: ec.EllipticCurve = None): """ Instantiates a Capsule object from the serialized data. """ curve = curve if curve is not None else default_curve() key_size = get_curve_keysize_bytes(curve) capsule_buff = BytesIO(capsule_bytes) # BigNums are the keysize in bytes, Points are compressed and the # keysize + 1 bytes long. if len(capsule_bytes) == 197: eph_e = Point.from_bytes(capsule_buff.read(key_size + 1), curve) eph_v = Point.from_bytes(capsule_buff.read(key_size + 1), curve) sig = BigNum.from_bytes(capsule_buff.read(key_size), curve) e_prime = Point.from_bytes(capsule_buff.read(key_size + 1), curve) v_prime = Point.from_bytes(capsule_buff.read(key_size + 1), curve) eph_ni = Point.from_bytes(capsule_buff.read(key_size + 1), curve) else: eph_e = Point.from_bytes(capsule_buff.read(key_size + 1), curve) eph_v = Point.from_bytes(capsule_buff.read(key_size + 1), curve) sig = BigNum.from_bytes(capsule_buff.read(key_size), curve) e_prime = v_prime = eph_ni = None return cls(point_eph_e=eph_e, point_eph_v=eph_v, bn_sig=sig, e_prime=e_prime, v_prime=v_prime, noninteractive_point=eph_ni)
def _decapsulate_reencrypted(pub_key: Point, priv_key: BigNum, 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 = BigNum.hash_to_bn(xcomp, pub_key, priv_key * xcomp, params=params) e_prime = capsule._point_eph_e_prime v_prime = capsule._point_eph_v_prime shared_key = d * (e_prime + v_prime) key = kdf(shared_key, key_length) e = capsule._point_eph_e v = capsule._point_eph_v s = capsule._bn_sig h = BigNum.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 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 test_cast_bignum_to_int(): x = BigNum.gen_rand() x_as_int_from_dunder = x.__int__() x_as_int_type_caster = int(x) assert x_as_int_from_dunder == x_as_int_type_caster x = x_as_int_type_caster y = BigNum.from_int(x) assert x == y
def _check_challenge(capsule: Capsule, cfrag: CapsuleFrag, challenge_resp: ChallengeResponse, pub_a: Point, pub_b: Point, challenge_metadata: bytes = None, params: UmbralParameters = None) -> bool: params = params if params is not None else default_params() e = capsule._point_eph_e v = capsule._point_eph_v e1 = cfrag.point_eph_e1 v1 = cfrag.point_eph_v1 xcomp = cfrag.point_eph_ni kfrag_id = cfrag.bn_kfrag_id e2 = challenge_resp.point_eph_e2 v2 = challenge_resp.point_eph_v2 g = params.g u = params.u u1 = challenge_resp.point_kfrag_commitment u2 = challenge_resp.point_kfrag_pok z1 = challenge_resp.bn_kfrag_sig1 z2 = challenge_resp.bn_kfrag_sig2 z3 = challenge_resp.bn_sig g_y = (z2 * g) + (z1 * pub_a) hash_input = [e, e1, e2, v, v1, v2, u, u1, u2] if challenge_metadata is not None: hash_input.append(challenge_metadata) h = BigNum.hash_to_bn(*hash_input, params=params) check31 = z1 == BigNum.hash_to_bn(g_y, kfrag_id, pub_a, pub_b, u1, xcomp, params=params) check32 = z3 * e == e2 + (h * e1) check33 = z3 * u == u2 + (h * u1) check34 = z3 * v == v2 + (h * v1) return check31 & check32 & check33 & check34
def split_rekey( priv_a: Union[UmbralPrivateKey, BigNum], pub_b: Union[UmbralPublicKey, Point], threshold: int, N: int, params: UmbralParameters = None) -> Tuple[List[KFrag], List[Point]]: """ Creates a re-encryption key and splits it using Shamir's Secret Sharing. Requires a threshold number of fragments out of N to rebuild rekey. Returns rekeys and the vKeys. """ 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)] h = params.h u = params.u v_keys = [coeff * h for coeff in coeffs] rk_shares = [] for _ in range(N): id_kfrag = BigNum.gen_rand(params.curve) rk = poly_eval(coeffs, id_kfrag) 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) rk_shares.append(kFrag) return rk_shares, v_keys
def gen_rand(cls, curve: ec.EllipticCurve = None): """ Returns a Point object with a cryptographically secure EC_POINT based on the provided curve. """ curve = curve if curve is not None else default_curve() curve_nid = backend._elliptic_curve_to_nid(curve) group = backend._lib.EC_GROUP_new_by_curve_name(curve_nid) backend.openssl_assert(group != backend._ffi.NULL) generator = backend._lib.EC_GROUP_get0_generator(group) backend.openssl_assert(generator != backend._ffi.NULL) rand_point = backend._lib.EC_POINT_new(group) backend.openssl_assert(rand_point != backend._ffi.NULL) rand_point = backend._ffi.gc(rand_point, backend._lib.EC_POINT_clear_free) rand_bn = BigNum.gen_rand(curve).bignum with backend._tmp_bn_ctx() as bn_ctx: res = backend._lib.EC_POINT_mul(group, rand_point, backend._ffi.NULL, generator, rand_bn, bn_ctx) backend.openssl_assert(res == 1) return Point(rand_point, curve_nid, group)
def test_alice_sends_fake_kfrag_to_ursula(N, M): priv_key_alice = keys.UmbralPrivateKey.gen_key() pub_key_alice = priv_key_alice.get_pubkey() priv_key_bob = keys.UmbralPrivateKey.gen_key() pub_key_bob = priv_key_bob.get_pubkey() plaintext = b'attack at dawn' ciphertext, capsule = umbral.encrypt(pub_key_alice, plaintext) cleartext = umbral.decrypt(capsule, priv_key_alice, ciphertext) assert cleartext == plaintext k_frags, vkeys = umbral.split_rekey(priv_key_alice, pub_key_bob, M, N) # Alice tries to frame the first Ursula by sending her a random kFrag k_frags[0].bn_key = BigNum.gen_rand() for k_frag in k_frags: c_frag = umbral.reencrypt(k_frag, capsule) capsule.attach_cfrag(c_frag) with pytest.raises(Exception): _ = umbral.decrypt(capsule, priv_key_bob, ciphertext, pub_key_alice)
def test_capsule_equality(): one_capsule = Capsule(point_eph_e=Point.gen_rand(), point_eph_v=Point.gen_rand(), bn_sig=BigNum.gen_rand()) another_capsule = Capsule(point_eph_e=Point.gen_rand(), point_eph_v=Point.gen_rand(), bn_sig=BigNum.gen_rand()) assert one_capsule != another_capsule activated_capsule = Capsule(e_prime=Point.gen_rand(), v_prime=Point.gen_rand(), noninteractive_point=Point.gen_rand()) assert activated_capsule != one_capsule
def _reconstruct_shamirs_secret(self, pub_a: Union[UmbralPublicKey, Point], priv_b: Union[UmbralPrivateKey, BigNum], params: UmbralParameters = None) -> None: params = params if params is not None else default_params() if isinstance(priv_b, UmbralPrivateKey): priv_b = priv_b.bn_key if isinstance(pub_a, UmbralPublicKey): pub_a = pub_a.point_key g = params.g pub_b = priv_b * g g_ab = priv_b * pub_a 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() id_cfrag_pairs = list(self._attached_cfrags.items()) id_0, cfrag_0 = id_cfrag_pairs[0] x_0 = BigNum.hash_to_bn(id_0, hashed_dh_tuple, params=params) if len(id_cfrag_pairs) > 1: xs = [ BigNum.hash_to_bn(_id, hashed_dh_tuple, params=params) for _id in self._attached_cfrags.keys() ] lambda_0 = lambda_coeff(x_0, xs) e = lambda_0 * cfrag_0.point_eph_e1 v = lambda_0 * cfrag_0.point_eph_v1 for id_i, cfrag in id_cfrag_pairs[1:]: x_i = BigNum.hash_to_bn(id_i, hashed_dh_tuple, params=params) lambda_i = lambda_coeff(x_i, xs) e = e + (lambda_i * cfrag.point_eph_e1) v = v + (lambda_i * cfrag.point_eph_v1) else: e = cfrag_0.point_eph_e1 v = cfrag_0.point_eph_v1 self._point_eph_e_prime = e self._point_eph_v_prime = v self._point_noninteractive = cfrag_0.point_eph_ni
def verify(self, params: UmbralParameters = None) -> bool: params = params if params is not None else default_params() e = self._point_eph_e v = self._point_eph_v s = self._bn_sig h = BigNum.hash_to_bn(e, v, params=params) return s * params.g == v + (h * e)
def from_bytes(data: bytes, curve: ec.EllipticCurve = None): """ Instantiate a KFrag object from the serialized data. """ curve = curve if curve is not None else default_curve() key_size = get_curve_keysize_bytes(curve) data = BytesIO(data) # BigNums are the keysize in bytes, Points are compressed and the # keysize + 1 bytes long. id = BigNum.from_bytes(data.read(key_size), curve) key = BigNum.from_bytes(data.read(key_size), curve) eph_ni = Point.from_bytes(data.read(key_size + 1), curve) commitment = Point.from_bytes(data.read(key_size + 1), curve) sig1 = BigNum.from_bytes(data.read(key_size), curve) sig2 = BigNum.from_bytes(data.read(key_size), curve) return KFrag(id, key, eph_ni, commitment, sig1, sig2)
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 _challenge(kfrag: KFrag, capsule: Capsule, cfrag: CapsuleFrag, challenge_metadata: bytes = None, params: UmbralParameters = None) -> ChallengeResponse: params = params if params is not None else default_params() e1 = cfrag.point_eph_e1 v1 = cfrag.point_eph_v1 e = capsule._point_eph_e v = capsule._point_eph_v u = params.u u1 = kfrag.point_commitment t = BigNum.gen_rand(params.curve) e2 = t * e v2 = t * v u2 = t * u hash_input = [e, e1, e2, v, v1, v2, u, u1, u2] if challenge_metadata is not None: hash_input.append(challenge_metadata) h = BigNum.hash_to_bn(*hash_input, params=params) z3 = t + h * kfrag.bn_key ch_resp = ChallengeResponse(e2=e2, v2=v2, u1=u1, u2=u2, z1=kfrag.bn_sig1, z2=kfrag.bn_sig2, z3=z3) # Check correctness of original ciphertext (check nº 2) at the end # to avoid timing oracles if not capsule.verify(params): raise capsule.NotValid("Capsule verification failed.") return ch_resp
def from_bytes(cls, data: bytes, curve: ec.EllipticCurve = None): """ Instantiate ChallengeResponse from serialized data. """ curve = curve if curve is not None else default_curve() key_size = get_curve_keysize_bytes(curve) data = BytesIO(data) # BigNums are the keysize in bytes, Points are compressed and the # keysize + 1 bytes long. e2 = Point.from_bytes(data.read(key_size + 1), curve) v2 = Point.from_bytes(data.read(key_size + 1), curve) kfrag_commitment = Point.from_bytes(data.read(key_size + 1), curve) kfrag_pok = Point.from_bytes(data.read(key_size + 1), curve) kfrag_sig1 = BigNum.from_bytes(data.read(key_size), curve) kfrag_sig2 = BigNum.from_bytes(data.read(key_size), curve) sig = BigNum.from_bytes(data.read(key_size), curve) return cls(e2, v2, kfrag_commitment, kfrag_pok, kfrag_sig1, kfrag_sig2, sig)
def test_bad_capsule_fails_reencryption(alices_keys): priv_key_alice, pub_key_alice = alices_keys kfrags = pre.split_rekey(priv_key_alice, pub_key_alice, 1, 2) bollocks_capsule = Capsule(point_eph_e=Point.gen_rand(), point_eph_v=Point.gen_rand(), bn_sig=BigNum.gen_rand()) with pytest.raises(Capsule.NotValid): pre.reencrypt(kfrags[0], bollocks_capsule)
def _encapsulate(alice_pub_key: Point, key_length=32, params: UmbralParameters = None) -> Tuple[bytes, Capsule]: """Generates a symmetric key and its associated KEM ciphertext""" params = params if params is not None else default_params() g = params.g priv_r = BigNum.gen_rand(params.curve) pub_r = priv_r * g priv_u = BigNum.gen_rand(params.curve) pub_u = priv_u * g h = hash_to_bn([pub_r, pub_u], params) s = priv_u + (priv_r * h) shared_key = (priv_r + priv_u) * alice_pub_key # Key to be used for symmetric encryption key = kdf(shared_key, key_length) return key, Capsule(point_eph_e=pub_r, point_eph_v=pub_u, bn_sig=s)
def from_bytes(data: bytes, curve: ec.EllipticCurve = None): """ Instantiates a CapsuleFrag object from the serialized data. """ curve = curve if curve is not None else default_curve() key_size = get_curve_keysize_bytes(curve) data = BytesIO(data) # BigNums are the keysize in bytes, Points are compressed and the # keysize + 1 bytes long. e1 = Point.from_bytes(data.read(key_size + 1), curve) v1 = Point.from_bytes(data.read(key_size + 1), curve) kfrag_id = BigNum.from_bytes(data.read(key_size), curve) eph_ni = Point.from_bytes(data.read(key_size + 1), curve) return CapsuleFrag(e1, v1, kfrag_id, eph_ni)
def test_capsule_creation(alices_keys): with pytest.raises(TypeError): rare_capsule = Capsule() # Alice cannot make a capsule this way. # Some users may create capsules their own way. custom_capsule = Capsule(point_eph_e=Point.gen_rand(), point_eph_v=Point.gen_rand(), bn_sig=BigNum.gen_rand()) assert isinstance(custom_capsule, Capsule) # Typical Alice, constructing a typical capsule _, alices_public_key = alices_keys plaintext = b'peace at dawn' ciphertext, typical_capsule = pre.encrypt(alices_public_key, plaintext) assert isinstance(typical_capsule, Capsule)
def derive_privkey_by_label(self, label: bytes, salt: bytes = None, params: UmbralParameters = None): """ Derives an UmbralPrivateKey using a KDF from this instance of UmbralKeyingMaterial, a label, and an optional salt. """ params = params if params is not None else default_params() key_material = HKDF(algorithm=hashes.BLAKE2b(64), length=64, salt=salt, info=b"NuCypherKMS/KeyDerivation/" + label, backend=default_backend()).derive( self.keying_material) bn_key = BigNum.hash_to_bn(key_material, params=params) return UmbralPrivateKey(bn_key, params)
def test_cannot_create_capsule_from_bogus_material(alices_keys): with pytest.raises(TypeError): capsule_of_questionable_parentage = pre.Capsule(point_eph_e=Point.gen_rand(), point_eph_v=42, bn_sig=BigNum.gen_rand()) with pytest.raises(TypeError): capsule_of_questionable_parentage = pre.Capsule(point_eph_e=Point.gen_rand(), point_eph_v=Point.gen_rand(), bn_sig=42) with pytest.raises(TypeError): capsule_of_questionable_parentage = pre.Capsule(e_prime=Point.gen_rand(), v_prime=42, noninteractive_point=Point.gen_rand()) with pytest.raises(TypeError): capsule_of_questionable_parentage = pre.Capsule(e_prime=Point.gen_rand(), v_prime=Point.gen_rand(), noninteractive_point=42)
def from_bytes(cls, data, curve: ec.EllipticCurve = None): """ Returns a Point object from the given byte data on the curve provided. """ curve = curve if curve is not None else default_curve() try: curve_nid = backend._elliptic_curve_to_nid(curve) except AttributeError: # Presume that the user passed in the curve_nid curve_nid = curve # Check if compressed if data[0] in [2, 3]: type_y = data[0] - 2 if len(data[1:]) > get_curve_keysize_bytes(curve): raise ValueError("X coordinate too large for curve.") affine_x = BigNum.from_bytes(data[1:], curve) ec_point = backend._lib.EC_POINT_new(affine_x.group) backend.openssl_assert(ec_point != backend._ffi.NULL) ec_point = backend._ffi.gc(ec_point, backend._lib.EC_POINT_clear_free) with backend._tmp_bn_ctx() as bn_ctx: res = backend._lib.EC_POINT_set_compressed_coordinates_GFp( affine_x.group, ec_point, affine_x.bignum, type_y, bn_ctx) backend.openssl_assert(res == 1) return cls(ec_point, curve_nid, affine_x.group) # Handle uncompressed point elif data[0] == 4: key_size = get_curve_keysize_bytes(curve) affine_x = int.from_bytes(data[1:key_size + 1], 'big') affine_y = int.from_bytes(data[1 + key_size:], 'big') return Point.from_affine((affine_x, affine_y), curve) else: raise ValueError("Invalid point serialization.")
def from_bytes(cls, key_bytes: bytes, params: UmbralParameters = None, password: bytes = None, _scrypt_cost: int = 20, decoder: Callable = None): """ 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, 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() if decoder: key_bytes = decoder(key_bytes) 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 get_order_from_curve(cls, curve: ec.EllipticCurve = None): """ Returns the order from the given curve as a BigNum. """ curve = curve if curve is not None else default_curve() try: curve_nid = backend._elliptic_curve_to_nid(curve) except AttributeError: # Presume that the user passed in the curve_nid curve_nid = curve group = backend._lib.EC_GROUP_new_by_curve_name(curve_nid) backend.openssl_assert(group != backend._ffi.NULL) order = backend._lib.BN_new() backend.openssl_assert(order != backend._ffi.NULL) order = backend._ffi.gc(order, backend._lib.BN_clear_free) with backend._tmp_bn_ctx() as bn_ctx: res = backend._lib.EC_GROUP_get_order(group, order, bn_ctx) backend.openssl_assert(res == 1) return BigNum(order, curve_nid, group, order)
def test_point_curve_mult_regression(): k256_point_bytes = b'\x03\xe0{\x1bQ\xbf@\x1f\x95\x8d\xe1\x17\xa7\xbe\x9e-G`T\xbf\xd7\x9e\xa7\x10\xc8uA\xc0z$\xc0\x92\x8a' k256_bn_bytes = b'4u\xd70-\xa0h\xdeG\xf0\x143\x06!\x91\x05{\xe4jC\n\xf1h\xed7a\xf8\x9d\xec^\x19\x8c' k256_point = Point.from_bytes(k256_point_bytes) k256_bn = BigNum.from_bytes(k256_bn_bytes) product_with_star_operator = k256_point * k256_bn # Make sure we have instantiated a new, unequal point in the same curve and group assert isinstance(product_with_star_operator, Point), "Point.__mul__ did not return a point instance" assert k256_point != product_with_star_operator assert k256_point.curve_nid == product_with_star_operator.curve_nid assert k256_point.group == product_with_star_operator.group product_bytes = b'\x03\xc9\xda\xa2\x88\xe2\xa0+\xb1N\xb6\xe6\x1c\xa5(\xe6\xe0p\xf6\xf4\xa9\xfc\xb1\xfaUV\xd3\xb3\x0e4\x94\xbe\x12' product_point = Point.from_bytes(product_bytes) assert product_with_star_operator.to_bytes() == product_bytes assert product_point == product_with_star_operator # Repeating the operation, should return the same result. product_with_star_operator_again = k256_point * k256_bn assert product_with_star_operator == product_with_star_operator_again
def random_ec_bignum2(): yield BigNum.gen_rand()
def test_bn_to_cryptography_privkey(): bn = BigNum.gen_rand() crypto_privkey = bn.to_cryptography_priv_key() assert int(bn) == crypto_privkey.private_numbers().private_value
def gen_priv(curve: ec.EllipticCurve = None) -> BigNum: curve = curve if curve is not None else default_curve() return BigNum.gen_rand(curve)