Open meenukrishnamurthy opened 5 years ago
One major mistake is you didn't give a buffer to AesCipherDataSink which makes CacheDataSource return encrypted data on the first read. I noticed it's quite hard to get this code right. I'll do some improvements on the library but for now here is a working code with ExoPlayer Demo app.
private static final String SECRET_KEY = "testKey:12345678";
private static final int ENCRYPTION_BUFFER_SIZE = 10 * 1024;
private synchronized void initDownloadManager() {
if (downloadManager == null) {
Cache cache = getDownloadCache();
DownloaderConstructorHelper downloaderConstructorHelper =
new DownloaderConstructorHelper(
cache,
buildHttpDataSourceFactory(),
getCacheReadDataSourceFactory(),
getCacheWriteDataSinkFactory(cache),
/* priorityTaskManager= */ null);
downloadManager =
new DownloadManager(
new File(getDownloadDirectory(), DOWNLOAD_ACTION_FILE),
new DefaultDownloaderFactory(downloaderConstructorHelper),
MAX_SIMULTANEOUS_DOWNLOADS,
DownloadManager.DEFAULT_MIN_RETRY_COUNT);
downloadTracker =
new DownloadTracker(
/* context= */ this,
buildCacheDataSource(buildHttpDataSourceFactory(), cache),
new File(getDownloadDirectory(), DOWNLOAD_TRACKER_ACTION_FILE));
downloadManager.addListener(downloadTracker);
}
}
private static CacheDataSourceFactory buildReadOnlyCacheDataSource(
DefaultDataSourceFactory upstreamFactory, Cache cache) {
return new CacheDataSourceFactory(
cache,
upstreamFactory,
getCacheReadDataSourceFactory(),
/* cacheWriteDataSinkFactory= */ null,
CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR,
/* eventListener= */ null);
}
private static CacheDataSourceFactory buildCacheDataSource(
Factory upstreamFactory, Cache cache) {
return new CacheDataSourceFactory(
cache,
upstreamFactory,
getCacheReadDataSourceFactory(),
getCacheWriteDataSinkFactory(cache),
CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR,
/* eventListener= */ null);
}
private static DataSource.Factory getCacheReadDataSourceFactory() {
return () -> new AesCipherDataSource(Util.getUtf8Bytes(SECRET_KEY), new FileDataSource());
}
private static DataSink.Factory getCacheWriteDataSinkFactory(final Cache cache) {
return () -> {
CacheDataSink cacheSink =
new CacheDataSink(cache, CacheDataSource.DEFAULT_MAX_CACHE_FILE_SIZE);
return new AesCipherDataSink(
Util.getUtf8Bytes(SECRET_KEY), cacheSink, new byte[ENCRYPTION_BUFFER_SIZE]);
};
}
Thanks a ton the solution worked
I'm glad that you find it useful. Let's keep this issue to track improvements on this issue.
Can you recommend a way of creating the SECRET_KEY? I guess it is not optimal to just hardcode something random. I am thinking to either generate a key based on some installationID or use something from the jetpack security library. I first tried
val masterKey = MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build();
val keyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
Then I could use the keyAlias
as the secret key. But MasterKeys is deprecated and also requires API 23. Any recommendations here would be appreciated.
Can you recommend a way of creating the SECRET_KEY?
I don't think this is an ExoPlayer specific question. It's a generic question about key management. We're not experts in this area and I imagine it's probably quite a complicated topic (e.g., where you store the key after you generate it is presumably also important). I think you'll find better resources elsewhere. A few Google searches for things like ("secret keys android") suggests there's quite a lot of information online about this topic. You'll likely also find people more qualified to answer questions about this on Stackoverflow than you will here.
@ojw28 and @erdemguven Thanks a lot for your responses.
private static final int ENCRYPTION_BUFFER_SIZE = 10 * 1024;
private synchronized void initDownloadManager() {
if (downloadManager == null) {
DefaultDownloadIndex downloadIndex = new DefaultDownloadIndex(getDatabaseProvider());
upgradeActionFile(
DOWNLOAD_ACTION_FILE, downloadIndex, /* addNewDownloadsAsCompleted= */ false);
upgradeActionFile(
DOWNLOAD_TRACKER_ACTION_FILE, downloadIndex, /* addNewDownloadsAsCompleted= */ true);
downloadManager = new DownloadManager(
this,
getDatabaseProvider(),
getDownloadCache(),
buildCacheDataSourceFactory(),
Executors.newFixedThreadPool(6)
);
downloadTracker =
new DownloadTracker(/* context= */ this, buildReadOnlyCacheDataSourceFactory(), downloadManager);
}
}
public DataSource.Factory buildReadOnlyCacheDataSourceFactory() {
DefaultDataSourceFactory upstreamFactory =
new DefaultDataSourceFactory(this, buildHttpDataSourceFactory());
return new CacheDataSource.Factory()
.setCache(getDownloadCache())
.setUpstreamDataSourceFactory(upstreamFactory)
.setCacheReadDataSourceFactory(getCacheReadDataSourceFactory())
.setCacheWriteDataSinkFactory(null)
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
}
public CacheDataSource.Factory buildCacheDataSourceFactory() {
DefaultDataSourceFactory upstreamFactory =
new DefaultDataSourceFactory(this, buildHttpDataSourceFactory());
return new CacheDataSource.Factory()
.setCache(getDownloadCache())
.setCacheKeyFactory(CacheKeyFactory.DEFAULT)
.setCacheWriteDataSinkFactory(getCacheWriteDataSinkFactory(getDownloadCache()))
.setCacheReadDataSourceFactory(getCacheReadDataSourceFactory())
.setUpstreamDataSourceFactory(upstreamFactory)
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
}
public HttpDataSource.Factory buildHttpDataSourceFactory() {
return new DefaultHttpDataSource.Factory().setUserAgent(userAgent);
}
private static DataSource.Factory getCacheReadDataSourceFactory() {
return () -> {
return new AesCipherDataSource(Util.getUtf8Bytes("secretkey1234"), new FileDataSource());
}
};
}
private static DataSink.Factory getCacheWriteDataSinkFactory(final Cache cache) {
return () -> {
CacheDataSink cacheSink =
new CacheDataSink(cache, CacheDataSink.DEFAULT_FRAGMENT_SIZE, CacheDataSink.DEFAULT_BUFFER_SIZE);
return new AesCipherDataSink(
Util.getUtf8Bytes("secretkey1234"), cacheSink, new byte[ENCRYPTION_BUFFER_SIZE]);
};
}
@meenukrishnamurthy Hey Thanks for your question, that helped a lot.
@abrarahmadraza - I think the problem is where you're doing:
downloadManager = new DownloadManager(
this,
getDatabaseProvider(),
getDownloadCache(),
buildCacheDataSourceFactory(),
Executors.newFixedThreadPool(6)
the fourth argument is supposed to be a DataSource
for requesting from upstream, rather than one that writes to the cache. If you look at what happens in that constructor (here), you'll see that it's building a CacheDataSource.Factory
internally to write to the cache, and it doesn't encrypt. Since your code is passing a CacheDataSource.Factory
in as upstream, you end up with one wrapping the other, so everything is written to the cache twice, and the one that writes last is the internal one that doesn't encrypt.
To fix this, you need to use the DownloadManager
constructor that takes Context context, WritableDownloadIndex downloadIndex, DownloaderFactory downloaderFactory
. Something like:
downloadManager =
new DownloadManager(
context,
new DefaultDownloadIndex(getDatabaseProvider(context)),
new DefaultDownloaderFactory(
buildCacheDataSourceFactory(),
Executors.newFixedThreadPool(/* nThreads= */ 6)));
I am facing a issue while encryption and decryption of offline video.I am using the download manager and download tracker to download the offline video like in the demo app. My code is as follows:
I call the above method while creating the datasource factory
I call the method buildDataSourceFactoryCache in initdownload manager,
My download does not take place it shows as download failed.Can anyone tell me where i am going wrong