Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reproducing encrypted video using ExoPlayer

Tags:

I'm using ExoPlayer, in Android, and I'm trying to reproduce an encrypted video stored locally.

The modularity of ExoPlayer allows to create custom components that can be injected in the ExoPlayer, and this seems the case. Indeed, after some researches I realized that for achive that task I could create a custom DataSource and overriding open(), read() and close().

I have also found this solution, but actually here the entire file is decrypted in one step and stored in a clear inputstream. This can be good in many situation. But what if I need to reproduce big file?

So the question is: how can I reproduce encrypted video in ExoPlayer, decrypting content "on-fly" (without decrypting the entire file)? Is this possibile?

I tried creating a custom DataSource that has the open() method:

@Override     public long open(DataSpec dataSpec) throws FileDataSourceException {         try {             File file = new File(dataSpec.uri.getPath());              clearInputStream = new CipherInputStream(new FileInputStream(file), mCipher);              long skipped = clearInputStream.skip(dataSpec.position);             if (skipped < dataSpec.position) {                 throw new EOFException();             }             if (dataSpec.length != C.LENGTH_UNBOUNDED) {                 bytesRemaining = dataSpec.length;             } else {                 bytesRemaining = clearInputStream.available();                 if (bytesRemaining == 0) {                     bytesRemaining = C.LENGTH_UNBOUNDED;                 }             }         } catch (EOFException e) {             e.printStackTrace();         } catch (IOException e) {             e.printStackTrace();         }          opened = true;         if (listener != null) {             listener.onTransferStart();         }          return bytesRemaining;     } 

And this is the read() method:

@Override public int read(byte[] buffer, int offset, int readLength) throws FileDataSourceException {         if (bytesRemaining == 0) {             return -1;         } else {             int bytesRead = 0;                  int bytesToRead = bytesRemaining == C.LENGTH_UNBOUNDED ? readLength                         : (int) Math.min(bytesRemaining, readLength);             try {                 bytesRead = clearInputStream.read(buffer, offset, bytesToRead);             } catch (IOException e) {                 e.printStackTrace();             }              if (bytesRead > 0) {                 if (bytesRemaining != C.LENGTH_UNBOUNDED) {                     bytesRemaining -= bytesRead;                 }                 if (listener != null) {                     listener.onBytesTransferred(bytesRead);                 }             }              return bytesRead;         }     } 

If instead of an encoded file I pass a clear file, and just remove the CipherInputStream part, then it works fine, instead with encrypted file I obtain this error:

    Unexpected exception loading stream java.lang.IllegalStateException: Top bit not zero: -1195853062 at com.google.android.exoplayer.util.ParsableByteArray.readUnsignedIntToInt(ParsableByteArray.java:240) at com.google.android.exoplayer.extractor.mp4.Mp4Extractor.readSample(Mp4Extractor.java:331) at com.google.android.exoplayer.extractor.mp4.Mp4Extractor.read(Mp4Extractor.java:122) at com.google.android.exoplayer.extractor.ExtractorSampleSource$ExtractingLoadable.load(ExtractorSampleSource.java:745) at com.google.android.exoplayer.upstream.Loader$LoadTask.run(Loader.java:209) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588) at java.lang.Thread.run(Thread.java:818) 

EDIT:

the encrypted video is generated in this way:

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec keySpec = new SecretKeySpec("0123456789012345".getBytes(), "AES"); IvParameterSpec ivSpec = new IvParameterSpec("0123459876543210".getBytes()); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);  outputStream = new CipherOutputStream(output_stream, cipher); 

Then the outputStream is saved into a File.

like image 424
GVillani82 Avatar asked Aug 02 '16 19:08

GVillani82


1 Answers

Example how to play encrypted audio file, hope this will help to someone. I'm using Kotlin here

import android.net.Uri import com.google.android.exoplayer2.C import com.google.android.exoplayer2.upstream.DataSource import com.google.android.exoplayer2.upstream.DataSourceInputStream import com.google.android.exoplayer2.upstream.DataSpec import com.google.android.exoplayer2.util.Assertions import java.io.IOException import javax.crypto.CipherInputStream  class EncryptedDataSource(upstream: DataSource) : DataSource {      private var upstream: DataSource? = upstream     private var cipherInputStream: CipherInputStream? = null      override fun open(dataSpec: DataSpec?): Long {         val cipher = getCipherInitDecrypt()         val inputStream = DataSourceInputStream(upstream, dataSpec)         cipherInputStream = CipherInputStream(inputStream, cipher)         inputStream.open()         return C.LENGTH_UNSET.toLong()      }      override fun read(buffer: ByteArray?, offset: Int, readLength: Int): Int {         Assertions.checkNotNull<Any>(cipherInputStream)         val bytesRead = cipherInputStream!!.read(buffer, offset, readLength)         return if (bytesRead < 0) {             C.RESULT_END_OF_INPUT         } else bytesRead     }      override fun getUri(): Uri {         return upstream!!.uri     }      @Throws(IOException::class)     override fun close() {         if (cipherInputStream != null) {             cipherInputStream = null             upstream!!.close()         }     } } 

In function above you need to get Cipher which was used for encryption and init it: smth like this

fun getCipherInitDecrypt(): Cipher {     val cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");     val iv = IvParameterSpec(initVector.toByteArray(charset("UTF-8")))     val skeySpec = SecretKeySpec(key, TYPE_RSA)     cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv)     return cipher } 

Next step is creating DataSource.Factory for DataSource we've implemented earlier

import com.google.android.exoplayer2.upstream.DataSource  class EncryptedFileDataSourceFactory(var dataSource: DataSource) : DataSource.Factory {      override fun createDataSource(): DataSource {         return EncryptedDataSource(dataSource)     } } 

And last step is players initialization

    private fun prepareExoPlayerFromFileUri(uri: Uri) {         val player = ExoPlayerFactory.newSimpleInstance(                     DefaultRenderersFactory(this),                     DefaultTrackSelector(),                     DefaultLoadControl())          val playerView = findViewById<PlayerView>(R.id.player_view)         playerView.player = player          val dsf = DefaultDataSourceFactory(this, Util.getUserAgent(this, "ExoPlayerInfo"))         //This line do the thing         val mediaSource = ExtractorMediaSource.Factory(EncryptedFileDataSourceFactory(dsf.createDataSource())).createMediaSource(uri)         player.prepare(mediaSource)     } 
like image 177
Bender Avatar answered Sep 21 '22 03:09

Bender