Skip to content

Public API#

// Structure of documents
└── src/
    └── Encryption/
        ├── EncryptedValue.php
        ├── EncryptionException.php
        ├── EncryptorInterface.php
    └── KeyManagement/
        ├── KeyGeneratorException.php
        ├── KeyGeneratorInterface.php
        ├── KeyRotationException.php
        ├── KeyRotationOptions.php
        ├── KeyRotationPlan.php
        ├── KeyRotationResult.php
        ├── KeyRotatorInterface.php
    └── Sodium/
        └── Exception/
            ├── SodiumKeyPairException.php
            ├── SodiumKeyPairNotFoundException.php
            ├── SodiumKeyPairOperationException.php
        └── SodiumKeyPairRepositoryInterface.php

Path: /src/Encryption/EncryptedValue.php#

namespace Drupal\easy_encryption\Encryption;

/**
 * Immutable value object representing encrypted data with key metadata.
 *
 * The ciphertext is stored internally as raw binary data. Use
 * getCiphertextHex() for storage in configuration, databases, or files that
 * expect string data.
 *
 * @immutable
 */
final class EncryptedValue
{
    public function __construct(
        private readonly string $ciphertext,
        public readonly string $keyId,
    ) {
        /* ... */
    }


    /**
     * Returns the raw binary ciphertext as produced by libsodium.
     *
     * @return string
     *   The binary ciphertext.
     */
    public function getCiphertext(): string
    {
        /* ... */
    }


    /**
     * Returns hex-encoded ciphertext for storage.
     *
     * Use this method when persisting encrypted data to configuration, databases,
     * or files. To reconstruct the object from stored hex, use
     * EncryptedValue::fromHex().
     *
     * @return string
     *   The hex-encoded ciphertext.
     */
    public function getCiphertextHex(): string
    {
        /* ... */
    }


    /**
     * Creates an EncryptedValue from hex-encoded ciphertext.
     *
     * Use this factory method when reading encrypted data from storage that was
     * previously encoded with getCiphertextHex().
     *
     * @param string $hex
     *   The hex-encoded ciphertext.
     * @param string $keyId
     *   The key pair identifier.
     *
     * @return static
     *   A new EncryptedValue instance.
     *
     * @throws \SodiumException
     *   If the hex string is invalid.
     * @throws \InvalidArgumentException
     *   If the ciphertext or key ID is empty.
     */
    public static function fromHex(string $hex, string $keyId): self
    {
        /* ... */
    }
}

Path: /src/Encryption/EncryptionException.php#

namespace Drupal\easy_encryption\Encryption;

/**
 * Thrown when an encryption or decryption operation fails.
 */
class EncryptionException extends \RuntimeException
{
}

Path: /src/Encryption/EncryptorInterface.php#

namespace Drupal\easy_encryption\Encryption;

/**
 * Defines an interface for encrypting and decrypting sensitive data.
 */
interface EncryptorInterface
{
    /**
     * Encrypts a value.
     *
     * @param string $value
     *   The plaintext value to encrypt.
     *
     * @return \Drupal\easy_encryption\Encryption\EncryptedValue
     *   The encrypted value with metadata.
     *
     * @throws \InvalidArgumentException
     *   If the value is empty.
     * @throws \Drupal\easy_encryption\Encryption\EncryptionException
     *   If encryption fails.
     */
    public function encrypt(
        #[\SensitiveParameter]
        string $value,
    ): EncryptedValue;


    /**
     * Decrypts a value.
     *
     * @param \Drupal\easy_encryption\Encryption\EncryptedValue $value
     *   The encrypted value to decrypt.
     *
     * @return string
     *   The decrypted plaintext.
     *
     * @throws \Drupal\easy_encryption\Encryption\EncryptionException
     *   If decryption fails.
     */
    public function decrypt(EncryptedValue $value): string;


    /**
     * Validates that encryption and decryption work correctly.
     *
     * Implementations may skip this test silently if the environment does not
     * support decryption (for example, when only a public key is available).
     *
     * @throws \Drupal\easy_encryption\Encryption\EncryptionException
     *   If the self-test fails.
     */
    public function selfTest(): void;
}

Path: /src/KeyManagement/KeyGeneratorException.php#

namespace Drupal\easy_encryption\KeyManagement;

/**
 * Thrown when key generation or activation fails.
 *
 * This exception is intended to be thrown by the KeyGenerator application
 * service, so callers do not need to depend on the underlying repository
 * exception hierarchy.
 */
final class KeyGeneratorException extends \RuntimeException
{
    /**
     * Creates an exception for a failed key pair generation.
     *
     * @param string|null $keyPairId
     *   The key pair identifier, if one was known at the time of failure.
     * @param \Throwable|null $previous
     *   (optional) The underlying exception that caused generation to fail.
     *
     * @return self
     *   The exception instance.
     */
    public static function generationFailed(?string $keyPairId = null, ?\Throwable $previous = null): self
    {
        /* ... */
    }


