Encrypt files on the device before uploading to Firebase Storage using a Custom Action with the encrypt Dart package. Derive a strong AES-256 key from the user's passphrase via PBKDF2 with a unique salt stored in the Firestore file document. On download, retrieve the encrypted bytes, decrypt with the same passphrase, and display or save the file locally.
Client-Side File Encryption with AES-256 in FlutterFlow
Standard Firebase Storage protects files with access rules, but anyone with storage access can read file contents. Client-side encryption adds a second layer: files are encrypted on the device before upload, so even a storage breach exposes only unreadable ciphertext. This tutorial uses AES-256 encryption with PBKDF2 key derivation for strong, password-based protection.
Prerequisites
- A FlutterFlow project with Firestore and Firebase Storage configured
- Firebase Authentication set up with user accounts
- Basic understanding of file upload and download in FlutterFlow
- The encrypt Dart package added as a Custom Action dependency
Step-by-step guide
Set up the Firestore document model for encrypted file metadata
Set up the Firestore document model for encrypted file metadata
Create an encrypted_files collection with fields: userId (Reference), fileName (String, original file name), storagePath (String, Firebase Storage path), salt (String, base64-encoded random salt used for key derivation), iv (String, base64-encoded initialization vector used for AES encryption), fileSize (Int, original file size in bytes), mimeType (String), uploadedAt (Timestamp), isEncrypted (Boolean, always true for this collection). The salt and IV are not secret — they are safe to store alongside the file reference. The security comes from the passphrase that only the user knows.
Expected result: Firestore has an encrypted_files collection storing metadata needed to decrypt each file, but not the encryption key itself.
Build the Custom Action for PBKDF2 key derivation and AES-256 encryption
Build the Custom Action for PBKDF2 key derivation and AES-256 encryption
Create a Custom Action named encryptAndUploadFile with parameters: fileBytes (FFUploadedFile), passphrase (String), fileName (String). Add the encrypt and crypto packages as dependencies. In the action: generate a random 16-byte salt and a random 16-byte IV using SecureRandom. Derive the 256-bit AES key using PBKDF2 with HMAC-SHA256, 100000 iterations, the salt, and the passphrase. Create an AES Encrypter in CBC mode with the derived key. Encrypt the file bytes with the IV. Upload the encrypted bytes to Firebase Storage at encrypted/{userId}/{timestamp}_{fileName}. Create the Firestore encrypted_files doc with the salt, IV (both base64-encoded), storage path, and file metadata. Return success.
Expected result: Files are encrypted client-side with AES-256 and uploaded as ciphertext. The salt and IV are stored in Firestore for later decryption.
Build the Custom Action for file decryption on download
Build the Custom Action for file decryption on download
Create a Custom Action named decryptAndDownloadFile with parameters: encryptedFileDoc (Document Reference), passphrase (String). The action reads the Firestore doc to get the salt, IV, and storagePath. Downloads the encrypted bytes from Firebase Storage. Decodes the base64 salt and IV. Derives the same AES-256 key using PBKDF2 with the same salt and passphrase. Decrypts the ciphertext with AES-CBC using the key and IV. If decryption fails (wrong passphrase), the decrypt call throws — catch this and return an error message. On success, return the decrypted bytes for display or local file save.
Expected result: Entering the correct passphrase decrypts the file successfully. A wrong passphrase produces a clear error message without crashing.
Build the encrypted file manager UI with upload and password entry
Build the encrypted file manager UI with upload and password entry
Create a MyEncryptedFilesPage. Add an Upload FAB that opens a BottomSheet with a file picker (FlutterFlowUploadButton) and a TextField for the encryption passphrase (obscured). On Submit, call the encryptAndUploadFile Custom Action with the selected file and passphrase. Show a CircularProgressIndicator during upload. Below the FAB, display a ListView querying encrypted_files where userId equals the current user, ordered by uploadedAt desc. Each list item shows a Container card with a lock Icon, file name Text, file size Text, and upload date Text. Tapping a file opens a Dialog with a password TextField. On Submit, call decryptAndDownloadFile. Show the decrypted file or a download option on success.
Expected result: Users can upload files with a passphrase, see their encrypted files listed with lock icons, and decrypt any file by entering the correct passphrase.
Add passphrase strength validation and secure sharing support
Add passphrase strength validation and secure sharing support
Before encryption, validate the passphrase: minimum 8 characters, at least one uppercase letter, one number. Show a strength indicator (weak/medium/strong) below the passphrase field using Conditional Styling on a Container. For file sharing between users, implement a Cloud Function shareEncryptedFile(fileDocId, recipientUserId) that duplicates the encrypted_files metadata doc for the recipient. The recipient still needs the passphrase from the sender (shared out of band). Display shared files with a different icon (share icon instead of lock icon) and the sharer's name.
Expected result: Users receive feedback on passphrase strength before encrypting, and can share encrypted file access with other users who know the passphrase.
Complete working example
1FIRESTORE DATA MODEL:2 encrypted_files/{docId}3 userId: Reference (users)4 fileName: String (e.g., 'contract.pdf')5 storagePath: String (e.g., 'encrypted/uid123/1711700000_contract.pdf')6 salt: String (base64, 16 bytes)7 iv: String (base64, 16 bytes)8 fileSize: Int (original bytes)9 mimeType: String (e.g., 'application/pdf')10 uploadedAt: Timestamp11 isEncrypted: Boolean (true)12 sharedBy: Reference (users, nullable)1314CUSTOM ACTION: encryptAndUploadFile15 Input: fileBytes, passphrase, fileName16 Process:17 1. salt = SecureRandom(16 bytes)18 2. iv = SecureRandom(16 bytes)19 3. key = PBKDF2(passphrase, salt, iterations:100000, bits:256)20 4. encrypter = AES(key, mode: CBC)21 5. encrypted = encrypter.encrypt(fileBytes, iv: iv)22 6. Upload encrypted.bytes to Storage23 7. Create Firestore doc with salt, iv (base64), storagePath24 Returns: success or error2526CUSTOM ACTION: decryptAndDownloadFile27 Input: fileDocRef, passphrase28 Process:29 1. Read doc → salt, iv, storagePath30 2. Download encrypted bytes from Storage31 3. key = PBKDF2(passphrase, base64Decode(salt), 100000, 256)32 4. decrypter = AES(key, mode: CBC)33 5. decrypted = decrypter.decrypt(encrypted, iv: base64Decode(iv))34 Returns: decrypted bytes or error3536PAGE: MyEncryptedFilesPage37 Scaffold38 AppBar (title: "Encrypted Files")39 Backend Query: encrypted_files where userId == currentUser40 ListView41 Card (padding: 12)42 Row43 Icon (lock, blue)44 Expanded Column45 Text (fileName, bodyLarge)46 Text (fileSize formatted + uploadedAt, bodySmall, grey)47 IconButton (download)48 On Tap → Show Dialog with passphrase TextField49 On Submit → decryptAndDownloadFile50 FAB (icon: add)51 On Tap → BottomSheet52 Column53 FlutterFlowUploadButton54 TextField (passphrase, obscured)55 Passphrase strength indicator56 Button "Encrypt & Upload"57 On Tap → encryptAndUploadFileCommon mistakes when creating a File Encryption System Before Storage in FlutterFlow
Why it's a problem: Using the user's password directly as the AES encryption key
How to avoid: Use PBKDF2 with HMAC-SHA256 and 100,000 iterations to derive a strong 256-bit key from any passphrase. The salt prevents precomputed attacks.
Why it's a problem: Reusing the same initialization vector for every file encrypted with the same key
How to avoid: Generate a fresh random 16-byte IV for each file encryption. Store the IV in the Firestore document alongside the salt.
Why it's a problem: Storing the encryption key or passphrase in Firestore or Firebase Storage
How to avoid: Never store the key or passphrase. The user must enter it each time they want to decrypt. Store only the salt and IV, which are not secret.
Best practices
- Generate a unique random salt and IV for every file encryption operation
- Use at least 100,000 PBKDF2 iterations to make brute-force attacks computationally expensive
- Validate passphrase strength before allowing encryption — minimum 8 characters with mixed case and numbers
- Show a clear error message when decryption fails due to wrong passphrase rather than displaying corrupted data
- Store file metadata (name, size, type) in Firestore so the file list loads without downloading encrypted content
- Add a loading indicator during encryption and decryption since large files take noticeable processing time
- Never log or print the passphrase or derived key in Custom Action debug output
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I need to build file encryption in FlutterFlow that encrypts files on the device before uploading to Firebase Storage. Show me the PBKDF2 key derivation, AES-256-CBC encryption Custom Action, Firestore metadata model, and decryption flow.
Create a page called Encrypted Files with a list showing file name, file size, and a lock icon for each item. Add a floating action button that opens a bottom sheet with a file picker and a password field. Add a download button on each list item.
Frequently asked questions
Is client-side encryption in FlutterFlow truly secure?
Yes, when implemented correctly. AES-256 with PBKDF2 key derivation is industry-standard encryption. The security depends on passphrase strength — a strong passphrase makes brute-force attacks computationally infeasible.
What happens if the user forgets their encryption passphrase?
The file cannot be decrypted. There is no recovery mechanism by design — this is what makes encryption secure. Advise users to store passphrases in a password manager.
Can I encrypt files larger than 10MB in FlutterFlow?
AES encryption of large files can be slow on mobile devices. For files over 10MB, consider encrypting in a Cloud Function instead. The client uploads the raw file to a temporary secure location, the Cloud Function encrypts and moves it to permanent storage.
Does encrypting files affect Firebase Storage costs?
Encrypted files are roughly the same size as the originals (AES adds minimal padding). Storage costs remain essentially the same. The overhead is in processing time on the device, not storage space.
Can two users decrypt the same file with different passwords?
Not with this single-key approach. Both users need the same passphrase. For per-user access, implement RSA public-key encryption: encrypt the file key with each recipient's public key so each person decrypts with their own private key.
What if I need end-to-end encrypted file sharing across my app?
RapidDev has implemented end-to-end encryption systems in FlutterFlow apps with per-user key pairs, key exchange protocols, and encrypted file sharing for compliance-sensitive industries like healthcare and finance.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation