Skip to content

Commit cd05e2a

Browse files
reaperhulkalex
authored andcommitted
ed25519 support (pyca#4114)
* ed25519 support * review feedback
1 parent 6a02279 commit cd05e2a

11 files changed

Lines changed: 782 additions & 0 deletions

File tree

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Changelog
2020
* **BACKWARDS INCOMPATIBLE**: Removed ``cryptography.x509.Certificate.serial``,
2121
which had been deprecated for nearly 3 years. Use
2222
:attr:`~cryptography.x509.Certificate.serial_number` instead.
23+
* Added support for :doc:`/hazmat/primitives/asymmetric/ed25519` when using
24+
OpenSSL 1.1.1.
2325
* Add support for easily mapping an object identifier to its elliptic curve
2426
class via
2527
:func:`~cryptography.hazmat.primitives.asymmetric.ec.get_curve_for_oid`.
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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.primitives.asymmetric.ed25519 import Ed25519PrivateKey
20+
>>> private_key = Ed25519PrivateKey.generate()
21+
>>> signature = private_key.sign(b"my authenticated message")
22+
>>> public_key = private_key.public_key()
23+
>>> # Raises InvalidSignature if verification fails
24+
>>> public_key.verify(signature, b"my authenticated message")
25+
26+
Key interfaces
27+
~~~~~~~~~~~~~~
28+
29+
.. class:: Ed25519PrivateKey
30+
31+
.. versionadded:: 2.6
32+
33+
.. classmethod:: generate()
34+
35+
Generate an Ed25519 private key.
36+
37+
:returns: :class:`Ed25519PrivateKey`
38+
39+
.. classmethod:: from_private_bytes(data)
40+
41+
:param data: 32 byte private key.
42+
:type data: :term:`bytes-like`
43+
44+
:returns: :class:`Ed25519PrivateKey`
45+
46+
.. doctest::
47+
48+
>>> from cryptography.hazmat.primitives import serialization
49+
>>> from cryptography.hazmat.primitives.asymmetric import ed25519
50+
>>> private_key = ed25519.Ed25519PrivateKey.generate()
51+
>>> private_bytes = private_key.private_bytes(
52+
... encoding=serialization.Encoding.Raw,
53+
... format=serialization.PrivateFormat.Raw,
54+
... encryption_algorithm=serialization.NoEncryption()
55+
... )
56+
>>> loaded_private_key = ed25519.Ed25519PrivateKey.from_private_bytes(private_bytes)
57+
58+
59+
.. method:: public_key()
60+
61+
:returns: :class:`Ed25519PublicKey`
62+
63+
.. method:: sign(data)
64+
65+
:param bytes data: The data to sign.
66+
67+
:returns bytes: The 64 byte signature.
68+
69+
.. method:: private_bytes(encoding, format, encryption_algorithm)
70+
71+
Allows serialization of the key to bytes. Encoding (
72+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`,
73+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, or
74+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`) and
75+
format (
76+
:attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8`
77+
or
78+
:attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw`
79+
) are chosen to define the exact serialization.
80+
81+
:param encoding: A value from the
82+
:class:`~cryptography.hazmat.primitives.serialization.Encoding` enum.
83+
84+
:param format: A value from the
85+
:class:`~cryptography.hazmat.primitives.serialization.PrivateFormat`
86+
enum. If the ``encoding`` is
87+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`
88+
then ``format`` must be
89+
:attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw`
90+
, otherwise it must be
91+
:attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8`.
92+
93+
:param encryption_algorithm: An instance of an object conforming to the
94+
:class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption`
95+
interface.
96+
97+
:return bytes: Serialized key.
98+
99+
.. class:: Ed25519PublicKey
100+
101+
.. versionadded:: 2.6
102+
103+
.. classmethod:: from_public_bytes(data)
104+
105+
:param bytes data: 32 byte public key.
106+
107+
:returns: :class:`Ed25519PublicKey`
108+
109+
.. doctest::
110+
111+
>>> from cryptography.hazmat.primitives import serialization
112+
>>> from cryptography.hazmat.primitives.asymmetric import ed25519
113+
>>> private_key = ed25519.Ed25519PrivateKey.generate()
114+
>>> public_key = private_key.public_key()
115+
>>> public_bytes = public_key.public_bytes(
116+
... encoding=serialization.Encoding.Raw,
117+
... format=serialization.PublicFormat.Raw
118+
... )
119+
>>> loaded_public_key = ed25519.Ed25519PublicKey.from_public_bytes(public_bytes)
120+
121+
.. method:: public_bytes(encoding, format)
122+
123+
Allows serialization of the key to bytes. Encoding (
124+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`,
125+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, or
126+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`) and
127+
format (
128+
:attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo`
129+
or
130+
:attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw`
131+
) are chosen to define the exact serialization.
132+
133+
:param encoding: A value from the
134+
:class:`~cryptography.hazmat.primitives.serialization.Encoding` enum.
135+
136+
:param format: A value from the
137+
:class:`~cryptography.hazmat.primitives.serialization.PublicFormat`
138+
enum. If the ``encoding`` is
139+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`
140+
then ``format`` must be
141+
:attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw`
142+
, otherwise it must be
143+
:attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo`.
144+
145+
:returns bytes: The public key bytes.
146+
147+
.. method:: verify(signature, data)
148+
149+
:param bytes signature: The signature to verify.
150+
151+
:param bytes data: The data to verify.
152+
153+
:raises cryptography.exceptions.InvalidSignature: Raised when the
154+
signature cannot be verified.
155+
156+
157+
158+
.. _`EdDSA`: https://en.wikipedia.org/wiki/EdDSA
159+
.. _`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
x448
2829
ec

docs/spelling_wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ hostname
5353
idna
5454
indistinguishability
5555
initialisms
56+
interoperability
5657
interoperable
5758
introspectability
5859
invariants

src/_cffi_src/openssl/cryptography.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@
6262
(OPENSSL_VERSION_NUMBER < 0x101000af || CRYPTOGRAPHY_IS_LIBRESSL)
6363
#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 \
6464
(OPENSSL_VERSION_NUMBER < 0x10101000 || CRYPTOGRAPHY_IS_LIBRESSL)
65+
#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B \
66+
(OPENSSL_VERSION_NUMBER < 0x10101020 || CRYPTOGRAPHY_IS_LIBRESSL)
6567
"""
6668

6769
TYPES = """
@@ -72,6 +74,7 @@
7274
static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_102I;
7375
static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_102;
7476
static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_111;
77+
static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B;
7578
7679
static const int CRYPTOGRAPHY_IS_LIBRESSL;
7780

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

Lines changed: 44 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+
_ED25519_KEY_SIZE, _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,
@@ -511,6 +514,9 @@ def _evp_pkey_to_private_key(self, evp_pkey):
511514
self.openssl_assert(dh_cdata != self._ffi.NULL)
512515
dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free)
513516
return _DHPrivateKey(self, dh_cdata, evp_pkey)
517+
elif key_type == getattr(self._lib, "EVP_PKEY_ED25519", None):
518+
# EVP_PKEY_ED25519 is not present in OpenSSL < 1.1.1
519+
return _Ed25519PrivateKey(self, evp_pkey)
514520
elif key_type == getattr(self._lib, "EVP_PKEY_X448", None):
515521
# EVP_PKEY_X448 is not present in OpenSSL < 1.1.1
516522
return _X448PrivateKey(self, evp_pkey)
@@ -548,6 +554,9 @@ def _evp_pkey_to_public_key(self, evp_pkey):
548554
self.openssl_assert(dh_cdata != self._ffi.NULL)
549555
dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free)
550556
return _DHPublicKey(self, dh_cdata, evp_pkey)
557+
elif key_type == getattr(self._lib, "EVP_PKEY_ED25519", None):
558+
# EVP_PKEY_ED25519 is not present in OpenSSL < 1.1.1
559+
return _Ed25519PublicKey(self, evp_pkey)
551560
elif key_type == getattr(self._lib, "EVP_PKEY_X448", None):
552561
# EVP_PKEY_X448 is not present in OpenSSL < 1.1.1
553562
return _X448PublicKey(self, evp_pkey)
@@ -2188,6 +2197,41 @@ def x448_generate_key(self):
21882197
def x448_supported(self):
21892198
return not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111
21902199

