Class CipherTransformInputStreamBuilder

java.lang.Object
zeroecho.core.io.CipherTransformInputStreamBuilder

public final class CipherTransformInputStreamBuilder extends Object
Builder for chunk-transforming InputStreams backed by a Cipher.

This builder configures and creates an InputStream that reads from an upstream source and applies a block-based transformation using a provided Cipher. Three stream variants are available:

  • Independent block stream - invokes Cipher.doFinal(byte[], int, int, byte[], int) for each full input block; a final partial block (if any) is processed by a single doFinal. This mode is restricted to RSA and ElGamal.
  • Left-padded independent block stream - like the independent block stream, but left-pads each transformed output block with zeros up to outChunkSize. Final blocks must be complete; otherwise an IllegalStateException is thrown.
  • Continuous stream - uses Cipher.update(...) for bulk bytes and a single doFinal() at end of stream. This is suitable for CTR/CFB/OFB/GCM and padding modes.

Block sizing

The builder exposes inChunkSize and outChunkSize. For AES in ECB/CBC/CTR-like modes these are typically 16 bytes, but the stream can use larger logical chunks for I/O efficiency. The implementation buffers up to bufferedBlocks chunks per fill to balance throughput and memory.

Finalization headroom

Some ciphers or modes produce extra bytes at end-of-stream (for example, a padding block for CBC/PKCS5 or an authentication tag for GCM). To ensure the final doFinal(...) fits even when the steady-state output area is full, you can reserve additional output capacity via withFinalizationOutputChunks(int). This increases only the output-side buffer, leaving the input buffer size unchanged.

Thread-safety

Instances produced by this builder are not thread-safe. Do not share a single stream across threads without external synchronization.

Examples

// AES/ECB/NOPADDING: aligned input, no extra finalization output
Cipher c1 = Cipher.getInstance("AES/ECB/NOPADDING");
c1.init(Cipher.ENCRYPT_MODE, key);
InputStream s1 = CipherTransformInputStreamBuilder.builder()
    .withUpstream(in)
    .withCipher(c1)
    .withUpdateStreaming(true)     // allowed; final len must be multiple of 16
    .withInputBlockSize(16)
    .withOutputBlockSize(16)
    .withBufferedBlocks(200)
    .withFinalizationOutputChunks(0)
    .build();

// AES/CBC/PKCS5PADDING: one final padding block
Cipher c2 = Cipher.getInstance("AES/CBC/PKCS5PADDING");
c2.init(Cipher.ENCRYPT_MODE, key, ivSpec);
InputStream s2 = CipherTransformInputStreamBuilder.builder()
    .withUpstream(in)
    .withCipher(c2)
    .withUpdateStreaming(true)
    .withInputBlockSize(16)
    .withOutputBlockSize(16)
    .withBufferedBlocks(200)
    .withFinalizationOutputChunks(1)
    .build();

// AES/GCM/NOPADDING (16-byte tag): tail + tag -> reserve two chunks
Cipher c3 = Cipher.getInstance("AES/GCM/NOPADDING");
c3.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv));
InputStream s3 = CipherTransformInputStreamBuilder.builder()
    .withUpstream(in)
    .withCipher(c3)
    .withUpdateStreaming(true)
    .withInputBlockSize(16).withOutputBlockSize(16)
    .withBufferedBlocks(200)
    .withFinalizationOutputChunks(2)
    .build();

// ElGamal (asymmetric; non-streaming semantics): no extra finalization output
Cipher c4 = Cipher.getInstance("ElGamal/None/NOPADDING"); // provider-specific
c4.init(Cipher.ENCRYPT_MODE, publicKey, params);
InputStream s4 = CipherTransformInputStreamBuilder.builder()
    .withUpstream(in)
    .withCipher(c4)
    .withIndependentBlocks()
    .withInputBlockSize(elgIn)
    .withOutputBlockSize(elgOut)
    .withBufferedBlocks(200)
    .withFinalizationOutputChunks(0)
    .build();
