Fetch icon indicating copy to clipboard operation
Fetch copied to clipboard

Resume supported on fly encryption example

Open Quuantum-Studios opened this issue 3 years ago • 3 comments

Thank you so much for creating this library @tonyofrancis . After reading solutions for different issues and spending a week implementing this library according to my needs, I can literally see how much hard work you have put into it by responding to issues and providing fixes. Really appreciate your dedication to this. I'll surely try to contribute to this soon.

I have implemented this library in my project and it's working great. Now I want that the documents and videos I download must be encrypted. I found similar issues like this #543 where I got this example code.

` public static class MyStorageResolver extends DefaultStorageResolver {

    public MyStorageResolver(Context context) {
        super(context, FetchCoreUtils.getFileTempDir(context));
    }


    @NotNull
    @Override
    
    public OutputResourceWrapper getRequestOutputResourceWrapper(@NotNull Downloader.ServerRequest request) {
        try {
            
            return new OutputResourceWrapper() {
                @Override
                public void write(@NotNull byte[] byteArray, int offSet, int length) throws IOException {
                   doCryptoInBlowFish(Cipher.ENCRYPT_MODE,"SAMPLEKEY",saveFile,saveFile);
                }

                @Override
                public void setWriteOffset(long offset) throws IOException {
                    fileOutputStream.getChannel().position(offset);
                }

                @Override
                public void flush() throws IOException {
                    fileOutputStream.flush();
                }

                @Override
                public void close() throws IOException {
                    fileOutputStream.close();
                }
            };
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return super.getRequestOutputResourceWrapper(request);
    }
    
}

`

And following this for doing simple encryption.

` private static void doCryptoInBlowFish(int cipherMode,String KEY,File inputFile,File outputFile) throws CryptoException{ String ALGORITHM = "Blowfish"; String MODE = "Blowfish/CBC/PKCS5Padding"; String IV = "!a3edr45";

    try {
        Key secretKey = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
        Cipher cipher = Cipher.getInstance(MODE);
        cipher.init(cipherMode, secretKey, new IvParameterSpec(IV.getBytes()));

        FileInputStream inputStream = new FileInputStream(inputFile);
        byte[] inputBytes = new byte[(int) inputFile.length()];
        inputStream.read(inputBytes);

        byte[] outputBytes = cipher.doFinal(inputBytes);

        FileOutputStream outputStream = new FileOutputStream(outputFile);
        outputStream.write(outputBytes);

        inputStream.close();
        outputStream.close();

    } catch (NoSuchPaddingException | NoSuchAlgorithmException
            | InvalidKeyException | BadPaddingException
            | IllegalBlockSizeException | IOException ex) {
        ex.fillInStackTrace();
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    }
}

`

Now from my understanding, it's going to probably work for continuous downloads but how it would work for resumed downloads as doCryptoInBlowFish is not taking or calculating any int offSet, int length for appending data to the right place. I have found this #470 which addresses this problem but there's no proper example provided for understanding.

If someone already achieved it in a better way, please post a full example for newbie programmers like us. Thank you.

Quuantum-Studios avatar Jul 09 '21 10:07 Quuantum-Studios

This is the code I managed to write after some research. But it's giving a download error.

` public static class MyStorageResolver extends DefaultStorageResolver { String ALGORITHM = "Blowfish"; String MODE = "Blowfish/CBC/PKCS5Padding"; String IV = "!a3edr45"; String KEY = "xxxxxxxx"; Cipher cipher;

    public MyStorageResolver(Context context) {
        super(context, FetchCoreUtils.getFileTempDir(context));
    }


    @NotNull
    @Override

    public OutputResourceWrapper getRequestOutputResourceWrapper(@NotNull Downloader.ServerRequest request) {
        try {
            Key secretKey = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
            cipher = Cipher.getInstance(MODE);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(IV.getBytes()));

            return new OutputResourceWrapper() {
                RandomAccessFile randomAccessFile = new RandomAccessFile(request.getFile(), "rw");

                @Override
                public void write(@NotNull byte[] byteArray, int offSet, int length) throws IOException {
                    randomAccessFile.write(cipher.update(byteArray, offSet, length), offSet, length);
                    Log.d("download", "raf writing");
                }

                @Override
                public void setWriteOffset(long offset) throws IOException {
                    int AES_BLOCK_SIZE = 16;
                    int skip = (int) (offset % AES_BLOCK_SIZE);
                    long blockOffset = offset - skip;
                    long numberOfBlocks = blockOffset / AES_BLOCK_SIZE;

                    BigInteger ivForOffsetAsBigInteger = new BigInteger(1, IV.getBytes()).add(BigInteger.valueOf(numberOfBlocks));
                    byte[] ivForOffsetByteArray = ivForOffsetAsBigInteger.toByteArray();
                    IvParameterSpec computedIvParameterSpecForOffset;
                    if (ivForOffsetByteArray.length < AES_BLOCK_SIZE) {
                        byte[] resizedIvForOffsetByteArray = BigInteger.valueOf(AES_BLOCK_SIZE).toByteArray();
                        System.arraycopy(ivForOffsetByteArray, 0, resizedIvForOffsetByteArray, AES_BLOCK_SIZE - ivForOffsetByteArray.length, ivForOffsetByteArray.length);
                        computedIvParameterSpecForOffset = new IvParameterSpec(resizedIvForOffsetByteArray);
                    } else {
                        computedIvParameterSpecForOffset = new IvParameterSpec(ivForOffsetByteArray, ivForOffsetByteArray.length - AES_BLOCK_SIZE, AES_BLOCK_SIZE);
                    }
                    try {
                        cipher.init(Cipher.ENCRYPT_MODE, secretKey, computedIvParameterSpecForOffset);
                    } catch (InvalidAlgorithmParameterException | InvalidKeyException e) {
                        e.printStackTrace();
                    }
                    byte[] skipBuffer = BigInteger.valueOf(skip).toByteArray();
                    try {
                        cipher.update(skipBuffer, 0, skip, skipBuffer);
                    } catch (ShortBufferException e) {
                        e.printStackTrace();
                    }
                    randomAccessFile.seek(offset);

                    Log.d("download", "raf seeked");

                }

                @Override
                public void flush() {
                }

                @Override
                public void close() throws IOException {
                    randomAccessFile.close();
                }
            };
        } catch (FileNotFoundException | NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException e) {
            Log.d("download", "raf error");

            e.printStackTrace();
        }
        return super.getRequestOutputResourceWrapper(request);
    }

}

`

I'm initializing fetch like this

` FetchConfiguration fetchConfiguration = new FetchConfiguration.Builder(this) .setDownloadConcurrentLimit(2) .enableRetryOnNetworkGain(true) // .setNamespace("TdeskDownloader") .enableFileExistChecks(true) .setProgressReportingInterval(500) .setAutoRetryMaxAttempts(3) .setNotificationManager(new DefaultFetchNotificationManager(this) {

                @Override
                public void updateNotification(@NotNull NotificationCompat.Builder notificationBuilder, @NotNull DownloadNotification downloadNotification, @NotNull Context context) {
                    Intent intent = new Intent(getBaseContext(), DownloadsActivity.class);
                    PendingIntent pendingIntent = PendingIntent.getActivity(getBaseContext(), 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);

                    notificationBuilder
                            .setOnlyAlertOnce(true)
                            .setColorized(true)
                            .setNotificationSilent()
                            .setContentIntent(pendingIntent)
                            .setAutoCancel(true)
                            .setColor(getResources().getColor(R.color.colorAccent))
                            .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher_round))
                            .setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL);

                    super.updateNotification(notificationBuilder, downloadNotification, context);
                }

                @NotNull
                @Override
                public Fetch getFetchInstanceForNamespace(@NotNull String namespace) {
                    return fetch;
                }

                @Override
                public boolean shouldCancelNotification(@NotNull DownloadNotification downloadNotification) {
                    return downloadNotification.getStatus() == Status.CANCELLED;
                }
            })
            .setHttpDownloader(new HttpUrlConnectionDownloader(Downloader.FileDownloaderType.PARALLEL))
            .setStorageResolver(new MyStorageResolver(this.getApplicationContext()))
            .build();
    Fetch.Impl.setDefaultInstanceConfiguration(fetchConfiguration);

    fetch = Fetch.Impl.getDefaultInstance();`

Can someone please help point me to where I'm making mistake in the code?

Quuantum-Studios avatar Jul 09 '21 22:07 Quuantum-Studios

This is the error i got in logs

2021-07-10 04:19:15.724 E/LibGlobalFetchLib: FileDownloader download:DownloadInfo(id=-1757988576, namespace='LibGlobalFetchLib', url='https://file-examples-com.github.io/uploads/2017/10/file-example_PDF_500_kB.pdf', file='/storage/emulated/0/Download/tdesk/file-example_PDF_500_kB.pdf', group=0, priority=HIGH, headers={}, downloaded=0, total=469513, status=DOWNLOADING, error=NONE, networkType=ALL, created=1625870930995, tag=null, enqueueAction=UPDATE_ACCORDINGLY, identifier=0, downloadOnEnqueue=true, extras={"mime":"application\/pdf"}, autoRetryMaxAttempts=0, autoRetryAttempts=3, etaInMilliSeconds=-1, downloadedBytesPerSecond=-1) java.lang.ArrayIndexOutOfBoundsException: src.length=8 srcPos=0 dst.length=1 dstPos=8 length=8 at java.lang.System.arraycopy(Native Method) at com.toppersdesk.app.commonUtility$MyStorageResolver$1.setWriteOffset(commonUtility.java:366) at com.tonyodev.fetch2.downloader.ParallelFileDownloaderImpl.downloadSliceFiles(ParallelFileDownloaderImpl.kt:416) at com.tonyodev.fetch2.downloader.ParallelFileDownloaderImpl.run(ParallelFileDownloaderImpl.kt:149) at com.tonyodev.fetch2.downloader.DownloadManagerImpl$start$$inlined$synchronized$lambda$1.run(DownloadManagerImpl.kt:103) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:764)

Quuantum-Studios avatar Jul 09 '21 22:07 Quuantum-Studios

@toppersdesk were you able to find a solution for this?

rishabh876 avatar Jun 08 '22 06:06 rishabh876