8

Project Requirement: Music player app which will download audio files, encrypt and save them. The audio files should be playable in the app only. No other app should be able to play the files. Nor the user should be able to copy the files.

Approach: I don't want the entire decryted audio file to exist at any moment. So I want to encrypt the audio file as soon as it is downloaded. Then when the file is to be played, I want it to be decrypted chunk-by-chunk and played. I believe this can be achieved by using stream. As far as I searched, a package named "just_audio" can play the audio from stream source.

Problem: I cannot find any encryption package for Flutter/Dart which will output the decrypted data in the form of a stream. This is the first time I am trying to implement encryption/decryption, so my knowledge is very poor in this regard.

Notes:

  1. The encryption does not need to be heavy. A typical user not being able to copy the files and play them elsewhere would suffice.
  2. Audio files are going to be large, some of them even hours in length.
  3. I need all the usual functions of a music player (e.g. albums, playlists, progress bars with seeking function, etc.)

Options:

  1. It will be best if there is a package which can do what I need, off the shelf.
  2. To find a basic package and then modifying it into doing what is needed.
  3. Some radically different solution, which takes entirely different path but provides all the solutions.
user1543784
  • 271
  • 2
  • 4
  • 19
  • check https://pub.dev/packages/encrypt and modify your input stream, for more see https://dart.dev/tutorials/language/streams#modify-stream-methods – pskink Nov 12 '21 at 06:57
  • I read what you suggested, but I'm not sure if I get it. Are you saying that I encrypt the input stream when audio file is being downloaded? – user1543784 Nov 12 '21 at 07:12
  • yes: you encrypt the input stream when audio file is being downloaded, this is done with https://dart.dev/tutorials/language/streams#modify-stream-methods - for example: `final small = Stream.fromIterable(['foo', 'bar', 'spam']); small .map((s) => s.toUpperCase()) .listen((u) => print('data converted: $u'))` – pskink Nov 12 '21 at 07:14
  • Sorry for the stupid question, but I fail to uderstand how this will help? IMO, the encryption part should not be a problem. It is okay if the file is encrypted after it is downloaded entirely. It is the decryption part at the time of playing the audio file which I cannot figure out. Or am I missing something here? – user1543784 Nov 12 '21 at 07:18
  • i gave you a small example: input stream contains 3 items: 'foo', 'bar', 'spam', run this code and check the logs to see "modified" stream - all you need is to replace "mapping" code (`(s) => s.toUpperCase()`) with some calls from `encrypt` package – pskink Nov 12 '21 at 07:23
  • Seems I failed to explain it correctly. I think the encryption part should not present any problem. The file will be downloaded entirely, ecnrypted, and saved. Then original file will be deleted as soon as encryption is done. But when it comes to playing the encrypted file, it has to be decrypted again. And this is when the whole decrypted file will be there as long as it is being played. I don't want this. Instead of decrypting the entire file, I want to decrypt and play it chunk-by-chunk. Every chunk should be played and deleted as soon as next chunk starts playing. – user1543784 Nov 12 '21 at 07:34
  • I believe most encryption methods won't allow decryption of chunks (i.e. streamed segments) of a file if the file itself was encrypted as a whole. Rather, you should split the downloaded file into chunks of fixed size, encrypt each separately, join the parts, then execute the process in reverse for decryption. Does that make sense? – David Schneider Feb 02 '22 at 12:04
  • @DavidSchneider yes this makes sense. I had a thought similar to this, but never gave it a real try. Though there are a few issues I think I will face. Like how does the seekbar of the audio player work in this case? Maybe if the chunks are small enough and the chunk where the seekbar has landed will start playing? I think this is going to keep me occupied for today. Thanks for the suggestion. – user1543784 Feb 02 '22 at 12:38
  • 1
    @user1543784 as long a s the encrypted file is either uncompressed, or compressed at a constant bitrate, I don't see the need for adjusting the block size. Just calculate the required block index based upon requested timecode & bitrate, decrypt & discard the "leading" portion of that block (before the requested timecode). – David Schneider Feb 02 '22 at 12:48
  • Hi, does my answer solve the question? – ch271828n Feb 08 '22 at 23:42