    /**
     * Creates an exception for a failed key pair activation.
     *
     * @param string $keyPairId
     *   The key pair identifier that could not be activated.
     * @param \Throwable|null $previous
     *   (optional) The underlying exception that caused activation to fail.
     *
     * @return self
     *   The exception instance.
     */
    public static function activationFailed(string $keyPairId, ?\Throwable $previous = null): self
    {
        /* ... */
    }
}

Path: /src/KeyManagement/KeyGeneratorInterface.php#

namespace Drupal\easy_encryption\KeyManagement;

/**
 * Application service for generating and activating key pairs.
 *
 * This service returns key identifiers, not key material, to keep the
 * application layer decoupled from the underlying cryptographic library.
 */
interface KeyGeneratorInterface
{
    /**
     * Generates and persists a key pair.
     *
     * If no ID is provided, the generator MUST create a new identifier.
     *
     * @param string|null $keyPairId
     *   (optional) The key pair identifier to use.
     *
     * @return string
     *   The key pair identifier that was generated.
     *
     * @throws \Drupal\easy_encryption\KeyManagement\KeyGeneratorException
     *   Thrown when key pair generation fails.
     */
    public function generate(?string $keyPairId = null): string;


    /**
     * Activates an existing key pair by ID.
     *
     * Activation MUST be allowed when private key is missing, as long as a public
     * key exists (encrypt-only environments).
     *
     * @param string $keyPairId
     *   The key pair identifier.
     *
     * @throws \Drupal\easy_encryption\KeyManagement\KeyGeneratorException
     *   Thrown when activation fails.
     */
    public function activate(string $keyPairId): void;
}

Path: /src/KeyManagement/KeyRotationException.php#

namespace Drupal\easy_encryption\KeyManagement;

/**
 * Exception is thrown by key rotation workflows.
 */
final class KeyRotationException extends \RuntimeException
{
    /**
     * Creates an exception for failures while building a rotation plan.
     *
     * Planning is a non-mutating operation (used for dry-run previews). This
     * exception indicates that the system could not compute a plan, typically due
     * to storage access errors or unexpected runtime issues.
     *
     * @param \Throwable|null $previous
     *   (optional) The underlying exception that caused planning to fail.
     *
     * @return self
     *   The exception instance.
     */
    public static function planFailed(?\Throwable $previous = null): self
    {
        /* ... */
    }


    /**
     * Creates an exception for failures while rotating the active key pair.
     *
     * This exception indicates that generating or activating a new key pair
     * failed.
     *
     * @param \Throwable|null $previous
     *   (optional) The underlying exception that caused rotation to fail.
     *
     * @return self
     *   The exception instance.
     */
    public static function rotateFailed(?\Throwable $previous = null): self
    {
        /* ... */
    }


    /**
     * Creates an exception when re-encryption cannot be performed.
     *
     * This is used when the operation is not possible in the current environment,
     * for example because the private key is not available and existing encrypted
     * values therefore cannot be decrypted for re-encryption.
     *
     * @param string $reason
     *   A human-readable explanation of why re-encryption cannot be performed.
     * @param \Throwable|null $previous
     *   (optional) The underlying exception, if any.
     *
     * @return self
     *   The exception instance.
     */
    public static function reencryptNotPossible(string $reason, ?\Throwable $previous = null): self
    {
        /* ... */
    }


    /**
     * Creates an exception when one or more credentials fail to re-encrypt.
     *
     * @param int $failedCount
     *   The number of credentials that failed to re-encrypt.
     * @param \Throwable|null $previous
     *   (optional) The underlying exception, if any.
     *
     * @return self
     *   The exception instance.
     */
    public static function reencryptFailed(int $failedCount, ?\Throwable $previous = null): self
    {
        /* ... */
    }
}

Path: /src/KeyManagement/KeyRotationOptions.php#

namespace Drupal\easy_encryption\KeyManagement;

/**
 * Options for the rotation operation.
 */
final class KeyRotationOptions
{
    public function __construct(
        public readonly bool $reencryptKeys = false,
        public readonly bool $failOnReencryptErrors = true,
    ) {
        /* ... */
    }
}

Path: /src/KeyManagement/KeyRotationPlan.php#

namespace Drupal\easy_encryption\KeyManagement;

/**
 * Describes what would be re-encrypted without performing changes.
 *
 * @immutable
 */
final class KeyRotationPlan
{
    public function __construct(
        public readonly ?string $activeKeyId,
        public readonly int $total = 0,
        public readonly int $toUpdate = 0,
        public readonly int $toSkip = 0,
    ) {
        /* ... */
    }
}

