Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ Changelog

* The :mod:`X.509 verification <cryptography.x509.verification>` APIs are now
considered stable and are subject to our API stability policy.
* Added the :doc:`/cobblestone` recipe, an implementation of the
Cobblestone-128 and Cobblestone-256 instantiations of the `C2SP
chunked-encryption specification
<https://c2sp.org/chunked-encryption>`_ for streaming authenticated
encryption of large messages.
* Parsing a Signed Certificate Timestamp list now rejects encodings that
carry trailing bytes after the list or after an individual SCT, instead of
silently ignoring them.
Expand Down
185 changes: 185 additions & 0 deletions docs/cobblestone.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
Cobblestone (streaming symmetric encryption)
=============================================

.. currentmodule:: cryptography.cobblestone

Cobblestone provides authenticated symmetric encryption of large
messages — up to 4 PiB — as a stream, without ever holding the whole
message in memory. It is an implementation of the `C2SP
chunked-encryption specification`_'s two named instantiations:
**Cobblestone-128** (SHA-512 and AES-128-GCM, the recommended choice)
and **Cobblestone-256** (SHA-512 and AES-256-GCM, for environments
that mandate 256-bit keys).

.. doctest::

>>> from cryptography.cobblestone import (
... Cobblestone128Decryptor, Cobblestone128Encryptor
... )
>>> key = Cobblestone128Encryptor.generate_key()
>>> encryptor = Cobblestone128Encryptor(
... key, context=b"example-app file encryption"
... )
>>> ciphertext = encryptor.update(b"a secret message")
>>> ciphertext += encryptor.finalize()
>>> decryptor = Cobblestone128Decryptor(
... key, context=b"example-app file encryption"
... )
>>> decryptor.update(ciphertext) + decryptor.finalize()
b'a secret message'

.. class:: Cobblestone128Encryptor(key, context)

.. versionadded:: 50.0.0

Encrypts a single message under ``key`` with Cobblestone-128. Each
instance must be used for exactly one message: call :meth:`update`
(or :meth:`update_into`) any number of times, then call
:meth:`finalize` exactly once. The concatenation of the returned
bytes is the ciphertext.

:param key: A 16-byte key. This **must** be kept secret, and
**must** be uniformly random (e.g. the output of
:meth:`generate_key` — never a password). A single key may be
used to encrypt a practically unlimited number of messages.
:type key: :term:`bytes-like`
:param context: Application-provided context, bound to the
ciphertext. Decryption fails unless the same value is passed to
:class:`Cobblestone128Decryptor`. It is not secret, may be
empty, and is not part of the ciphertext, so it must be
available to the decrypting party independently. It can be used
for domain separation, e.g. ``b"myapp v2 backup encryption"``.
:type context: :term:`bytes-like`
:raises ValueError: If ``key`` is not 16 bytes.

.. staticmethod:: generate_key()

Generates a fresh 16-byte key.

:return bytes: A new key.

.. method:: update(data)

Encrypts ``data``. Data is internally buffered into 16 KiB
chunks, so between 0 and ``len(data) + 16 KiB`` bytes of
ciphertext are returned.

:param data: The data to encrypt.
:type data: :term:`bytes-like`
:return bytes: The next portion of the ciphertext.

.. method:: update_into(data, buf)

Encrypts ``data``, writing the resulting ciphertext into
``buf``, and returns the number of bytes written. This avoids
allocating a new buffer for each call.

:param data: The data to encrypt.
:type data: :term:`bytes-like`
:param buf: A writable buffer to write the ciphertext into. A
buffer of ``len(data) + len(data) // 1024 + 16456`` bytes
is always large enough.
:type buf: :term:`bytes-like`
:return int: The number of bytes written to ``buf``.
:raises ValueError: If ``buf`` is too small.

.. method:: finalize()

Encrypts the final chunk and returns the last portion of the
ciphertext. This must always be called, and the instance cannot
be used afterwards.

:return bytes: The remainder of the ciphertext.
:raises cryptography.exceptions.AlreadyFinalized: If
``finalize`` has already been called.

.. class:: Cobblestone128Decryptor(key, context)

.. versionadded:: 50.0.0

Decrypts a single message encrypted by
:class:`Cobblestone128Encryptor` with the same ``key`` and
``context``. Call :meth:`update` (or :meth:`update_into`) with the
ciphertext any number of times, then call :meth:`finalize` exactly
once. The concatenation of the returned bytes is the plaintext.

Any returned plaintext is authenticated, but until
:meth:`finalize` returns successfully the message could still turn
out to be truncated: an application acting on streamed plaintext
before that point must be prepared to discard its work if a later
call raises :class:`~cryptography.exceptions.InvalidTag`.

Once any method raises
:class:`~cryptography.exceptions.InvalidTag`, the instance is
permanently unusable and all further calls raise
:class:`~cryptography.exceptions.AlreadyFinalized`.

:param key: The 16-byte key the message was encrypted with.
:type key: :term:`bytes-like`
:param context: The context value the message was encrypted with.
:type context: :term:`bytes-like`
:raises ValueError: If ``key`` is not 16 bytes.

.. method:: update(data)

Processes ``data``, which need not be aligned to any boundary,
and returns the plaintext of all complete chunks that have been
authenticated so far.

