Skip to content

Commit 88b3d81

Browse files
Add docs for ML-DSA-65 (#14568)
* Add doc for MLDSA * Skip ML-DSA-65 doctests when backend is not AWS-LC Signed-off-by: Facundo Tuesca <facundo.tuesca@trailofbits.com> * Use ML-DSA instead of ML-DSA-65 in docs Signed-off-by: Facundo Tuesca <facundo.tuesca@trailofbits.com> * Put ML-DSA first in docs Signed-off-by: Facundo Tuesca <facundo.tuesca@trailofbits.com> * Use backend method to check if ML-DSA is supported Signed-off-by: Facundo Tuesca <facundo.tuesca@trailofbits.com> * Add CHANGELOG entry Signed-off-by: Facundo Tuesca <facundo.tuesca@trailofbits.com> --------- Signed-off-by: Facundo Tuesca <facundo.tuesca@trailofbits.com> Co-authored-by: Alexis <alexis.challande@trailofbits.com>
1 parent a3313d7 commit 88b3d81

4 files changed

Lines changed: 238 additions & 0 deletions

File tree

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ Changelog
8989
method for computing hashes.
9090
* Added :doc:`/hazmat/primitives/hpke` support implementing :rfc:`9180` for
9191
hybrid authenticated encryption.
92+
* Added new :doc:`/hazmat/primitives/asymmetric/mldsa` module with
93+
support for ML-DSA-65 signing and verification with the AWS-LC backend.
9294

9395
.. _v46-0-6:
9496

docs/conf.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@
5050
"sphinx_inline_tabs",
5151
]
5252

