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;
}