Path: /src/KeyManagement/KeyRotationResult.php#

namespace Drupal\easy_encryption\KeyManagement;

/**
 * Result of rotating the active key and optionally re-encrypting.
 *
 * @immutable
 */
final class KeyRotationResult
{
    public function __construct(
        public readonly ?string $oldActiveKeyId,
        public readonly string $newActiveKeyId,
        public readonly int $updated = 0,
        public readonly int $skipped = 0,
        public readonly int $failed = 0,
    ) {
        /* ... */
    }
}

Path: /src/KeyManagement/KeyRotatorInterface.php#

namespace Drupal\easy_encryption\KeyManagement;

/**
 * Application service for planning and performing key rotation.
 */
interface KeyRotatorInterface
{
    /**
     * Builds a non-mutating plan describing what would change.
     *
     * @throws \Drupal\easy_encryption\KeyManagement\KeyRotationException
     *   If the plan cannot be computed.
     */
    public function plan(bool $includeReencryptCounts = true): KeyRotationPlan;


    /**
     * Rotates the active key and optionally re-encrypts credentials.
     *
     * @throws \Drupal\easy_encryption\KeyManagement\KeyRotationException
     *   If rotation fails, or re-encryption fails and failOnReencryptErrors
     *   is TRUE.
     */
    public function rotate(KeyRotationOptions $options): KeyRotationResult;
}

Path: /src/Sodium/Exception/SodiumKeyPairException.php#

namespace Drupal\easy_encryption\Sodium\Exception;

/**
 * Base exception for sodium key pair repository failures.
 *
 * All exceptions thrown by SodiumKeyPairRepositoryInterface
 * implementations SHOULD extend this class so that callers can
 * catch and handle repository errors in a generic way.
 */
class SodiumKeyPairException extends \RuntimeException
{
    /**
     * Creates an exception for a key pair that was not found.
     *
     * @param string $id
     *   The key pair identifier.
     *
     * @return self
     *   The exception instance.
     */
    public static function notFound(string $id): self
    {
        /* ... */
    }
}

Path: /src/Sodium/Exception/SodiumKeyPairNotFoundException.php#

namespace Drupal\easy_encryption\Sodium\Exception;

/**
 * Thrown when a requested key pair cannot be found.
 */
final class SodiumKeyPairNotFoundException extends SodiumKeyPairException
{
    /**
     * Creates an exception for a missing key pair identifier.
     *
     * @param string $id
     *   The missing key pair identifier.
     * @param \Throwable|null $previous
     *   (optional) The previous throwable.
     *
     * @return static
     *   The exception instance.
     */
    public static function forId(string $id, ?\Throwable $previous = null): self
    {
        /* ... */
    }


    /**
     * Creates an exception for a missing active key pair.
     *
     * @return self
     *   The exception instance.
     */
    public static function forActive(): self
    {
        /* ... */
    }
}

Path: /src/Sodium/Exception/SodiumKeyPairOperationException.php#

namespace Drupal\easy_encryption\Sodium\Exception;

/**
 * Thrown when a key pair operation fails.
 *
 * This exception covers failures related to key pair generation, storage,
 * retrieval, deletion, and cryptographic operations that depend on key
 * availability or validity.
 */
final class SodiumKeyPairOperationException extends SodiumKeyPairException
{
    /**
     * Creates an exception for a failed generation or activation.
     *
     * @param \Throwable|null $previous
     *   (optional) The previous throwable.
     *
     * @return self
     *   The exception instance.
     */
    public static function generationFailed(?\Throwable $previous = null): self
    {
        /* ... */
    }


    /**
     * Creates an exception for a failed load of an existing key pair.
     *
     * @param string $id
     *   The key pair identifier.
     * @param \Throwable|null $previous
     *   (optional) The previous throwable.
     *
     * @return self
     *   The exception instance.
     */
    public static function loadFailed(string $id, ?\Throwable $previous = null): self
    {
        /* ... */
    }


    /**
     * Creates an exception for an attempt to delete the active key pair.
     *
     * @param string $id
     *   The key pair identifier.
     *
     * @return self
     *   The exception instance.
     */
    public static function cannotDeleteActive(string $id): self
    {
        /* ... */
    }


    /**
     * Creates an exception for a failed deletion operation.
     *
     * @param string $id
     *   The key pair identifier.
     * @param \Throwable|null $previous
     *   (optional) The previous throwable.
     *
     * @return self
     *   The exception instance.
     */
    public static function deleteFailed(string $id, ?\Throwable $previous = null): self
    {
        /* ... */
    }


