Skip to content

Commit b03f144

Browse files
committed
ed25519 support
1 parent 0dc5e3c commit b03f144

File tree

7 files changed

+369
-0
lines changed

7 files changed

+369
-0
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
.. hazmat::
2+
3+
Ed25519 signing
4+
===============
5+
6+
.. currentmodule:: cryptography.hazmat.primitives.asymmetric.ed25519
7+
8+
9+
Ed25519 is an elliptic curve signing algorithm using `EdDSA`_ and
10+
`Curve25519`_. If you do not have legacy interoperability concerns then you
11+
should strongly consider using this signature algorithm.
12+
13+
14+
Signing & Verification
15+
~~~~~~~~~~~~~~~~~~~~~~
16+
17+
.. doctest::
18+
19+
>>> from cryptography.hazmat.backends import default_backend
20+
>>> from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
21+
>>> private_key = Ed25519PrivateKey.generate()
22+
>>> signature = private_key.sign(b"my authenticated message")
23+
>>> public_key = private_key.public_key()
24+
>>> # Raises InvalidSignature if verification fails
25+
>>> public_key.verify(signature, b"my authenticated message")
26+
27+
Key interfaces
28+
~~~~~~~~~~~~~~
29+
30+
.. class:: Ed25519PrivateKey
31+
32+
.. versionadded:: 2.5
33+
34+
.. classmethod:: generate()
35+
36+
Generate an Ed25519 private key.
37+
38+
:returns: :class:`Ed25519PrivateKey`
39+
40+
.. method:: public_key()
41+
42+
:returns: :class:`Ed25519PublicKey`
43+
44+
.. method:: sign(data)
45+
46+
:param bytes data: The data to sign.
47+
48+
:returns bytes: The 64 byte signature.
49+
50+
.. class:: Ed25519PublicKey
51+
52+
.. versionadded:: 2.5
53+
54+
.. classmethod:: from_public_bytes(data)
55+
56+
:param bytes data: 32 byte public key.
57+
58+
:returns: :class:`Ed25519PublicKey`
59+
60+
.. method:: public_bytes()
61+
62+
:returns bytes: The raw bytes of the public key.
63+
64+
.. method:: verify(signature, data)
65+
66+
:param bytes signature: The signature to verify.
67+
68+
:param bytes data: The data to verify.
69+
70+
:raises cryptography.exceptions.InvalidSignature: Raised when the
71+
signature cannot be verified.
72+
73+
74+
75+
.. _`EdDSA`: https://en.wikipedia.org/wiki/EdDSA
76+
.. _`Curve25519`: https://en.wikipedia.org/wiki/Curve25519

docs/hazmat/primitives/asymmetric/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ private key is able to decrypt it.
2323
.. toctree::
2424
:maxdepth: 1
2525

26+
ed25519
2627
x25519
2728
ec
2829
rsa

docs/spelling_wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ hostname
5252
idna
5353
indistinguishability
5454
initialisms
55+
interoperability
5556
interoperable
5657
introspectability
5758
invariants