:param data: The next portion of the ciphertext.
:type data: :term:`bytes-like`
:return bytes: The next portion of the plaintext.
:raises cryptography.exceptions.InvalidTag: If the ciphertext
was encrypted with a different key or context, or has been
modified.

.. method:: update_into(data, buf)

Like ``update``, but writes the plaintext into ``buf`` and
returns the number of bytes written.

:param data: The next portion of the ciphertext.
:type data: :term:`bytes-like`
:param buf: A writable buffer to write the plaintext into. A
buffer of ``len(data) + 16400`` bytes is always large
enough.
:type buf: :term:`bytes-like`
:return int: The number of bytes written to ``buf``.
:raises ValueError: If ``buf`` is too small.
:raises cryptography.exceptions.InvalidTag: If the ciphertext
was encrypted with a different key or context, or has been
modified. Note that in this case unauthenticated data may
have been written to ``buf`` and must not be used.

.. method:: finalize()

Decrypts and authenticates the final chunk, verifying that the
entire message has been processed, and returns the final
portion of the plaintext. This must always be called: a
successful return is what guarantees the complete message was
authentic and not truncated.

:return bytes: The remainder of the plaintext.
:raises cryptography.exceptions.InvalidTag: If the ciphertext
was truncated or otherwise modified.
:raises cryptography.exceptions.AlreadyFinalized: If
``finalize`` has already been called.

.. class:: Cobblestone256Encryptor(key, context)

.. versionadded:: 50.0.0

Exactly like :class:`Cobblestone128Encryptor`, but implements
Cobblestone-256: the ``key`` is 32 bytes and messages are encrypted
with AES-256-GCM. Use this when a 256-bit key is mandated;
otherwise Cobblestone-128 is recommended.

.. class:: Cobblestone256Decryptor(key, context)

.. versionadded:: 50.0.0

Exactly like :class:`Cobblestone128Decryptor`, but decrypts
messages produced by :class:`Cobblestone256Encryptor` with a
32-byte key.

.. _`C2SP chunked-encryption specification`: https://c2sp.org/chunked-encryption
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ hazmat layer only when necessary.
:caption: The recipes layer

fernet
cobblestone
x509/index

.. toctree::
Expand Down
4 changes: 4 additions & 0 deletions docs/spelling_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ CentOS
changelog
Changelog
ciphertext
Cobblestone
codebook
committer
committers
Expand All @@ -42,6 +43,7 @@ Decapsulate
Decapsulation
declaratively
decrypt
Decryptor
decrypts
Decrypts
decrypted
Expand All @@ -61,6 +63,7 @@ duplicative
El
embeddability
Encodings
Encryptor
endian
Euler
extendable
Expand Down Expand Up @@ -122,6 +125,7 @@ parsers
Parsers
PEM
PHC
PiB
pickleable
plaintext
Poly
Expand Down
21 changes: 21 additions & 0 deletions src/cryptography/cobblestone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# This file is dual licensed under the terms of the Apache License, Version
Comment thread
alex marked this conversation as resolved.
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.

from __future__ import annotations

from cryptography.hazmat.bindings._rust import (
cobblestone as _cobblestone,
)

Cobblestone128Decryptor = _cobblestone.Cobblestone128Decryptor
Cobblestone128Encryptor = _cobblestone.Cobblestone128Encryptor
Cobblestone256Decryptor = _cobblestone.Cobblestone256Decryptor
Cobblestone256Encryptor = _cobblestone.Cobblestone256Encryptor

__all__ = [
"Cobblestone128Decryptor",
"Cobblestone128Encryptor",
"Cobblestone256Decryptor",
"Cobblestone256Encryptor",
]
33 changes: 33 additions & 0 deletions src/cryptography/hazmat/bindings/_rust/cobblestone.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.

from cryptography.utils import Buffer

class Cobblestone128Encryptor:
def __init__(self, key: Buffer, context: Buffer) -> None: ...
@staticmethod
def generate_key() -> bytes: ...
def update(self, data: Buffer) -> bytes: ...
def update_into(self, data: Buffer, buf: Buffer) -> int: ...
def finalize(self) -> bytes: ...

class Cobblestone128Decryptor:
def __init__(self, key: Buffer, context: Buffer) -> None: ...
def update(self, data: Buffer) -> bytes: ...
def update_into(self, data: Buffer, buf: Buffer) -> int: ...
def finalize(self) -> bytes: ...

class Cobblestone256Encryptor:
def __init__(self, key: Buffer, context: Buffer) -> None: ...
@staticmethod
def generate_key() -> bytes: ...
def update(self, data: Buffer) -> bytes: ...
def update_into(self, data: Buffer, buf: Buffer) -> int: ...
def finalize(self) -> bytes: ...

class Cobblestone256Decryptor:
def __init__(self, key: Buffer, context: Buffer) -> None: ...
def update(self, data: Buffer) -> bytes: ...
def update_into(self, data: Buffer, buf: Buffer) -> int: ...
def finalize(self) -> bytes: ...
2 changes: 1 addition & 1 deletion src/rust/src/backend/aead.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ impl AesGcm {
}

#[pyo3(signature = (nonce, data, associated_data, buf))]
fn decrypt_into(
pub(crate) fn decrypt_into(
&self,
py: pyo3::Python<'_>,
nonce: CffiBuf<'_>,
Expand Down
Loading