    /**
     * Creates an exception for a failed decryption operation.
     *
     * @param string $keyId
     *   The key pair identifier.
     * @param \Throwable|null $previous
     *   (optional) The previous throwable.
     *
     * @return self
     *   The exception instance.
     */
    public static function decryptionFailed(string $keyId, ?\Throwable $previous = null): self
    {
        /* ... */
    }


    /**
     * Creates an exception when the private key is not available.
     *
     * This typically occurs in encrypt-only environments where the private key
     * has been intentionally excluded for security reasons.
     *
     * @param string $keyId
     *   The key pair identifier.
     *
     * @return self
     *   The exception instance.
     */
    public static function privateKeyNotAvailable(string $keyId): self
    {
        /* ... */
    }


    /**
     * Creates an exception when the public key is not available.
     *
     * @param string $keyId
     *   The key pair identifier.
     *
     * @return self
     *   The exception instance.
     */
    public static function publicKeyNotAvailable(string $keyId): self
    {
        /* ... */
    }


    /**
     * Creates an exception when keypair construction fails.
     *
     * This occurs when sodium_crypto_box_keypair_from_secretkey_and_publickey()
     * throws an exception, typically indicating key corruption or mismatch.
     *
     * @param string $keyId
     *   The key pair identifier.
     * @param \Throwable $previous
     *   The previous throwable from the Sodium operation.
     *
     * @return self
     *   The exception instance.
     */
    public static function keypairConstructionFailed(string $keyId, \Throwable $previous): self
    {
        /* ... */
    }
}

Path: /src/Sodium/SodiumKeyPairRepositoryInterface.php#

namespace Drupal\easy_encryption\Sodium;

/**
 * Provides access to sodium key pairs used for encryption and decryption.
 */
interface SodiumKeyPairRepositoryInterface
{
    /**
     * Returns the key pair currently used for new encryption operations.
     *
     * The active key pair is the one that MUST be used when creating new
     * ciphertexts. Implementations MAY return a key pair without a private
     * key in encrypt-only environments, in which case only encryption is
     * possible with the returned object.
     *
     * @throws \Drupal\easy_encryption\Sodium\Exception\SodiumKeyPairNotFoundException
     * @throws \Drupal\easy_encryption\Sodium\Exception\SodiumKeyPairOperationException
     */
    public function getActiveKeyPair(): SodiumKeyPair;


    /**
     * Returns a key pair by its identifier.
     *
     * Implementations MAY omit the private key when the current environment is
     * not allowed to perform decryption.
     *
     * @throws \Drupal\easy_encryption\Sodium\Exception\SodiumKeyPairNotFoundException
     * @throws \Drupal\easy_encryption\Sodium\Exception\SodiumKeyPairOperationException
     */
    public function getKeyPairById(string $id): SodiumKeyPair;


    /**
     * Generates and persists a key pair for the given identifier.
     *
     * This method MUST:
     * - Generate a cryptographically secure libsodium key pair.
     * - Persist the public key.
     * - Persist the private key where appropriate for the environment.
     * - Register the key pair identifier in configuration (easy_encryption.keys),
     *   so the system knows the key pair exists.
     *
     * This method MUST NOT change which key pair is active.
     *
     * @param string $id
     *   The key pair identifier to generate.
     *
     * @throws \Drupal\easy_encryption\Sodium\Exception\SodiumKeyPairOperationException
     *   Thrown if generation or persistence fails.
     */
    public function generateKeyPair(string $id): void;


    /**
     * Marks an existing key pair as the active key pair.
     *
     * Activation MUST succeed even when the private key is not available, as long
     * as the public key exists. This enables encrypt-only environments.
     *
     * @throws \Drupal\easy_encryption\Sodium\Exception\SodiumKeyPairNotFoundException
     * @throws \Drupal\easy_encryption\Sodium\Exception\SodiumKeyPairOperationException
     */
    public function activateKeyPair(string $id): void;


    /**
     * Returns identifiers for all stored key pairs.
     *
     * Implementations MUST return identifiers for all key pairs known to
     * the repository, regardless of whether the corresponding private
     * keys are available in the current environment.
     *
     * @return string[]
     *   An array of key pair identifiers.
     */
    public function listKeyPairIds(): array;


    /**
     * Deletes a key pair by its identifier.
     *
     * Implementations MUST refuse to delete the currently active key pair.
     * Callers SHOULD rotate to a new active key pair before attempting to
     * delete an older one.
     *
     * @param string $id
     *   The key pair identifier to delete.
     *
     * @throws \Drupal\easy_encryption\Sodium\Exception\SodiumKeyPairOperationException
     *   Thrown if the key pair is active, or if deletion fails due to a
     *   storage or permissions error.
     */
    public function deleteKeyPair(string $id): void;
}