53+
doctest_global_setup = """
54+
from cryptography.hazmat.backends.openssl.backend import backend as _backend
55+
"""
56+
5357
if spelling is not None:
5458
extensions.append("sphinxcontrib.spelling")
5559

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+
mldsa
2627
ed25519
2728
x25519
2829
ed448
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
.. hazmat::
2+
3+
ML-DSA signing
4+
=================
5+
6+
.. currentmodule:: cryptography.hazmat.primitives.asymmetric.mldsa
7+
8+
ML-DSA is a post-quantum digital signature algorithm based on module
9+
lattices, standardized in `FIPS 204`_.
10+
11+
Signing & Verification
12+
~~~~~~~~~~~~~~~~~~~~~~~
13+
14+
.. doctest::
15+
:skipif: not _backend.mldsa_supported()
16+
17+
>>> from cryptography.hazmat.primitives.asymmetric.mldsa import MlDsa65PrivateKey
18+
>>> private_key = MlDsa65PrivateKey.generate()
19+
>>> signature = private_key.sign(b"my authenticated message")
20+
>>> public_key = private_key.public_key()
21+
>>> public_key.verify(signature, b"my authenticated message")
22+
23+
Context-based Signing & Verification
24+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25+
26+
ML-DSA supports context strings to bind additional information to signatures.
27+
The context can be up to 255 bytes and is used to differentiate signatures in
28+
different contexts or protocols.
29+
30+
.. doctest::
31+
:skipif: not _backend.mldsa_supported()
32+
33+
>>> from cryptography.hazmat.primitives.asymmetric.mldsa import MlDsa65PrivateKey
34+
>>> private_key = MlDsa65PrivateKey.generate()
35+
>>> context = b"email-signature-v1"
36+
>>> signature = private_key.sign(b"my authenticated message", context)
37+
>>> public_key = private_key.public_key()
38+
>>> # Verification requires the same context
39+
>>> public_key.verify(signature, b"my authenticated message", context)
40+
41+
Key interfaces
42+
~~~~~~~~~~~~~~
43+
44+
.. class:: MlDsa65PrivateKey
45+
46+
.. versionadded:: 47.0
47+
48+
.. classmethod:: generate()
49+
50+
Generate an ML-DSA-65 private key.
51+
52+
:returns: :class:`MlDsa65PrivateKey`
53+
54+
:raises cryptography.exceptions.UnsupportedAlgorithm: If ML-DSA-65 is
55+
not supported by the backend ``cryptography`` is using.
56+
57+
.. classmethod:: from_seed_bytes(data)
58+
59+
Load an ML-DSA-65 private key from seed bytes.
60+
61+
:param data: 32 byte seed.
62+
:type data: :term:`bytes-like`
63+
64+
:returns: :class:`MlDsa65PrivateKey`
65+
66+
:raises ValueError: If the seed is not 32 bytes.
67+
68+
:raises cryptography.exceptions.UnsupportedAlgorithm: If ML-DSA-65 is
69+
not supported by the backend ``cryptography`` is using.
70+
71+
.. doctest::
72+
:skipif: not _backend.mldsa_supported()
73+
74+
>>> from cryptography.hazmat.primitives.asymmetric import mldsa
75+
>>> private_key = mldsa.MlDsa65PrivateKey.generate()
76+
>>> seed = private_key.private_bytes_raw()
77+
>>> same_key = mldsa.MlDsa65PrivateKey.from_seed_bytes(seed)
78+
79+
.. method:: public_key()
80+
81+
:returns: :class:`MlDsa65PublicKey`
82+
83+
.. method:: sign(data, context=None)
84+
85+
Sign the data using ML-DSA-65. An optional context string can be
86+
provided.
87+
88+
:param data: The data to sign.
89+
:type data: :term:`bytes-like`
90+
91+
:param context: An optional context string (up to 255 bytes).
92+
:type context: :term:`bytes-like` or ``None``
93+
94+
:returns bytes: The signature (3309 bytes).
95+
96+
:raises ValueError: If the context is longer than 255 bytes.
97+
98+
.. method:: private_bytes(encoding, format, encryption_algorithm)
99+
100+
Allows serialization of the key to bytes. Encoding (
101+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`,
102+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, or
103+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`) and
104+
format (
105+
:attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8`
106+
or
107+
:attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw`
108+
) are chosen to define the exact serialization.
109+
110+
This method only returns the serialization of the seed form of the
111+
private key, never the expanded one.
112+
113+
:param encoding: A value from the
114+
:class:`~cryptography.hazmat.primitives.serialization.Encoding` enum.
115+
116+
:param format: A value from the
117+
:class:`~cryptography.hazmat.primitives.serialization.PrivateFormat`
118+
enum. If the ``encoding`` is
119+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`
120+
then ``format`` must be
121+
:attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw`
122+
, otherwise it must be
123+
:attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8`.
124+
125+
:param encryption_algorithm: An instance of an object conforming to the
126+
:class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption`
127+
interface.
128+
129+
:return bytes: Serialized key.
130+
131+
.. method:: private_bytes_raw()
132+
133+
Allows serialization of the key to raw bytes. This method is a
134+
convenience shortcut for calling :meth:`private_bytes` with
135+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`
136+
encoding,
137+
:attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw`
138+
format, and
139+
:class:`~cryptography.hazmat.primitives.serialization.NoEncryption`.
140+
141+
This method only returns the seed form of the private key (32 bytes).
142+
143+
:return bytes: Raw key (32-byte seed).
144+
145+
.. class:: MlDsa65PublicKey
146+
147+
.. versionadded:: 47.0
148+
149+
.. classmethod:: from_public_bytes(data)
150+
151+
:param bytes data: 1952 byte public key.
152+
153+
:returns: :class:`MlDsa65PublicKey`
154+
155+
:raises ValueError: If the public key is not 1952 bytes.
156+
157+
:raises cryptography.exceptions.UnsupportedAlgorithm: If ML-DSA-65 is
158+
not supported by the backend ``cryptography`` is using.
159+
160+
.. doctest::
161+
:skipif: not _backend.mldsa_supported()
162+
163+
>>> from cryptography.hazmat.primitives import serialization
164+
>>> from cryptography.hazmat.primitives.asymmetric import mldsa
165+
>>> private_key = mldsa.MlDsa65PrivateKey.generate()
166+
>>> public_key = private_key.public_key()
167+
>>> public_bytes = public_key.public_bytes(
168+
... encoding=serialization.Encoding.Raw,
169+
... format=serialization.PublicFormat.Raw
170+
... )
171+
>>> loaded_public_key = mldsa.MlDsa65PublicKey.from_public_bytes(public_bytes)
172+
173+
.. method:: public_bytes(encoding, format)
174+
175+
Allows serialization of the key to bytes. Encoding (
176+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`,
177+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, or
178+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`) and
179+
format (
180+
:attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo`
181+
or
182+
:attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw`
183+
) are chosen to define the exact serialization.
184+
185+
:param encoding: A value from the
186+
:class:`~cryptography.hazmat.primitives.serialization.Encoding` enum.
187+
188+
:param format: A value from the
189+
:class:`~cryptography.hazmat.primitives.serialization.PublicFormat`
190+
enum. If the ``encoding`` is
191+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`
192+
then ``format`` must be
193+
:attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw`
194+
, otherwise it must be
195+
:attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo`.
196+
197+
:returns bytes: The public key bytes.
198+
199+
.. method:: public_bytes_raw()
200+
201+
Allows serialization of the key to raw bytes. This method is a
202+
convenience shortcut for calling :meth:`public_bytes` with
203+
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`
204+
encoding and
205+
:attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw`
206+
format.
207+
208+
:return bytes: 1952-byte raw public key.
209+
210+
.. method:: verify(signature, data, context=None)
211+
212+
Verify a signature using ML-DSA-65. If a context string was used during
213+
signing, the same context must be provided for verification to succeed.
214+
215+
:param signature: The signature to verify.
216+
:type signature: :term:`bytes-like`
217+
218+
:param data: The data to verify.
219+
:type data: :term:`bytes-like`
220+
221+
:param context: An optional context string (up to 255 bytes) that was
222+
used during signing.
223+
:type context: :term:`bytes-like` or ``None``
224+
225+
:returns: None
226+
:raises cryptography.exceptions.InvalidSignature: Raised when the
227+
signature cannot be verified.
228+
:raises ValueError: If the context is longer than 255 bytes.
229+
230+
231+
.. _`FIPS 204`: https://csrc.nist.gov/pubs/fips/204/final

0 commit comments

Comments
 (0)