stream

package
v0.0.0-...-249dd9b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 22, 2026 License: BSD-3-Clause Imports: 5 Imported by: 0

Documentation

Overview

Package stream implements the STREAM online authenticated-encryption construction from "Online Authenticated-Encryption and its Nonce-Reuse Misuse-Resistance" (Hoang, Reyhanitabar, Rogaway, Vizár – CRYPTO 2015).

Overview

STREAM partitions an arbitrarily long plaintext into fixed-size blocks and seals each block independently with a deterministic per-block nonce. The nonce is derived from a caller-supplied random seed and a monotonically increasing counter; a one-byte domain separator in the final nonce byte distinguishes intermediate blocks (0x00) from the mandatory end-of-stream block (0x01). This design prevents truncation, extension, and block- reordering attacks without requiring the receiver to keep state beyond the current block counter.

Nonce layout

For an AEAD whose nonce is N bytes, the per-block nonce is assembled as:

[ seed: N-5 bytes ] [ counter: 4 bytes big-endian ] [ d: 1 byte ]

where d = 0x00 for every block except the last, and d = 0x01 (EOS) for the final block. Concrete examples:

AES-GCM    (N=12):  [ 7-byte seed ][ 4-byte counter ][ d ]
XChaCha20  (N=24):  [ 19-byte seed ][ 4-byte counter ][ d ]

Usage

// Encrypt
w, err := stream.NewWriter(dst, aead, seed, ad, blockSize)
w.Write(plaintext)
w.Close() // always call Close to emit the mandatory EOS block

// Decrypt
r, err := stream.NewReader(src, aead, seed, ad, blockSize)
io.Copy(dst, r)
if !r.IsComplete() {
    // stream was truncated or corrupted before the EOS block
}

Index

Constants

