AwsEncryptionSdk.Stream.Decryptor (AWS Encryption SDK v0.7.0)
View SourceStreaming decryptor state machine for incremental ciphertext processing.
When to Use Streaming
Use Stream.Decryptor instead of Client.decrypt/2 when:
- Decrypting large files that don't fit in memory
- Processing encrypted data from network streams
- Working with ciphertext sources that produce chunks incrementally
- Memory constraints require bounded memory usage
For small messages (< 1MB), the simpler Client.decrypt/2 API is recommended.
Memory Efficiency
The streaming decryptor maintains constant memory usage:
- Buffers only data needed to parse the current frame
- Emits plaintext incrementally after frame authentication
- No need to load entire ciphertext into memory
Memory usage is bounded by the frame size plus header size, regardless of total message size.
Plaintext Verification Status
Decrypted plaintext is tagged with verification status:
:verified- Plaintext is authenticated and safe to use- For unsigned suites: immediately after frame authentication
- For signed suites: after signature verification completes
:unverified- Plaintext not yet cryptographically verified- Only for signed algorithm suites
- Signature verification happens at end of stream
- Do not use unverified plaintext until signature validates
Handling Signed Suites
For signed algorithm suites (ECDSA P-384), you must handle verification:
Option 1: Fail immediately (safest):
{:ok, dec} = Decryptor.init(
get_materials: materials_fn,
fail_on_signed: true
)Option 2: Buffer unverified plaintext (for streaming):
plaintexts = []
for {plaintext, status} <- decrypted_chunks do
case status do
:verified -> use_plaintext(plaintext)
:unverified -> plaintexts = [plaintext | plaintexts]
end
end
# At end of stream, all buffered plaintext is verifiedOption 3: Use high-level API (recommended):
Use AwsEncryptionSdk.Stream.decrypt/3 which handles verification automatically.
Integration with Elixir Streams
Designed to work seamlessly with Stream module:
File.stream!("encrypted.bin", [], 4096)
|> AwsEncryptionSdk.Stream.decrypt(client)
|> Stream.map(fn {plaintext, _status} -> plaintext end)
|> Stream.into(File.stream!("decrypted.bin"))
|> Stream.run()See AwsEncryptionSdk.Stream for high-level streaming API.
State Machine
The decryptor progresses through these states:
:init- Not started, awaiting ciphertext:reading_header- Accumulating header bytes:decrypting- Processing frames:reading_footer- Accumulating footer (signed suites only):done- Decryption complete
Low-Level Example
For custom streaming logic, use the state machine directly:
get_materials = fn header ->
# Obtain decryption materials from CMM
cmm.get_decryption_materials(...)
end
{:ok, dec} = Decryptor.init(get_materials: get_materials)
# Process ciphertext chunks
{:ok, dec, plaintexts1} = Decryptor.update(dec, chunk1)
{:ok, dec, plaintexts2} = Decryptor.update(dec, chunk2)
{:ok, dec, final_plaintexts} = Decryptor.finalize(dec)
# Each plaintexts is a list of {binary, :verified | :unverified} tuplesSecurity
- Never release unauthenticated plaintext to untrusted contexts
- For signed suites, verify signature before using plaintext
- The decryptor validates authentication tags before emitting plaintext
- Commitment verification happens during header processing
See Also
AwsEncryptionSdk.Stream- High-level streaming APIAwsEncryptionSdk.Stream.Encryptor- Streaming encryptionAwsEncryptionSdk.Client- Non-streaming decryption API
Summary
Functions
Finalizes decryption.
Returns the parsed header, if available.
Initializes a new streaming decryptor.
Returns the current state.
Processes ciphertext chunk.
Types
@type plaintext_status() :: :verified | :unverified
@type state() :: :init | :reading_header | :decrypting | :reading_footer | :done
@type t() :: %AwsEncryptionSdk.Stream.Decryptor{ buffer: binary(), derived_key: binary() | nil, expected_sequence: pos_integer(), fail_on_signed: boolean(), final_frame_plaintext: binary() | nil, get_materials: (AwsEncryptionSdk.Format.Header.t() -> {:ok, AwsEncryptionSdk.Materials.DecryptionMaterials.t()} | {:error, term()}) | nil, header: AwsEncryptionSdk.Format.Header.t() | nil, materials: AwsEncryptionSdk.Materials.DecryptionMaterials.t() | nil, signature_acc: AwsEncryptionSdk.Stream.SignatureAccumulator.t() | nil, state: state() }
Functions
@spec finalize(t()) :: {:ok, t(), [{binary(), plaintext_status()}]} | {:error, term()}
Finalizes decryption.
Verifies no trailing bytes remain and completes signature verification for signed suites.
Returns {:ok, updated_decryptor, final_plaintexts}.
@spec header(t()) :: AwsEncryptionSdk.Format.Header.t() | nil
Returns the parsed header, if available.
Initializes a new streaming decryptor.
Options
:get_materials- Function(header) -> {:ok, materials} | {:error, reason}to obtain decryption materials after header is parsed. Required.:fail_on_signed- Iftrue, fails immediately when a signed algorithm suite is detected. Default:false.
Returns the current state.
Processes ciphertext chunk.
Returns {:ok, updated_decryptor, plaintexts} where plaintexts is a list of
{plaintext_binary, status} tuples. Status is :verified for unsigned suites
or final frame after signature verification, :unverified otherwise.
For unsigned suites, plaintext is released immediately after frame authentication.