Encryptor.Vault.Partition (Encryptor v0.7.0)

Copy Markdown View Source

Derives the fixed-width cache partition id a vault hands the caching CMM.

One cache process serves every partition within a vault (ADR-0001 decision 3), so the thing that keeps one scope's data key out of another scope's cache lookup is the partition id, not a second process. Decision 7 fixes the derivation:

partition_id = binary_part(sha256(vault_namespace, 0, encoded_selector), 0, 16)

and this module is the only place it is computed. The result is what the vault hands the engine's caching CMM as :partition_id.

The write side also carries the key

The engine's encryption cache id hashes the partition id, the suite and the context, and never the key that wrapped the entry's data key. With the formula above alone, a write after a new key version is minted finds the warm entry from before the mint and wraps its data key under the old version. So the partition a vault hands the caching CMM on the write side (an encrypt, and a rekey's write half) also carries the resolved key's identity - the namespace and name of an Encryptor.Key.Aes, the key id of an Encryptor.Key.Kms - in a length-prefixed pre-image, and a new version is a new partition, cold at once (ADR-0001 Amendment B). The read side is unchanged: the decryption cache id already hashes the message's encrypted data keys, which name the key that wrapped them.

Why the width is fixed, and why it is 16

The engine's cache-id computation (compute_encryption_cache_id/3 in the aws_encryption_sdk caching CMM) concatenates the partition id into the cache id pre-image with no length prefix. A variable-width partition id therefore makes the pre-image ambiguous, and two different partitions could in principle hash to one cache id - which is two scopes sharing a data key. Sixteen bytes is the width of the UUID the engine generates when no partition id is given, so matching it removes the ambiguity by construction rather than by argument.

Nothing here may be relaxed into "any binary": the width is load-bearing.

What a partition id is not

It is a cache-key input only. It is not key material, it is not secret, and it never reaches a message. Deriving it by hash rather than using the raw selector keeps scope identifiers out of a structure this package does not control the lifetime of, and buys the uniform width for free.

Records: ADR-0001 decisions 3 and 7 and Amendment B; the selector type is ADR-0004 decision 3.

Summary

Functions

The width every partition id has, in bytes.

The 16-byte partition id for a vault and a key selector.

Functions

bytes()

@spec bytes() :: pos_integer()

The width every partition id has, in bytes.

iex> Encryptor.Vault.Partition.bytes()
16

id(vault, selector)

@spec id(module(), Encryptor.Error.selector()) :: binary()

The 16-byte partition id for a vault and a key selector.

Pure, total over the selector types ADR-0004 decision 3 admits, and allocating nothing that outlives the call.

iex> id = Encryptor.Vault.Partition.id(MyApp.Vault, "scope-42")
iex> byte_size(id)
16

iex> Encryptor.Vault.Partition.id(MyApp.Vault, "scope-42") ==
...>   Encryptor.Vault.Partition.id(MyApp.Vault, "scope-43")
false

iex> Encryptor.Vault.Partition.id(MyApp.Vault, :default) ==
...>   Encryptor.Vault.Partition.id(MyApp.OtherVault, :default)
false