def generate( public_key, encryption_alg="AES-PMAC-SIV", digest_alg=hashes.SHA256(), salt=None, csrng=os.urandom, ): """ Generate an XSTREAM encryptor object with a random ephemeral key :param public_key: 32-byte X25519 public key (i.e. compressed Montgomery-u coordinate) :param encryption_alg: symmetric encryption algorithm to use with STREAM (default "AES-PMAC-SIV") :param digest_alg: digest algorithm to use with HKDF (default "SHA256") :param salt: (optional) salt value to pass to HKDF (default None) :param csrng: (optional) secure random number generator used to generate ephemeral key (default os.urandom) :return: STREAM encryptor and ephemeral public key """ ephemeral_scalar = X25519PrivateKey._from_private_bytes( csrng(X25519_KEY_SIZE)) symmetric_key = kdf( private_key=ephemeral_scalar, public_key=X25519PublicKey.from_public_bytes(public_key), digest_alg=digest_alg, length=SYMMETRIC_KEY_SIZE, salt=salt) enc = Encryptor(encryption_alg, symmetric_key, NONCE) return enc, ephemeral_scalar.public_key().public_bytes()
def __init__(self, private_key, ephemeral_public, encryption_alg="AES-PMAC-SIV", digest_alg=hashes.SHA256(), salt=None): """ Create an XSTREAM decryptor object using our private key and an ephemeral public key :param private_key: 32-byte X25519 private key (i.e. private scalar) :param ephemeral_public: 32-byte X25519 ephemeral public key from XSTREAM encryption :param encryption_alg: symmetric encryption algorithm to use with STREAM (default "AES-PMAC-SIV") :param digest_alg: digest algorithm to use with HKDF (default "SHA256") :param salt: (optional) salt value to pass to HKDF (default None) """ # Perform an X25519 elliptic curve Diffie-Hellman operation and use # the resulting shared secret to derive a symmetric key (using HKDF) symmetric_key = kdf( private_key=X25519PrivateKey._from_private_bytes(private_key), public_key=X25519PublicKey.from_public_bytes(ephemeral_public), digest_alg=digest_alg, length=SYMMETRIC_KEY_SIZE, salt=salt) super(Decryptor, self).__init__(encryption_alg, symmetric_key, NONCE)
def test_rfc7748(self, vector, backend): private = binascii.unhexlify(vector["input_scalar"]) public = binascii.unhexlify(vector["input_u"]) shared_key = binascii.unhexlify(vector["output_u"]) private_key = X25519PrivateKey._from_private_bytes(private) public_key = X25519PublicKey.from_public_bytes(public) computed_shared_key = private_key.exchange(public_key) assert computed_shared_key == shared_key
def test_rfc7748_1000_iteration(self, backend): old_private = private = public = binascii.unhexlify( b"090000000000000000000000000000000000000000000000000000000000" b"0000") shared_key = binascii.unhexlify( b"684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d9953" b"2c51") private_key = X25519PrivateKey._from_private_bytes(private) public_key = X25519PublicKey.from_public_bytes(public) for _ in range(1000): computed_shared_key = private_key.exchange(public_key) private_key = X25519PrivateKey._from_private_bytes( computed_shared_key) public_key = X25519PublicKey.from_public_bytes(old_private) old_private = computed_shared_key assert computed_shared_key == shared_key
def test_rfc7748_1000_iteration(self, backend): old_private = private = public = binascii.unhexlify( b"090000000000000000000000000000000000000000000000000000000000" b"0000" ) shared_key = binascii.unhexlify( b"684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d9953" b"2c51" ) private_key = X25519PrivateKey._from_private_bytes(private) public_key = X25519PublicKey.from_public_bytes(public) for _ in range(1000): computed_shared_key = private_key.exchange(public_key) private_key = X25519PrivateKey._from_private_bytes( computed_shared_key ) public_key = X25519PublicKey.from_public_bytes(old_private) old_private = computed_shared_key assert computed_shared_key == shared_key
def test_null_shared_key_raises_error(self, backend): """ The vector used here is taken from wycheproof's x25519 test vectors """ public = binascii.unhexlify( "5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157") private = binascii.unhexlify( "78f1e8edf14481b389448dac8f59c70b038e7cf92ef2c7eff57a72466e115296") private_key = X25519PrivateKey._from_private_bytes(private) public_key = X25519PublicKey.from_public_bytes(public) with pytest.raises(ValueError): private_key.exchange(public_key)
def test_null_shared_key_raises_error(self, backend): """ The vector used here is taken from wycheproof's x25519 test vectors """ public = binascii.unhexlify( "5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157" ) private = binascii.unhexlify( "78f1e8edf14481b389448dac8f59c70b038e7cf92ef2c7eff57a72466e115296" ) private_key = X25519PrivateKey._from_private_bytes( private ) public_key = X25519PublicKey.from_public_bytes(public) with pytest.raises(ValueError): private_key.exchange(public_key)
def test_x25519(backend, wycheproof): assert list(wycheproof.testgroup.items()) == [("curve", "curve25519")] private_key = X25519PrivateKey._from_private_bytes( binascii.unhexlify(wycheproof.testcase["private"])) public_key = X25519PublicKey.from_public_bytes( binascii.unhexlify(wycheproof.testcase["public"])) assert wycheproof.valid or wycheproof.acceptable expected = binascii.unhexlify(wycheproof.testcase["shared"]) if expected == b"\x00" * 32: assert wycheproof.acceptable # OpenSSL returns an error on all zeros shared key with pytest.raises(ValueError): private_key.exchange(public_key) else: assert private_key.exchange(public_key) == expected
def test_public_bytes(self, private_bytes, public_bytes, backend): private_key = X25519PrivateKey._from_private_bytes(private_bytes) assert private_key.public_key().public_bytes() == public_bytes public_key = X25519PublicKey.from_public_bytes(public_bytes) assert public_key.public_bytes() == public_bytes