2200+
def ed25519_supported(self):
2201+
return not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B
2202+
2203+
def ed25519_load_public_bytes(self, data):
2204+
utils._check_bytes("data", data)
2205+
2206+
if len(data) != _ED25519_KEY_SIZE:
2207+
raise ValueError("An Ed25519 public key is 32 bytes long")
2208+
2209+
evp_pkey = self._lib.EVP_PKEY_new_raw_public_key(
2210+
self._lib.NID_ED25519, self._ffi.NULL, data, len(data)
2211+
)
2212+
self.openssl_assert(evp_pkey != self._ffi.NULL)
2213+
evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free)
2214+
2215+
return _Ed25519PublicKey(self, evp_pkey)
2216+
2217+
def ed25519_load_private_bytes(self, data):
2218+
if len(data) != _ED25519_KEY_SIZE:
2219+
raise ValueError("An Ed25519 private key is 32 bytes long")
2220+
2221+
utils._check_byteslike("data", data)
2222+
data_ptr = self._ffi.from_buffer(data)
2223+
evp_pkey = self._lib.EVP_PKEY_new_raw_private_key(
2224+
self._lib.NID_ED25519, self._ffi.NULL, data_ptr, len(data)
2225+
)
2226+
self.openssl_assert(evp_pkey != self._ffi.NULL)
2227+
evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free)
2228+
2229+
return _Ed25519PrivateKey(self, evp_pkey)
2230+
2231+
def ed25519_generate_key(self):
2232+
evp_pkey = self._evp_pkey_keygen_gc(self._lib.NID_ED25519)
2233+
return _Ed25519PrivateKey(self, evp_pkey)
2234+
21912235
def derive_scrypt(self, key_material, salt, length, n, r, p):
21922236
buf = self._ffi.new("unsigned char[]", length)
21932237
key_material_ptr = self._ffi.from_buffer(key_material)

0 commit comments

Comments
 (0)