src/cryptography/hazmat/backends/openssl/backend.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
from cryptography.hazmat.backends.openssl.ec import (
3838
_EllipticCurvePrivateKey, _EllipticCurvePublicKey
3939
)
40+
from cryptography.hazmat.backends.openssl.ed25519 import (
41+
_Ed25519PrivateKey, _Ed25519PublicKey
42+
)
4043
from cryptography.hazmat.backends.openssl.encode_asn1 import (
4144
_CRL_ENTRY_EXTENSION_ENCODE_HANDLERS,
4245
_CRL_EXTENSION_ENCODE_HANDLERS, _EXTENSION_ENCODE_HANDLERS,
@@ -2080,6 +2083,31 @@ def x25519_generate_key(self):
20802083
def x25519_supported(self):
20812084
return self._lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER
20822085

2086+
def ed25519_supported(self):
2087+
return not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111
2088+
2089+
def ed25519_load_public_bytes(self, data):
2090+
evp_pkey = self._lib.EVP_PKEY_new_raw_public_key(
2091+
self._lib.NID_ED25519, self._ffi.NULL, data, len(data)
2092+
)
2093+
self.openssl_assert(evp_pkey != self._ffi.NULL)
2094+
evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free)
2095+
2096+
return _Ed25519PublicKey(self, evp_pkey)
2097+
2098+
def ed25519_load_private_bytes(self, data):
2099+
evp_pkey = self._lib.EVP_PKEY_new_raw_private_key(
2100+
self._lib.NID_ED25519, self._ffi.NULL, data, len(data)
2101+
)
2102+
self.openssl_assert(evp_pkey != self._ffi.NULL)
2103+
evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free)
2104+
2105+
return _Ed25519PrivateKey(self, evp_pkey)
2106+
2107+
def ed25519_generate_key(self):
2108+
evp_pkey = self._evp_pkey_keygen_gc(self._lib.NID_ED25519)
2109+
return _Ed25519PrivateKey(self, evp_pkey)
2110+
20832111
def derive_scrypt(self, key_material, salt, length, n, r, p):
20842112
buf = self._ffi.new("unsigned char[]", length)
20852113
res = self._lib.EVP_PBE_scrypt(
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# This file is dual licensed under the terms of the Apache License, Version
2+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
3+
# for complete details.
4+
5+
from __future__ import absolute_import, division, print_function
6+
7+
from cryptography import exceptions, utils
8+
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
9+
Ed25519PrivateKey, Ed25519PublicKey
10+
)
11+
12+
13+
@utils.register_interface(Ed25519PublicKey)
14+
class _Ed25519PublicKey(object):
15+
def __init__(self, backend, evp_pkey):
16+
self._backend = backend
17+
self._evp_pkey = evp_pkey
18+
19+
def public_bytes(self):
20+
bio = self._backend._create_mem_bio_gc()
21+
res = self._backend._lib.i2d_PUBKEY_bio(bio, self._evp_pkey)
22+
self._backend.openssl_assert(res == 1)
23+
asn1 = self._backend._read_mem_bio(bio)
24+
# We serialize to the ASN.1 structure defined in
25+
# https://tools.ietf.org/html/draft-ietf-curdle-pkix-03. and
26+
# then take the last 32 bytes, which are the actual key.
27+
self._backend.openssl_assert(len(asn1) == 44)
28+
return asn1[-32:]
29+
30+
def verify(self, signature, data):
31+
evp_md_ctx = self._backend._lib.Cryptography_EVP_MD_CTX_new()
32+
self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL)
33+
evp_md_ctx = self._backend._ffi.gc(
34+
evp_md_ctx, self._backend._lib.Cryptography_EVP_MD_CTX_free
35+
)
36+
res = self._backend._lib.EVP_DigestVerifyInit(
37+
evp_md_ctx, self._backend._ffi.NULL, self._backend._ffi.NULL,
38+
self._backend._ffi.NULL, self._evp_pkey
39+
)
40+
self._backend.openssl_assert(res == 1)
41+
res = self._backend._lib.EVP_DigestVerify(
42+
evp_md_ctx, signature, len(signature), data, len(data)
43+
)
44+
if res != 1:
45+
self._backend._consume_errors()
46+
raise exceptions.InvalidSignature
47+
48+
49+
@utils.register_interface(Ed25519PrivateKey)
50+
class _Ed25519PrivateKey(object):
51+
def __init__(self, backend, evp_pkey):
52+
self._backend = backend
53+
self._evp_pkey = evp_pkey
54+
55+
def public_key(self):
56+
bio = self._backend._create_mem_bio_gc()
57+
res = self._backend._lib.i2d_PUBKEY_bio(bio, self._evp_pkey)
58+
self._backend.openssl_assert(res == 1)
59+
evp_pkey = self._backend._lib.d2i_PUBKEY_bio(
60+
bio, self._backend._ffi.NULL
61+
)
62+
return _Ed25519PublicKey(self._backend, evp_pkey)
63+
64+
def sign(self, data):
65+
evp_md_ctx = self._backend._lib.Cryptography_EVP_MD_CTX_new()
66+
self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL)
67+
evp_md_ctx = self._backend._ffi.gc(
68+
evp_md_ctx, self._backend._lib.Cryptography_EVP_MD_CTX_free
69+
)
70+
res = self._backend._lib.EVP_DigestSignInit(
71+
evp_md_ctx, self._backend._ffi.NULL, self._backend._ffi.NULL,
72+
self._backend._ffi.NULL, self._evp_pkey
73+
)
74+
self._backend.openssl_assert(res == 1)
75+
buf = self._backend._ffi.new("unsigned char[]", 64)
76+
buflen = self._backend._ffi.new("size_t *", len(buf))
77+
res = self._backend._lib.EVP_DigestSign(
78+
evp_md_ctx, buf, buflen, data, len(data)
79+
)
80+
self._backend.openssl_assert(res == 1)
81+
self._backend.openssl_assert(buflen[0] == 64)
82+
return self._backend._ffi.buffer(buf, buflen[0])[:]
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# This file is dual licensed under the terms of the Apache License, Version
2+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
3+
# for complete details.
4+
5+
from __future__ import absolute_import, division, print_function
6+
7+
import abc
8+
9+
import six
10+
11+
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
12+
13+
14+
@six.add_metaclass(abc.ABCMeta)
15+
class Ed25519PublicKey(object):
16+
@classmethod
17+
def from_public_bytes(cls, data):
18+
from cryptography.hazmat.backends.openssl.backend import backend
19+
if not backend.ed25519_supported():
20+
raise UnsupportedAlgorithm(
21+
"ed25519 is not supported by this version of OpenSSL.",
22+
_Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM
23+
)
24+
25+
if len(data) != 32:
26+
raise ValueError("An Ed25519 public key is 32 bytes long")
27+
28+
return backend.ed25519_load_public_bytes(data)
29+
30+
@abc.abstractmethod
31+
def public_bytes(self):
32+
"""
33+
The serialized bytes of the public key.
34+
"""
35+
36+
@abc.abstractmethod
37+
def verify(self, signature, data):
38+
"""
39+
Verify the signature.
40+
"""
41+
42+
43+
@six.add_metaclass(abc.ABCMeta)
44+
class Ed25519PrivateKey(object):
45+
@classmethod
46+
def generate(cls):
47+
from cryptography.hazmat.backends.openssl.backend import backend
48+
if not backend.ed25519_supported():
49+
raise UnsupportedAlgorithm(
50+
"ed25519 is not supported by this version of OpenSSL.",
51+
_Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM
52+
)
53+
return backend.ed25519_generate_key()
54+
55+
@classmethod
56+
def from_private_bytes(cls, data):
57+
from cryptography.hazmat.backends.openssl.backend import backend
58+
return backend.ed25519_load_private_bytes(data)
59+
60+
@abc.abstractmethod
61+
def public_key(self):
62+
"""
63+
The Ed25519PublicKey derived from the private key.
64+
"""
65+
66+
@abc.abstractmethod
67+
def sign(self, data):
68+
"""
69+
Signs the data.
70+
"""

0 commit comments

Comments
 (0)