David O'Meara
David O'Meara

Reputation: 3033

Blowfish code should be equivalent but is not

We have a class which wraps BouncyCastle (actually SpongyCastle for Android) Blowfish to encrypt data to stream:

public class BlowfishOutputStream extends OutputStream
{
    private final OutputStream os;
    private final PaddedBufferedBlockCipher bufferedCipher;

Our original code encrypted a whole byte array before writing to the output stream in a single operation

public void write(byte[] raw, int offset, int length) throws IOException
{
    byte[] out = new byte[bufferedCipher.getOutputSize(length)];
    int result = this.bufferedCipher.processBytes(raw, 0, length, out, 0);
    if (result > 0)
    {
        this.os.write(out, 0, result);
    }
}

When sending images (ie large amount of data at once) it results in two copies being retained in memory at once.

The following code is meant to be equivalent but is not, and I do not know why. I can verify that data is being sent (sum of c2 is equivalent to the length) but an intermediate process when it is received on our server discards the image before we get to see what arrives. All I know at this stage is that when the initial code is used, the response is received and the included images can be extracted, when the replacement code is used the response is received (and accepted) but images do not appear to be extracted.

public void write(byte[] raw, int offset, int length) throws IOException
{
    // write to the output stream as we encrypt, not all at once.
    final byte[] inBuffer = new byte[Constants.ByteBufferSize];
    final byte[] outBuffer = new byte[Constants.ByteBufferSize];
    ByteArrayInputStream bis = new ByteArrayInputStream(raw);
    // read into inBuffer, encrypt into outBuffer and write to output stream
    for (int len; (len = bis.read(inBuffer)) != -1;)
    {
        int c2 = this.bufferedCipher.processBytes(inBuffer, 0, len, outBuffer, 0);
        this.os.write(outBuffer, 0, c2);
    }
}

Note that the issue is not due to a missing call to doFinal, as this is called when the stream is closed.

public void close() throws IOException
{
    byte[] out = new byte[bufferedCipher.getOutputSize(0)];
    int result = this.bufferedCipher.doFinal(out, 0);
    if (result > 0)
    {
        this.os.write(out, 0, result);
    }
    *nb try/catch omitted*
}

Upvotes: 1

Views: 180

Answers (1)

David O'Meara
David O'Meara

Reputation: 3033

Confirmed, although ironically the issue was not with the images but in previous data, but that data was writing the complete raw byte array and not just the range specified. The equivalent code for encrypting the byte array on the fly is:

@Override
public void write(byte[] raw, int offset, int length) throws IOException
{
    // write to the stream as we encrypt, not all at once.
    final byte[] inBuffer = new byte[Constants.ByteBufferSize];
    final byte[] outBuffer = new byte[Constants.ByteBufferSize];
    int readStart = offset;
    // read into inBuffer, encrypt into outBuffer and write to output stream
    while(readStart<length)
    {
        int readAmount = Math.min(length-readStart, inBuffer.length);
        System.arraycopy(raw, readStart, inBuffer, 0, readAmount);
        readStart+=readAmount;
        int c2 = this.bufferedCipher.processBytes(inBuffer, 0, readAmount, outBuffer, 0);
        this.os.write(outBuffer, 0, c2);
    }
}

Upvotes: 2

Related Questions