Adapters¶
An adapter wraps one cryptographic backend. It takes and returns plain Python types rather than request models, so it can be unit-tested without the web stack, and so a missing optional backend breaks only its own operation.
Base interface¶
base
¶
Common interface shared by all cryptographic adapters.
Adapters deliberately speak plain Python types rather than the API's Pydantic
models. Translation between the request envelope and an adapter call happens in
core.manager. Keeping that boundary means an adapter can be imported and
unit-tested without the web stack installed, and a backend that is missing from
the environment only breaks its own operation.
CryptographicAdapter
¶
Bases: ABC
Base class for every cryptographic backend adapter.
execute
abstractmethod
¶
execute(
*,
action: str,
input_data: dict[str, Any],
parameters: dict[str, Any],
key_config: dict[str, Any] | None = None,
) -> AdapterResult
Run one operation against the backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
action
|
str
|
Sub-operation to perform, e.g. |
required |
input_data
|
dict[str, Any]
|
Data to be processed. |
required |
parameters
|
dict[str, Any]
|
Operation-specific configuration. |
required |
key_config
|
dict[str, Any] | None
|
Key generation or key loading configuration. |
None
|
Returns:
| Type | Description |
|---|---|
AdapterResult
|
A |
Raises:
| Type | Description |
|---|---|
AdapterError
|
If the operation cannot be completed. |
Source code in src/cryptography_manager/adapters/base.py
Differential privacy¶
differential_privacy
¶
Differential privacy adapter backed by PyDP.
PyDP wraps Google's differential privacy library, so the noise calibration and sensitivity analysis are inherited from a well-reviewed implementation rather than reimplemented here. This adapter's job is to translate a configuration or request into a correctly-parameterised PyDP algorithm and to keep epsilon accounting honest.
Two execution paths are offered. execute_all runs a configured plan of
queries in one go and debits the adapter's own budget; it exists for library
and scripted use. execute_query runs a single query and leaves budget
accounting to the caller, which is what the service layer needs because there
the budget belongs to a user rather than to an adapter instance.
DifferentialPrivacyAdapter
¶
DifferentialPrivacyAdapter(config: Config)
Bases: CryptographicAdapter
Differentially private aggregate queries over numeric data.
Initialise the adapter from configuration.
The query plan and data bounds are optional: a service handling per-request queries supplies them with each request, while a script driving a configured plan supplies them up front.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
Config
|
Loaded configuration object. |
required |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If the differential privacy section is absent or malformed. |
DifferentialPrivacyError
|
If the configured plan cannot fit inside the configured budget. |
Source code in src/cryptography_manager/adapters/differential_privacy.py
execute_query
¶
execute_query(
query_type: str,
epsilon: float,
data: Sequence[Any] | NDArray[Any],
lower_bound: float | int | None = None,
upper_bound: float | int | None = None,
budget: PrivacyBudget | None = None,
) -> float | int
Run a single differentially private query.
Budget accounting is the caller's responsibility unless budget is
supplied. The service layer debits a per-user store instead, and
double-debiting would silently halve every user's budget.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query_type
|
str
|
Name of the aggregate to compute. |
required |
epsilon
|
float
|
Privacy budget to spend on this query. |
required |
data
|
Sequence[Any] | NDArray[Any]
|
Numeric values to aggregate. |
required |
lower_bound
|
float | int | None
|
Lower value bound; falls back to configuration. |
None
|
upper_bound
|
float | int | None
|
Upper value bound; falls back to configuration. |
None
|
budget
|
PrivacyBudget | None
|
Optional budget to debit before executing. |
None
|
Returns:
| Type | Description |
|---|---|
float | int
|
The noisy aggregate. |
Raises:
| Type | Description |
|---|---|
DifferentialPrivacyError
|
If the query is unsupported, bounds are missing or invalid, or the backend fails. |
Source code in src/cryptography_manager/adapters/differential_privacy.py
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | |
execute_all
¶
Run every configured query in order, debiting the adapter budget.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Sequence[Any] | NDArray[Any]
|
Numeric values to aggregate. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, float | int]
|
Query name mapped to its noisy result. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If no queries are configured. |
DifferentialPrivacyError
|
If the budget is exceeded or a query fails. |
Source code in src/cryptography_manager/adapters/differential_privacy.py
execute
¶
execute(
*,
action: str,
input_data: dict[str, Any],
parameters: dict[str, Any],
key_config: dict[str, Any] | None = None,
) -> AdapterResult
Run one differentially private query for the service layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
action
|
str
|
|
required |
input_data
|
dict[str, Any]
|
|
required |
parameters
|
dict[str, Any]
|
|
required |
key_config
|
dict[str, Any] | None
|
Unused. |
None
|
Returns:
| Type | Description |
|---|---|
AdapterResult
|
A |
AdapterResult
|
the service layer adds it from the per-user store. |
Raises:
| Type | Description |
|---|---|
DifferentialPrivacyError
|
If the action is unknown or the query cannot be run. |
Source code in src/cryptography_manager/adapters/differential_privacy.py
Encryption and key management¶
encryption
¶
Encryption, decryption and key management via Google Tink.
Tink is used rather than a raw cipher library because it is misuse-resistant: algorithm parameters, nonce generation and ciphertext tagging are handled internally, and only vetted primitives are reachable. This adapter exposes the AEAD primitive plus the keyset lifecycle operations (generation, serialisation, loading and rotation).
The byte-level operations are the core; the file helpers are a thin convenience layer for the library and example code. A REST caller cannot pass file paths, so nothing above this module depends on the filesystem.
EncryptionAdapter
¶
Bases: CryptographicAdapter
Symmetric authenticated encryption and keyset management via Tink.
generate_keyset
staticmethod
¶
Generate a new keyset containing a single primary key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
template
|
str
|
Name of an allowlisted AEAD key template. |
DEFAULT_TEMPLATE
|
Returns:
| Type | Description |
|---|---|
KeysetHandle
|
A handle to the newly generated keyset. |
Source code in src/cryptography_manager/adapters/encryption.py
serialize_keyset
staticmethod
¶
Serialise a keyset to cleartext JSON.
The output contains raw key material and must be stored in a secret manager or encrypted at rest. It is never written to an audit record.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handle
|
KeysetHandle
|
The keyset to serialise. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The keyset as a JSON string. |
Source code in src/cryptography_manager/adapters/encryption.py
load_keyset
staticmethod
¶
Load a keyset from its cleartext JSON representation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
serialized
|
str
|
JSON produced by :meth: |
required |
Returns:
| Type | Description |
|---|---|
KeysetHandle
|
A handle to the loaded keyset. |
Raises:
| Type | Description |
|---|---|
KeyManagementError
|
If the keyset cannot be parsed. |
Source code in src/cryptography_manager/adapters/encryption.py
rotate_keyset
staticmethod
¶
Add a fresh key to a keyset and promote it to primary.
Existing keys are retained and stay enabled, so ciphertexts produced before the rotation remain decryptable. Tink prefixes each ciphertext with its key ID, which is what makes this work.
tink-py 1.12 exposes no KeysetManager, so the merge is done at the
protobuf layer using the public tink.proto and
proto_keyset_format modules rather than private attributes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handle
|
KeysetHandle
|
The keyset to rotate. |
required |
template
|
str
|
Template for the new primary key. |
DEFAULT_TEMPLATE
|
Returns:
| Type | Description |
|---|---|
KeysetHandle
|
A handle to the rotated keyset. |
Raises:
| Type | Description |
|---|---|
KeyManagementError
|
If the rotation fails. |
Source code in src/cryptography_manager/adapters/encryption.py
keyset_info
staticmethod
¶
Describe a keyset without exposing key material.
Only key IDs, type URLs, status and prefix type are returned, making the result safe to log and to return over the API.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handle
|
KeysetHandle
|
The keyset to describe. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Keyset metadata suitable for audit and API responses. |
Source code in src/cryptography_manager/adapters/encryption.py
encrypt_bytes
¶
Encrypt bytes with authenticated encryption.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plaintext
|
bytes
|
Data to encrypt. |
required |
handle
|
KeysetHandle
|
Keyset providing the AEAD primitive. |
required |
associated_data
|
bytes
|
Context bound to the ciphertext. It is authenticated but not encrypted, and the exact same value must be supplied to decrypt. |
b''
|
Returns:
| Type | Description |
|---|---|
bytes
|
The ciphertext. |
Raises:
| Type | Description |
|---|---|
StandardCryptographyError
|
If encryption fails. |
Source code in src/cryptography_manager/adapters/encryption.py
decrypt_bytes
¶
Decrypt bytes produced by :meth:encrypt_bytes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ciphertext
|
bytes
|
Data to decrypt. |
required |
handle
|
KeysetHandle
|
Keyset providing the AEAD primitive. |
required |
associated_data
|
bytes
|
The exact value used at encryption time. |
b''
|
Returns:
| Type | Description |
|---|---|
bytes
|
The recovered plaintext. |
Raises:
| Type | Description |
|---|---|
StandardCryptographyError
|
If decryption or authentication fails. |
Source code in src/cryptography_manager/adapters/encryption.py
encrypt_file
¶
Encrypt a file, binding the ciphertext to its basename.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
File to encrypt. |
required |
handle
|
KeysetHandle
|
Keyset providing the AEAD primitive. |
required |
output
|
str | Path | None
|
Destination; defaults to |
None
|
Returns:
| Type | Description |
|---|---|
Path
|
Path to the ciphertext file. |
Source code in src/cryptography_manager/adapters/encryption.py
decrypt_file
¶
Decrypt a file produced by :meth:encrypt_file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Ciphertext file; must end in |
required |
handle
|
KeysetHandle
|
Keyset providing the AEAD primitive. |
required |
output
|
str | Path | None
|
Destination; defaults to |
None
|
Returns:
| Type | Description |
|---|---|
Path
|
Path to the recovered plaintext file. |
Raises:
| Type | Description |
|---|---|
StandardCryptographyError
|
If the filename is not |
Source code in src/cryptography_manager/adapters/encryption.py
execute
¶
execute(
*,
action: str,
input_data: dict[str, Any],
parameters: dict[str, Any],
key_config: dict[str, Any] | None = None,
) -> AdapterResult
Run an encryption or key-management action.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
action
|
str
|
One of |
required |
input_data
|
dict[str, Any]
|
For |
required |
parameters
|
dict[str, Any]
|
May carry |
required |
key_config
|
dict[str, Any] | None
|
Keyset selection; see :meth: |
None
|
Returns:
| Type | Description |
|---|---|
AdapterResult
|
A |
AdapterResult
|
survives a JSON round trip. |
Raises:
| Type | Description |
|---|---|
StandardCryptographyError
|
If the action is unknown or fails. |
Source code in src/cryptography_manager/adapters/encryption.py
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 | |