Since:
1.0
  • Method Details

    • builder

      public static CipherTransformInputStreamBuilder builder()
      Returns a new builder instance with default settings.

      Defaults: inChunkSize=256, outChunkSize=256, bufferedBlocks=100, padding disabled.

      Returns:
      a new builder
    • withUpstream

      public CipherTransformInputStreamBuilder withUpstream(InputStream upstream)
      Sets the upstream source from which bytes will be read and transformed.
      Parameters:
      upstream - the input stream to wrap; must not be null
      Returns:
      this builder for chaining
    • withCipher

      public CipherTransformInputStreamBuilder withCipher(Cipher cipher)
      Sets the Cipher used to transform blocks.

      The cipher must be fully initialized for the intended mode (encrypt or decrypt) before calling build().

      Parameters:
      cipher - initialized cipher instance; must not be null
      Returns:
      this builder for chaining
    • withLeftZeroPadding

      public CipherTransformInputStreamBuilder withLeftZeroPadding(boolean padding)
      Configures whether left zero padding is applied to each output block.

      If set to true, the builder will produce a padded stream variant where each block is left-filled with zeros up to outChunkSize. If false, the plain non-padded variant is used.

      Parameters:
      padding - true to enable left zero padding, false to disable it
      Returns:
      this builder for chaining
    • withInputBlockSize

      public CipherTransformInputStreamBuilder withInputBlockSize(int inSize)
      Sets the size of a single input block fed to the cipher.
      Parameters:
      inSize - input chunk size in bytes; must be > 1
      Returns:
      this builder for chaining
    • withOutputBlockSize

      public CipherTransformInputStreamBuilder withOutputBlockSize(int outSize)
      Sets the size of a single output block expected from the cipher.
      Parameters:
      outSize - output chunk size in bytes; must be > 0
      Returns:
      this builder for chaining
    • withBufferedBlocks

      public CipherTransformInputStreamBuilder withBufferedBlocks(int bufferedBlocks)
      Sets the number of blocks buffered per internal fill cycle.

      The internal buffers will be sized to inChunkSize * bufferedBlocks and outChunkSize * bufferedBlocks.

      Parameters:
      bufferedBlocks - number of blocks to buffer; must be > 0
      Returns:
      this builder for chaining
    • withFinalizationOutputChunks

      public CipherTransformInputStreamBuilder withFinalizationOutputChunks(int chunks)
      Sets the number of extra output chunks reserved for the finalization step.

      Some modes may emit additional bytes at the very end (padding block for CBC/PKCS5, authentication tag for GCM/CCM/EAX, provider flush). The output buffer is allocated with bufferedBlocks + finalizationOutputChunks chunks to guarantee that the final doFinal(...) fits even when the steady-state area is full.

      • AES/CBC/PKCS5PADDING: 1
      • AES/GCM (16-byte tag): 2
      • AES/CTR, CFB, OFB: 1 (covers a tail up to 15 bytes)
      • AES/CBC/NOPADDING: 0 (total length must be a multiple of block size)
      • ElGamal/RSA: 0 (non-streaming; no extra finalization block)

      Notes

      • For GCM with a different tag size, use ceil((15 + tagLen)/outChunkSize). With 12–16 byte tags and outChunkSize=16, 2 is still safe.
      • This setting enlarges only the output buffer capacity; the input buffer remains inChunkSize * bufferedBlocks.
      Parameters:
      chunks - extra output chunks reserved for finalization; must be >= 0
      Returns:
      this builder for chaining
    • withUpdateStreaming

      public CipherTransformInputStreamBuilder withUpdateStreaming()
      Enables update-driven streaming (Cipher.update during transform, single doFinal at EOF). Suitable for CTR/CFB/OFB/GCM and padding modes.
      Returns:
      this builder for chaining
    • withUpdateStreaming

      public CipherTransformInputStreamBuilder withUpdateStreaming(boolean enable)
      Controls update-driven streaming explicitly.
      Parameters:
      enable - true to use update-driven streaming, false for per-block doFinal variant
      Returns:
      this builder
    • withIndependentBlocks

      public CipherTransformInputStreamBuilder withIndependentBlocks()
      Selects independent-block processing, in which every logical input block is passed to a separate Cipher.doFinal(byte[], int, int, byte[], int) invocation.

      This mode is supported only for RSA and ElGamal transformations. Stateful symmetric modes, including AES-GCM and AES-CBC, must use withUpdateStreaming().

      Returns:
      this builder
    • build

      public InputStream build()
      Builds a chunk-transforming InputStream using the configured options.

      The returned stream pulls from upstream and applies the configured cipher. The output buffer is allocated to outChunkSize * (bufferedBlocks + finalizationOutputChunks) bytes so that the final doFinal(...) can emit padding or tags even when the steady-state output window is full.

      Returns:
      a new InputStream that transforms bytes on the fly
      Throws:
      NullPointerException - if upstream or cipher is null
      IllegalArgumentException - if independent-block processing is selected for an unsupported algorithm or buffer geometry is invalid