2 Answers2

3

Firstly, to encrypt or decrypt data, have a look at https://pub.dev/packages/cryptography or https://pub.dev/packages/encrypt or something like that.

Secondly, since you want seeking, it may not be an optimal solution to use streams - the "Stream" abstraction is more like a sequence of data, instead of arbitrarily jumping (seeking) here and there. Instead, divide whole audio (say 1hour) into chunks (say 1minute), and upload/encrypt/decrypt/download each chunk separately and as a whole without using streams. If your chunk is small enough, download/decrypt a chunk will be fast and you do not stuck the UI. If the decrypting is still too slow, have a look at isolates, which are "threads" in Flutter. Run decrypt in a separate isolate then your UI will be smooth.

I need all the usual functions of a music player (e.g. albums, playlists, progress bars with seeking function, etc.)

Seeking is implemented above. For albums/playlists, you may modify any normal audio players in flutter to implement that, or directly implement by your own. it is more like just some UI things, no special things, and anyone familiar with Flutter can write it, no worry.

ch271828n
  • 15,854
  • 5
  • 53
  • 88
  • Thanks for answer. I think you missed a minor detail though. The audio files are to be downloaded only once and stored in encrypted form. When playing, only the decryption is to be performed. Does this cause any change in your solution? – user1543784 Feb 03 '22 at 02:00
  • @user1543784 no change. – ch271828n Feb 03 '22 at 02:21
  • Currently I have to implement same functionality in my music app.But i am facing issue during decryption it takes a lot of time and i tried it with chunk but it takes lot of time and also after decrypt i can able to play that video. @ch271828n ,So please can you given me some example or reference code for chunks conversion and decrypt audio file and play that audio file. I am facing these issues in download functionality. – Kesmi Topiwala Mar 26 '23 at 17:43
0

if you're open to a 3rd package, don't reinvent the wheel..

try this here https://morioh.com/p/34a06006b299 with various CipherStream Options

If you can forego stream encryption and do it after you have the file then try this package, Credit: I used the sample from this answer by Hoaea Varghese

With AES all you need is the path to the file, and you can encrypt files or albums with something as simple as

encrypted_file_path = EncryptData.encrypt_file('your/file/path');

With the code below

import 'dart:io';
import 'package:aes_crypt/aes_crypt.dart';

class EncryptData {
  static String encrypt_file(String path) {
    AesCrypt crypt = AesCrypt();
    crypt.setOverwriteMode(AesCryptOwMode.on);
    crypt.setPassword('my cool password');
    String encFilepath;
    try {
      encFilepath = crypt.encryptFileSync(path);
      print('The encryption has been completed successfully.');
      print('Encrypted file: $encFilepath');
    } catch (e) {
      if (e.type == AesCryptExceptionType.destFileExists) {
        print('The encryption has been completed unsuccessfully.');
        print(e.message);
      }
      else{
        return 'ERROR';
      }
    }
    return encFilepath;
  }

  static String decrypt_file(String path) {
    AesCrypt crypt = AesCrypt();
    crypt.setOverwriteMode(AesCryptOwMode.on);
    crypt.setPassword('my cool password');
    String decFilepath;
    try {
      decFilepath = crypt.decryptFileSync(path);
      print('The decryption has been completed successfully.');
      print('Decrypted file 1: $decFilepath');
      print('File content: ' + File(decFilepath).path);
    } catch (e) {
      if (e.type == AesCryptExceptionType.destFileExists) {
        print('The decryption has been completed unsuccessfully.');
        print(e.message);
      }
      else{
        return 'ERROR';
      }
    }
    return decFilepath;
  }
}
Transformer
  • 6,963
  • 2
  • 26
  • 52