\Drupal\strata\Crypto XChaCha20Poly1305Cipher

XChaCha20-Poly1305 through `ext-sodium`, which ships with PHP 8.3+.

Measured at 391 MB/s sealing and 392 MB/s opening on the reference host - roughly half the speed of the BLAKE2b that addresses the frame, and about a hundred times faster than the network it is written over, so it is never the bottleneck.

The nonce derivation. CipherInterface requires sealing to be deterministic, or the content-addressed store loses deduplication. A random nonce would destroy that. A fixed nonce would be worse: reusing a nonce across two different plaintexts under one key breaks XChaCha20 completely.

So the nonce is derived from the plaintext itself, through a KEYED hash:

nonce = BLAKE2b(plaintext, key = KDF(master, "strata-nonce"), 24 bytes)

Three properties follow:

  • Identical plaintexts under one key produce an identical nonce, so an identical sealed frame, so deduplication survives encryption. Within-site dedup is preserved exactly.
  • Different plaintexts produce different nonces, because BLAKE2b is collision resistant at 192 bits of output. Nonce reuse across distinct plaintexts is therefore infeasible rather than merely unlikely.
  • The nonce is not computable without the key, because the hash is keyed. That is what separates this from convergent encryption: an attacker holding a candidate plaintext cannot confirm the bucket contains it, and two sites with different keys share no ciphertext. Cross-site dedup is given up in exchange, since having it would leak that equality.

This is the synthetic-IV construction, applied so that a store addressed by content can also be encrypted. Random nonces would spend the dedup and delta gains that make second-granularity affordable.

Layout: the sealed frame is nonce || ciphertext, with the Poly1305 tag inside the ciphertext where sodium puts it. The frame's own digest is passed as associated data, so a frame relocated to another key in the bucket fails to open instead of opening as the wrong content.

Summary

Methods
Properties
Constants
__construct
id
isAvailable
unavailableReason
seal
open
No public properties found
No public constants found
No protected methods found
No protected properties found
No protected constants found
nonceFor
key
nonceKey
NONCE_CONTEXT

Constant

NONCE_CONTEXT

NONCE_CONTEXT = 'strata-nonce-derivation-v1'

Personalisation for the nonce-derivation subkey.

Keeps the derived subkey from colliding with any other use of the same master key.

Properties

$key

$key : string

The 32-byte encryption key.

Type

string

$nonceKey

$nonceKey : string

The subkey the nonce hash is keyed with, derived once.

Type

string

Methods

__construct()

__construct(string  $key) : mixed

Constructs a cipher around a key.

Parameters

string $key

Exactly 32 bytes of key material. Use KeyProviderInterface to obtain it rather than reading it from settings directly.

Throws

\InvalidArgumentException

When the key is not exactly 32 bytes. A short key is refused rather than stretched, because silently padding it would give a false impression of the strength in use.

\RuntimeException

When ext-sodium is not loaded.

Returns

mixed —

id()

id() : string

The stable identifier recorded in a frame header.

Returns

string —

A short lowercase token such as "xchacha20poly1305" or "none".

isAvailable()

isAvailable() : bool

Whether this cipher can run on this host.

Returns

bool —

TRUE when every extension it needs is present.

unavailableReason()

unavailableReason() : string|null

Why the cipher is unavailable, for the settings form and hook_requirements().

Returns

string|null —

A short human-readable reason, or NULL when the cipher is available.

seal()

seal(string  $plain, string  $associated = '') : string

Seals a frame.

Parameters

string $plain

The bytes to seal. An empty string seals to an empty string, so an absent payload never becomes a non-empty frame.

string $associated

Additional authenticated data - bound to the ciphertext but not encrypted. Strata passes the frame's own digest, which is what makes a frame moved to a different key in the bucket fail to open rather than open as the wrong content.

Returns

string —

The sealed bytes, including whatever nonce and tag the implementation needs to open them.

open()

open(string  $sealed, string  $associated = '') : string

Opens a sealed frame.

Parameters

string $sealed

The sealed bytes.

string $associated

The same additional authenticated data used to seal.

Returns

string —

The original bytes.

nonceFor()

nonceFor(string  $plain) : string

Derives the deterministic nonce for a plaintext.

Keyed, so the nonce is not computable without the key and a candidate plaintext cannot be confirmed against the bucket.

Parameters

string $plain

The plaintext being sealed.

Returns

string —

Exactly SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES bytes.