View Source
const (
	// NonceOverhead is the number of bytes consumed from the nonce by the
	// STREAM framing layer: 4 bytes for the block counter and 1 byte for the
	// end-of-stream domain separator.  The remaining nonce bytes are filled
	// by the caller-supplied seed.
	NonceOverhead = 4 + 1

	// DefaultBlockSize is the plaintext block size used when NewWriter or
	// NewReader is called with blockSize == 0.  64 KiB balances memory use
	// against per-block AEAD overhead for typical workloads.
	DefaultBlockSize = 64 * 1024

	// EOS is the domain-separator byte written into the last nonce byte of
	// the final (end-of-stream) block.  All other blocks use 0x00.
	EOS = 0x01

	// ErrStreamInit is returned by NewWriter and NewReader when the supplied
	// seed is shorter than the minimum required length (NonceSize - NonceOverhead).
	ErrStreamInit = Error("STREAM initialization error")
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Error

type Error string

Error is a string-based sentinel error type whose values can be compared with == without allocations.

func (Error) Error

func (e Error) Error() string

type STREAM

type STREAM struct {
	// contains filtered or unexported fields
}

STREAM is an online authenticated-encryption stream backed by an arbitrary cipher.AEAD. A single instance is used either for writing (encryption) or for reading (decryption), constructed by NewWriter or NewReader respectively.

The zero value is not usable; always obtain a STREAM through one of the constructor functions.

func NewReader

func NewReader(r io.Reader, a cipher.AEAD, seed, ad []byte, blockSize int) (*STREAM, error)

NewReader wraps r with a STREAM decryption reader.

Parameters:

  • r — source of the ciphertext produced by a STREAM writer.
  • a — the same AEAD cipher used to produce the ciphertext.
  • seed — the same seed passed to NewWriter; must be at least a.NonceSize()-NonceOverhead bytes long.
  • ad — the same associated data passed to NewWriter; may be nil.
  • blockSize — the same block size passed to NewWriter; 0 selects DefaultBlockSize.

After draining all plaintext, callers should check IsComplete() to confirm that the stream ended with a valid EOS block rather than being truncated.

func NewWriter

func NewWriter(w io.Writer, a cipher.AEAD, seed, ad []byte, blockSize int) (*STREAM, error)

NewWriter wraps w with a STREAM encryption writer.

Parameters:

  • w — destination for the ciphertext produced by Write and Close.
  • a — AEAD cipher used to seal each block.
  • seed — random bytes that fill the fixed portion of every nonce; must be at least a.NonceSize()-NonceOverhead bytes long. Only the first a.NonceSize()-NonceOverhead bytes are used.
  • ad — associated data authenticated (but not encrypted) with every block; may be nil. The reader must supply the same value.
  • blockSize — maximum plaintext bytes per sealed block; 0 selects DefaultBlockSize.

The caller must call Close exactly once to emit the mandatory EOS block. Failing to call Close leaves the ciphertext without a valid terminator, which causes the reader to report IsComplete() == false.

func (*STREAM) Block

func (s *STREAM) Block() uint32

Block returns the index of the next block that will be decrypted. Before any Read calls this is 0; after decrypting k blocks it is k.

func (*STREAM) BlockSize

func (s *STREAM) BlockSize() int

BlockSize returns the maximum plaintext byte count per sealed block.

func (*STREAM) Close

func (s *STREAM) Close() error

Close seals the remaining buffered plaintext (which may be zero bytes) as the final EOS block, writes it to the underlying writer, and resets the internal buffer.

Close always emits exactly one block, even when no plaintext was written at all. This guarantees that every stream has at least one authenticated block, giving the reader something to verify and allowing it to set IsComplete() == true for empty streams.

func (*STREAM) IsComplete

func (s *STREAM) IsComplete() bool

IsComplete reports whether the stream has been fully and correctly terminated. On the write path it is always false (the writer does not consume its own output). On the read path it becomes true only after a block authenticated with d=EOS has been decrypted and its plaintext fully handed to the caller.

A ciphertext that ends without a valid EOS block—due to truncation, corruption, or a missing Close call on the writer—leaves IsComplete false.

func (*STREAM) Read

func (s *STREAM) Read(p []byte) (n int, err error)

Read decrypts ciphertext from the underlying reader and writes plaintext into p. It satisfies the io.Reader contract:

  • Returns n > 0, nil while plaintext is available.
  • Returns 0, io.EOF when the stream is properly terminated (a valid EOS block has been decrypted and all its plaintext drained from buf).
  • Returns 0, io.EOF without setting IsComplete when the underlying reader is exhausted before a valid EOS block is found (truncated stream).
  • Returns 0, err on authentication failure or any other I/O error.

When a decrypted block produces more plaintext than p can hold, the excess is held in an internal buffer and returned on subsequent Read calls before any new ciphertext is consumed.

func (*STREAM) SeekBlock

func (s *STREAM) SeekBlock(block int)

SeekBlock repositions the nonce counter to the given block index so that the next Read call decrypts starting from that block.

The caller is responsible for also seeking the underlying io.Reader to the matching byte offset:

offset = block * (blockSize + aead.Overhead())

SeekBlock resets endOfStream and the plaintext overflow buffer so that a seek performed after a previous read—including one that reached EOF—does not immediately return stale data or a premature io.EOF.

func (*STREAM) StreamSize

func (s *STREAM) StreamSize(n int) int

StreamSize returns the total number of ciphertext bytes that will be produced for a plaintext of n bytes.

Every full plaintext block of blocksize bytes expands to blocksize + aead.Overhead() bytes. A partial trailing block, or an empty stream (n == 0), still emits one sealed EOS block of aead.Overhead() bytes (the AEAD tag alone, with zero plaintext bytes), so StreamSize(0) equals aead.Overhead().

func (*STREAM) Write

func (s *STREAM) Write(b []byte) (n int, err error)

Write buffers plaintext bytes and flushes full blocks to the underlying writer as sealed ciphertext. The internal buffer accumulates bytes until it reaches blocksize, at which point the full block is sealed with the non-last nonce (d=0x00) and written.

The last (possibly partial) block is never sealed here; it is sealed by Close with the EOS nonce (d=0x01). This ensures that the end-of-stream marker is always on the final block and nowhere else.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL