1

I have a encrypted mp4 file about 130M that I want to play in a VideoView. What is the best way to stream it? I don't want to copy to an unencrypted file and then use that is the time it takes to decrypt it is prohibitive on slower devices. What I have so far is copying to a file and start the VideoView by giving it that buffered file before the decryption finishes.

public class DecryptVideoTask extends AsyncTask<Void, File, File> {
    public static final String TAG = DecryptVideoTask.class.getCanonicalName();
    public static final int READ_AHEAD_BYTES = 65469100;

    private Context context;
    private VideoView videoView;
    private String encryptedFilePath;
    private ProgressDialog progressDialog;

    /**
     *
     * @param context currentContext
     * @param videoView VideoView that will play the video
     * @param encryptedFilePath conceal encrypted video
     * @param progressDialog progressDialog to hide when the video starts
     */
    public DecryptVideoTask(Context context, VideoView videoView, String encryptedFilePath, ProgressDialog progressDialog) {
        this.context = context;
        this.videoView = videoView;
        this.encryptedFilePath = encryptedFilePath;
        this.progressDialog = progressDialog;
    }

    @Override
    protected File doInBackground(Void... params) {
        File bufferFile = null;

        try {

            bufferFile = File.createTempFile("temp", ".mp4", context.getCacheDir());

            BufferedOutputStream bufferOS = new BufferedOutputStream(
                    new FileOutputStream(bufferFile));

            // getting CipherInputStream.  encryptedFilePath is the path to a conceal encrypted file
            InputStream is = Application.getInstance().getCryptoManager().decrypt(new File(encryptedFilePath));
            BufferedInputStream bis = new BufferedInputStream(is, 524288);

            byte[] buffer = new byte[32768];
            int numRead;
            long totalRead = 0;
            boolean started = false;

            long startTime = System.currentTimeMillis();
            while ((numRead = bis.read(buffer)) != -1) {

                bufferOS.write(buffer, 0, numRead);
                bufferOS.flush();
                totalRead += numRead;
                Log.d(TAG, "total read: " + totalRead);
                if (totalRead > READ_AHEAD_BYTES && !started) {
                    Log.d(TAG, "decryption took: " + (System.currentTimeMillis() - startTime) + "ms");
                    Log.d(TAG, "starting video: " + encryptedFilePath);
                    publishProgress(bufferFile);
                    started = true;
                }

            }

            bufferOS.close();
            bis.close();

            if (!started) {
                Log.d(TAG, "file is decrypted but video has not been started, starting now: " + encryptedFilePath);
                publishProgress(bufferFile);
                started = true;
            }
        } catch (IOException e) {
            Log.e(TAG, e.getMessage(), e);
            return bufferFile;
        }

        return bufferFile;
    }

    @Override
    protected void onProgressUpdate(File... values) {
        videoView.setVideoURI(Uri.parse("file://" + values[0].getAbsolutePath()));
        videoView.requestFocus();
        videoView.start();

        if (progressDialog != null && progressDialog.isShowing()) {
            progressDialog.dismiss();
        }
    }
}

The video starts but depending on what buffer sizes I fiddle with (READ_AHEAD_BYTES) the video may error out after a few seconds with

01-23 09:54:13.219 12087-12099/com.github.browep.privatephotovault E/MediaPlayer﹕ error (1, -1004) 01-23 09:54:13.219 12087-12087/com.github.browep.privatephotovault E/MediaPlayer﹕ Error (1,-1004) and a popup that says "can't play this video". Is there a better way to do this? FileDescriptor? something?

browep
  • 5,057
  • 5
  • 29
  • 37
  • Unless your `minSdkVersion` is under 11, I would just put the unencrypted video on internal storage, not external storage. You get the same level of security as you are getting with Conceal, with less overhead. And on API Level 11+ devices, usually internal and external storage are on the same partition and therefore have the same amount of available space. – CommonsWare Jan 23 '15 at 17:13
  • I understand that, however, the client I am building this for is pretty sensitive to risk and is worried about phones getting rooted and the app contents being raided, thus the "extra" step of using Conceal. I know this does not handle lots of attack vectors but it is the level that they want. – browep Jan 23 '15 at 17:22
  • Conceal does not help with rooted devices. Anyone with the ability to root the device can trivially decrypt the files. – CommonsWare Jan 23 '15 at 17:24
  • even without access to the encryption keys? – browep Jan 23 '15 at 17:25
  • Last I checked, with Conceal, the encryption keys are generated and stored on internal storage. Somebody with root access has access to the keys, and the encryption algorithm is public. Similarly, somebody with root access can get to an unencrypted video on internal storage. Hence, the only people that your use of Conceal will block will be those who know how to root a device but cannot decrypt the file (either themselves or using a Conceal-decrypter that somebody else wrote). IMHO, that's not a very big set of people and therefore not worth much effort. – CommonsWare Jan 23 '15 at 17:28
  • That's the default implementation which I have overridden for the reasons you explained above. You only need to pass a different implementation of https://github.com/facebook/conceal/blob/master/java/com/facebook/crypto/keychain/KeyChain.java to the Crypto constructor. – browep Jan 23 '15 at 17:42
  • In which case, presumably the encryption key is either being baked into your app (which can be obtained without root) or is being downloaded on the fly off of the Internet (which probably can be obtained without root). – CommonsWare Jan 23 '15 at 17:52
  • https://developer.android.com/training/articles/keystore.html for persisted keys, not baked into the APK. Like I said, I am not trying to handle all the attack vectors, just want something better than what stock gives me. I was hoping this wouldnt get into a SecOps discussion I just want to know if anyone has made this work with Facebook Conceal. – browep Jan 23 '15 at 18:01
  • OK, that's cool, at least for hardware-backed keystores. If you can switch from `VideoView` to `MediaPlayer` and a surface, I'd try a pipe using `ParcelFileDescriptor`. `VideoView` does not expose the `MediaPlayer` APIs that take a `FileDescriptor`, unfortunately. – CommonsWare Jan 23 '15 at 18:02

0 Answers0