4

I'm working on android application where I need to save Videos in SD Card which must not be transferable that's why I'm encrypting and decrypting as and when needed with Facebook Conceal which works perfectly fine if video size is smaller.

Whenever I tries to do encryption and decryption to large video files not more than 10MB in GenyMotion running 2.3.7 it crashes with OutOfMemoryException which means I'm running out of heap memory allocated to my application which can't be handled but must be prevented.

Tried :

  • Apache Common Utils IO package
  • Various IO Utils

Facebook Conceal : Says while decrypting

 You must read the entire stream to completion.
 The verification is done at the end of the stream.
 Thus not reading till the end of the stream will cause
 a security bug. For safety, you should not
 use any of the data until it's been fully read or throw
 away the data if an exception occurs.

Code I'm invoking which encryption and decryption with Facebook Conceal :

Encryption :

public void startEncryption() {
    // Creates a new Crypto object with default implementations of
    // a key chain as well as native library.
    // Check for whether the crypto functionality is available
    // This might fail if android does not load libaries correctly.
    if (!crypto.isAvailable()) {
        return;
    }
    OutputStream fileStream;
    try {
        File mEncryptedFile = new File(mPlainFile.getPath().substring(0,
                mPlainFile.getPath().length() - 4)
                + "_encrypted"
                + mPlainFile.getPath().substring(
                        mPlainFile.getPath().length() - 4,
                        mPlainFile.getPath().length()));

        fileStream = new BufferedOutputStream(new FileOutputStream(
                mEncryptedFile));

        // Creates an output stream which encrypts the data as
        // it is written to it and writes it out to the file.
        OutputStream outputStream;
        outputStream = crypto.getCipherOutputStream(fileStream, entity);
        outputStream.write(FileUtils.readFileToByteArray(mPlainFile));
        // fileStream.flush();
        // fileStream.close();
        // outputStream.flush();
        // outputStream.close();
        // outputStream.flush();
        File mRenameTo = new File(mPlainFile.getPath());
        mPlainFile.delete();
        mEncryptedFile.renameTo(mRenameTo);
    } catch (FileNotFoundException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (CryptoInitializationException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (KeyChainException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

Decryption :

public String startDecryption() {
    // Get the file to which ciphertext has been written.
    try {
        FileInputStream fileStream = new FileInputStream(mPlainFile);

        // Creates an input stream which decrypts the data as
        // it is read from it.
        InputStream inputStream;
        inputStream = crypto.getCipherInputStream(fileStream, entity);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // org.apache.commons.io.output.ByteArrayOutputStream out = new
        // org.apache.commons.io.output.ByteArrayOutputStream(1024);
        // Read into a byte array.
        // int read;
        // byte[] buffer = new byte[1024];
        // // You must read the entire stream to completion.
        // // The verification is done at the end of the stream.
        // // Thus not reading till the end of the stream will cause
        // // a security bug.
        // int i = 0;
        // while ((read = inputStream.read(buffer)) != -1) {
        // out.write(buffer, 0, read);
        // Log.i(TAG, "bytearrayoutputstream "+i++ + " "+read + " " +
        // buffer.length + " "+out.size());
        // }

        mDecryptedFile = new File(mPlainFile.getPath().substring(0,
                mPlainFile.getPath().length() - 4)
                + "_decrypted"
                + (mPlainFile.getPath().substring(mPlainFile.getPath()
                        .length() - 4, mPlainFile.getPath().length())));

        OutputStream outputStream = new FileOutputStream(mDecryptedFile);
        // IOUtils.copy(inputStream, outputStream);

        try {
            final byte[] buffer = new byte[1024];
            int read;

            while ((read = inputStream.read(buffer)) != -1)
                outputStream.write(buffer, 0, read);

            outputStream.flush();
        } catch (Exception e) {

        } finally {
            outputStream.close();
        }

        // out.writeTo(outputStream);
        // out.flush();
        // out.close();

        // fileStream.close();
        inputStream.close();
        // outputStream.flush();
        outputStream.close();
        return mDecryptedFile.getPath();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (CryptoInitializationException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (KeyChainException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return null;
}

Is there any solution which can work around and encrypts and decrypts large video files too?

Vikalp Patel
  • 10,669
  • 6
  • 61
  • 96

2 Answers2

2

Looks like you are reading in the whole file into a byte array. What you should do is create an input stream and feed it to the crypto engine.

So this:

 outputStream.write(FileUtils.readFileToByteArray(mPlainFile));

Should look like your decryption loop.

BufferedInputStream  bis = new BufferedInputStream(mPlainFile);
InputStream inputStream = crypto.getCipherInputStream(
                             bis,
                             entity);
while ((read = inputStream.read(buffer)) != -1) {
   outputStream.write(buffer, 0, read);
}

Not sure if this is what you ended up with. I pulled the crypto decoding from the docs. I think this confirms that their intent is to say the stream must be read until the end.

Bruce
  • 306
  • 2
  • 7
  • 2
    i'm not sure, but maybe it depends on algorithm, there are some different types ( e.g. Stream cipher, Block cipher) – Lostboy Jun 16 '15 at 10:05
  • As Facebook stated clearly on their document ` You must read the entire stream to completion. The verification is done at the end of the stream. Thus not reading till the end of the stream will cause a security bug. For safety, you should not use any of the data until it's been fully read or throw away the data if an exception occurs.`, by putting it like Stream didn't decrypt the file. – Vikalp Patel Jun 17 '15 at 08:54
  • @Bruce 's answer should work. The Facebook says you must read the entire stream to completion, but it is not necessary to load the whole file in memory. This means if any kind of error occurs you should delete the file, which has been produced, because it is not secure. – Anton Krosnev Jun 17 '15 at 08:54
  • @AntonK. Thanks AntonK. I've tried reading it, as Bruce said but unfortunately it leads to can't decrypt form. If you know some other way then it would be really helpful :) – Vikalp Patel Jun 17 '15 at 09:34
0

As Bruce did highlight on bottleneck on Encryption which leads to OutOfMemoryException at time of Decryption. So here's code which i'm executing while encrypting and decrypting which no more leads to OutOfMemoryException.

Encryption :

fileStream = new BufferedOutputStream(new FileOutputStream(mEncryptedFile));
OutputStream outputStream;
outputStream = crypto.getCipherOutputStream(fileStream, entity);
int read;
byte[] buffer = new byte[1024];
BufferedInputStream  bis = new BufferedInputStream(newFileInputStream(mPlainFile));
while ((read = bis.read(buffer)) != -1) {
       outputStream.write(buffer, 0, read);
    }
outputStream.close();
bis.close();

Decryption :

InputStream inputStream;
inputStream = crypto.getCipherInputStream(fileStream, entity);
ByteArrayOutputStream out = new ByteArrayOutputStream();
OutputStream outputStream = new FileOutputStream(mDecryptedFile);
BufferedInputStream bis = new BufferedInputStream(inputStream);
int mRead;
byte[] mBuffer = new byte[1024];
while ((mRead = bis.read(mBuffer)) != -1) {
   outputStream.write(mBuffer, 0, mRead);
    }
bis.close();
out.writeTo(outputStream);
inputStream.close();
outputStream.close();
out.close();
Vikalp Patel
  • 10,669
  • 6
  • 61
  • 96