0

I have a video file I have encrypted using AES algorithm but I need to decrypt the video before playing with video view. The problem is that the time it takes to decrypt the video is too much. Is there a way I can do it.

UPDATE:

E/ExoPlayerImplInternal: Playback error com.google.android.exoplayer2.ExoPlaybackException: Source error at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:579) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:164) at android.os.HandlerThread.run(HandlerThread.java:65) Caused by: com.google.android.exoplayer2.source.UnrecognizedInputFormatException: None of the available extractors (FragmentedMp4Extractor, Mp4Extractor, FlvExtractor, FlacExtractor, WavExtractor, AmrExtractor, PsExtractor, OggExtractor, TsExtractor, MatroskaExtractor, AdtsExtractor, Ac3Extractor, Ac4Extractor, Mp3Extractor, JpegExtractor) could read the stream. at com.google.android.exoplayer2.source.BundledExtractorsAdapter.init(BundledExtractorsAdapter.java:92) at com.google.android.exoplayer2.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1026) at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:415) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) at java.lang.Thread.run(Thread.java:764)

Joseph Ofem
  • 304
  • 2
  • 15

2 Answers2

2

When the file is stored locally you probably don't need to encrypt it at all. The local file storage in andorid is not visible for the user or any other apps. Only your app has acces to the storage so you don't need to worry about the user reading confidential data.

Edit: As i just saw your recent comment with local you mean on the device. You can save the video in the internal storage where nothing other than your app has acces to. Take a look at the documentary: https://developer.android.com/training/data-storage/app-specific

Luis Ka
  • 95
  • 7
  • On the contrary, The file is visible. I encrypted it because i didn't want it to be accessed outside of the application. However, when i used my file manager, i was able to see the file hence the need for encryption – Joseph Ofem Mar 31 '21 at 09:09
  • @Luis, thanks for the update. I tried it out and it worked as you said. But i really need to get around the encrypted files. Once again thanks – Joseph Ofem Mar 31 '21 at 11:05
1

Here you go. I don't know if it works, but it should:

import android.net.Uri;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.BaseDataSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.FileDataSource;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;

import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;

import static com.google.android.exoplayer2.util.Util.castNonNull;
import static java.lang.Math.min;

public class EncryptedFileDataSource extends BaseDataSource {
    public static class EncryptedFileDataSourceException extends IOException {
        public EncryptedFileDataSourceException(Throwable cause) {
            super(cause);
        }

        public EncryptedFileDataSourceException(String message, IOException cause) {
            super(message, cause);
        }
    }

    /**
     * {@link DataSource.Factory} for {@link EncryptedFileDataSource} instances.
     */
    public static final class Factory implements DataSource.Factory {
        @Nullable
        private TransferListener listener;

        private byte[] key;
        private String transformation;

        public Factory() {
            this(null, null);
        }

        public Factory(byte[] key) {
            this(key, null);
        }

        public Factory(byte[] key, String transformation) {
            setKey(key);
            setTransformation(transformation);
        }

        public void setKey(byte[] key) {
            if (key == null) {
                return;
            }
            this.key = key;
        }

        public void setTransformation(String transformation) {
            if (transformation == null) {
                transformation = "AES/ECB/PKCS5Padding";
            }
            this.transformation = transformation;
        }

        /**
         * Sets a {@link TransferListener} for {@link EncryptedFileDataSource} instances created by this factory.
         *
         * @param listener The {@link TransferListener}.
         * @return This factory.
         */
        public Factory setListener(@Nullable TransferListener listener) {
            this.listener = listener;
            return this;
        }

        @Override
        public EncryptedFileDataSource createDataSource() {
            EncryptedFileDataSource dataSource = new EncryptedFileDataSource(key, transformation);
            if (listener != null) {
                dataSource.addTransferListener(listener);
            }
            return dataSource;
        }
    }

    @Nullable
    private RandomAccessFile file;
    @Nullable
    private Uri uri;

    private long bytesRemaining;
    private boolean opened;

    private byte[] key;
    private String transformation;
    private Cipher cipher;

    private byte[] tmpBuffer;

    private EncryptedFileDataSource(byte[] key, String transformation) {
        super(false);

        setKey(key);
        setTransformation(transformation);
    }

    public void setKey(byte[] key) {
        this.key = key;
    }

    public void setTransformation(String transformation) {
        this.transformation = transformation;
    }

    @Override
    public long open(@NonNull DataSpec dataSpec) throws EncryptedFileDataSourceException {
        try {
            byte[] key = this.key;
            String transformation = this.transformation;

            Uri uri = dataSpec.uri;
            this.uri = uri;

            transferInitializing(dataSpec);

            if (key != null && transformation != null) {
                SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
                cipher = Cipher.getInstance(transformation);
                cipher.init(Cipher.DECRYPT_MODE, keySpec);
            }

            this.file = openLocalFile(uri);
            file.seek(dataSpec.position);
            bytesRemaining = dataSpec.length == C.LENGTH_UNSET ? file.length() - dataSpec.position
                    : dataSpec.length;
            if (bytesRemaining < 0) {
                throw new EOFException();
            }
        } catch (IOException
                | NoSuchPaddingException
                | NoSuchAlgorithmException
                | InvalidKeyException e) {
            throw new EncryptedFileDataSourceException(e);
        }

        opened = true;
        transferStarted(dataSpec);

        return bytesRemaining;
    }

    @Override
    public int read(@NonNull byte[] buffer, int offset, int readLength) throws EncryptedFileDataSourceException {
        if (readLength == 0) {
            return 0;
        } else if (bytesRemaining == 0) {
            return C.RESULT_END_OF_INPUT;
        } else {
            int bytesRead, bytesStored;
            try {
                int length = (int) min(bytesRemaining, readLength);

                Cipher cipher = this.cipher;
                if (cipher != null) {
                    tmpBuffer = resize(tmpBuffer, length);
                    bytesRead = castNonNull(file).read(tmpBuffer, 0, length);
                    bytesStored = cipher.doFinal(tmpBuffer, 0, length, buffer, offset);
                } else {
                    bytesStored = bytesRead = castNonNull(file).read(buffer, offset, length);
                }
            } catch (IOException
                    | BadPaddingException
                    | IllegalBlockSizeException
                    | ShortBufferException e) {
                throw new EncryptedFileDataSourceException(e);
            }

            if (bytesRead > 0) {
                bytesRemaining -= bytesRead;
                bytesTransferred(bytesRead);
            }

            return bytesStored;
        }
    }

    @Nullable
    @Override
    public Uri getUri() {
        return uri;
    }

    @Override
    public void close() throws EncryptedFileDataSourceException {
        uri = null;
        try {
            if (file != null) {
                file.close();
            }
        } catch (IOException e) {
            throw new EncryptedFileDataSourceException(e);
        } finally {
            file = null;
            key = null;
            transformation = null;
            cipher = null;
            if (opened) {
                opened = false;
                transferEnded();
            }

            byte[] tmpBuffer = this.tmpBuffer;
            this.tmpBuffer = null;
            if (tmpBuffer != null) {
                for (int i = tmpBuffer.length - 1; i >= 0; i -= 2) {
                    tmpBuffer[i] = (byte) 0;
                }
            }
        }
    }

    static byte[] resize(byte[] b, int newLen) {
        if (newLen < 0) return b;
        if (b == null || b.length < newLen) {
            return new byte[newLen];
        } else return b;
    }

    private static RandomAccessFile openLocalFile(Uri uri) throws FileDataSource.FileDataSourceException {
        try {
            return new RandomAccessFile(Assertions.checkNotNull(uri.getPath()), "r");
        } catch (FileNotFoundException e) {
            if (!TextUtils.isEmpty(uri.getQuery()) || !TextUtils.isEmpty(uri.getFragment())) {
                throw new FileDataSource.FileDataSourceException(
                        String.format(
                                "uri has query and/or fragment, which are not supported. Did you call Uri.parse()"
                                        + " on a string containing '?' or '#'? Use Uri.fromFile(new File(path)) to"
                                        + " avoid this. path=%s,query=%s,fragment=%s",
                                uri.getPath(), uri.getQuery(), uri.getFragment()),
                        e);
            }
            throw new FileDataSource.FileDataSourceException(e);
        }
    }
}

Use it like this:

public void playVideo(Uri uri, byte[] key, @Nullable String transformation) {
    playVideo(MediaItem.fromUri(uri), key, transformation);
}

public void playVideo(MediaItem media, byte[] key, @Nullable String transformation) {
    ExoPlayer player = new SimpleExoPlayer.Builder(this).build();
    playerView.setPlayer(player);
    
    try {
        DataSource.Factory dataSourceFactory = new EncryptedFileDataSource.Factory(key, transformation);
        ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
        
        ProgressiveMediaSource.Factory progressiveFactory = new ProgressiveMediaSource.Factory(
                dataSourceFactory,
                extractorsFactory
        );

        ProgressiveMediaSource mediaSource = progressiveFactory.createMediaSource(media);
        player.setMediaSource(mediaSource);
        //player.setMediaSource(mediaSource, startPositionMs: long);
        //player.setMediaSource(mediaSource, resetPosition: boolean);
        player.setPlayWhenReady(true);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

I used the following sources to make this:

IntoVoid
  • 914
  • 4
  • 7
  • I am getting an error when i press the play button. Please refer to my update for the error log. Thanks – Joseph Ofem Mar 31 '21 at 12:08
  • Maybe there is something wrong with my decryption. But the rest of the class should be working – IntoVoid Mar 31 '21 at 12:52
  • @JosephOfem Could you try it with a not encrypted video file and leave the key as null. (Also check the transformation. If it is the same as what you used for encryption) – IntoVoid Mar 31 '21 at 12:52