1

While reviewing an encryption scheme, I came accross the following code:


    @Override
    public OutputStream encrypt(OutputStream outputStream) throws Exception {

        // generate the IV for encryption
        final byte[] encryptionIV = KeyFileUtil.randomBytes(16);
        outputStream.write(encryptionIV);

        // now create the encryption cipher
        final Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, getKey(), new GCMParameterSpec(128, encryptionIV));

        // The CipherOutputStream shouldn't close the underlying stream
        outputStream = new FilterOutputStream(outputStream) {
            @Override
            public void close() throws IOException {
                // Do nothing
            }
        };
        final CipherOutputStream cos = new CipherOutputStream(outputStream, cipher);

        if (useZip) {
            final ZipOutputStream zipOutputStream = new ZipOutputStream(cos) {
                @Override
                public void finish() throws IOException {
                    super.finish();
                    def.end();
                }

                @Override
                public void flush() {
                    // Do nothing.
                }

                @Override
                public void close() throws IOException {
                    try {
                        super.flush();
                    } catch (final IOException exception) {
                        // Continue and try to close.
                    }
                    super.close();
                }
            };
            zipOutputStream.putNextEntry(new ZipEntry("ResourceContents"));
            return zipOutputStream;
        }
        return cos;
    }

As far as I understand the ordering of the streams is such that data is being encrypted first then (uselessly) compressed. Is this correct or am I misunderstanding how ordering works on OutputStreams?

Thanks

phil
  • 313
  • 1
  • 4
  • 13
  • 1
    Not related to your question, but the anonymous ZipOutputStream subclass' looks suspicious to me. If it works at all, I'd docment the hell out of it. Nothing it provides is intuitive : why would you end the delfater in `finish()` ? `finish()` is not a method of OutputStream (which is the return type) so no one calling your implementation would even know they could finish() the stream. They'll likely `close()` it which will call `def.end()` anyway. Preventing flushes means you really know what you're doing (DeflaterOutputStreap has un syncFlush flag, you're messing with it). Just being curious – GPI Jul 29 '20 at 09:45

1 Answers1

5

Yes, you're misunderstanding (or reading) how ordering works.

At new ZipOutputStream(cos) the CipherOutputStream is wrapped by the ZOS, therefore data is first zipped, then passed on to the wrapped stream, which will encrypt the data, and then pass it on to the next wrapped stream, and so on.

The outermost stream gets first go at the data, and if compression is enabled, return zipOutputStream; is called, with the zipstream being the outermost stream. It's idiomatic use of FilterOutputStream.

Kayaman
  • 72,141
  • 5
  • 83
  • 121
  • Thanks! Do you have a link to some java docs that explains this? The constructor of [FilterOutputStream](https://docs.oracle.com/javase/8/docs/api/java/io/FilterOutputStream.html) mentions the stream is "built on top of the specified underlying output stream" but that doesn't give any indication as to which stream is applied first when they are chained – phil Jul 29 '20 at 09:33
  • @phil That's all you need. The `ZipOutputStream` is writing to the `CipherOutputStream`, not the other way round. – user207421 Jul 29 '20 at 09:36
  • @phil well, which stream is applied first with `ZipOutputStream out = new ZipOutputStream(new CipherOutputStream(foo));` when calling `out.write()`? In fact, if you get a `ZipOutputStream`, you'll know that zipping will be applied, but you can't necessarily know whether other operations are applied afterwards unless you know how the stream was constructed. – Kayaman Jul 29 '20 at 09:52
  • I found what I was looking for. The docs for FilterOutputStream mention: "these streams sit on top of an already existing output stream (the underlying output stream) **which it uses as its basic sink of data**". The fact it uses the existing stream as its sink is what defines the ordering of chained streams. Thanks @Kayaman – phil Jul 29 '20 at 09:52
  • To elaborate on my previous comment: when we have `ZipOutputStream(CipherOutputStream(outputStream))`, the sink of the `ZipOutputStream` is `CipherOutputStream(outputStream)` meaning that we are compressing then encrypting as intended – phil Jul 29 '20 at 11:40