Compress Then Encrypt

Two decorator stacks around the same sink. The outermost wrapper transforms the payload first.

class Gzip:
    def __init__(self, inner): self.inner = inner
    def write(self, data: bytes) -> None:
        self.inner.write(gzip.compress(data))

class Encrypt:
    def __init__(self, inner, key): self.inner, self.key = inner, key
    def write(self, data: bytes) -> None:
        self.inner.write(aes_ctr(self.key, data))   # stream mode, no padding

stack_a = Gzip(Encrypt(File("out.bin"), KEY))
stack_b = Encrypt(Gzip(File("out.bin")), KEY)
Stack A compresses then encrypts; stack B encrypts then compresses stack_a stack_b Gzip.write Encrypt.write File Encrypt.write Gzip.write File

The payload is 4 MB of newline-delimited JSON access logs. What comes out?

A

stack_a wins, but only because AES-CTR pads the plaintext up to a block boundary; with a true stream cipher the two orders would be equivalent.

B

stack_a writes a few hundred KB. In stack_b gzip is handed AES-CTR output, which has no repeated substrings left to reference, so it writes slightly more than 4 MB.

C

stack_b writes less. Encrypting first means the compressor never sees plaintext, and gzip still finds structure in the 16-byte block boundaries of the ciphertext.

D

Both write the same number of bytes. Gzip and AES-CTR are both byte-stream transforms, so the order changes only which one burns CPU first.