# How to Create a File Encryption System Before Storage in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 30-40 min
- Compatibility: FlutterFlow Free+ (Firebase Storage requires Blaze plan for large files)
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

### 2. 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.

### 3. 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.

### 4. 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.

### 5. 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 code example

File: `FlutterFlow File Encryption System Setup`

```text
FIRESTORE DATA MODEL:
  encrypted_files/{docId}
    userId: Reference (users)
    fileName: String (e.g., 'contract.pdf')
    storagePath: String (e.g., 'encrypted/uid123/1711700000_contract.pdf')
    salt: String (base64, 16 bytes)
    iv: String (base64, 16 bytes)
    fileSize: Int (original bytes)
    mimeType: String (e.g., 'application/pdf')
    uploadedAt: Timestamp
    isEncrypted: Boolean (true)
    sharedBy: Reference (users, nullable)

CUSTOM ACTION: encryptAndUploadFile
  Input: fileBytes, passphrase, fileName
  Process:
    1. salt = SecureRandom(16 bytes)
    2. iv = SecureRandom(16 bytes)
    3. key = PBKDF2(passphrase, salt, iterations:100000, bits:256)
    4. encrypter = AES(key, mode: CBC)
    5. encrypted = encrypter.encrypt(fileBytes, iv: iv)
    6. Upload encrypted.bytes to Storage
    7. Create Firestore doc with salt, iv (base64), storagePath
  Returns: success or error

CUSTOM ACTION: decryptAndDownloadFile
  Input: fileDocRef, passphrase
  Process:
    1. Read doc → salt, iv, storagePath
    2. Download encrypted bytes from Storage
    3. key = PBKDF2(passphrase, base64Decode(salt), 100000, 256)
    4. decrypter = AES(key, mode: CBC)
    5. decrypted = decrypter.decrypt(encrypted, iv: base64Decode(iv))
  Returns: decrypted bytes or error

PAGE: MyEncryptedFilesPage
  Scaffold
    AppBar (title: "Encrypted Files")
    Backend Query: encrypted_files where userId == currentUser
    ListView
      Card (padding: 12)
        Row
          Icon (lock, blue)
          Expanded Column
            Text (fileName, bodyLarge)
            Text (fileSize formatted + uploadedAt, bodySmall, grey)
          IconButton (download)
            On Tap → Show Dialog with passphrase TextField
              On Submit → decryptAndDownloadFile
    FAB (icon: add)
      On Tap → BottomSheet
        Column
          FlutterFlowUploadButton
          TextField (passphrase, obscured)
          Passphrase strength indicator
          Button "Encrypt & Upload"
            On Tap → encryptAndUploadFile
```

## Common mistakes

- **Using the user's password directly as the AES encryption key** — Short or simple passwords create weak encryption keys that can be brute-forced. AES-256 needs a full 256-bit key derived from the password. Fix: Use PBKDF2 with HMAC-SHA256 and 100,000 iterations to derive a strong 256-bit key from any passphrase. The salt prevents precomputed attacks.
- **Reusing the same initialization vector for every file encrypted with the same key** — Reusing an IV with the same key in CBC mode leaks information about repeated plaintext blocks, weakening encryption significantly. Fix: Generate a fresh random 16-byte IV for each file encryption. Store the IV in the Firestore document alongside the salt.
- **Storing the encryption key or passphrase in Firestore or Firebase Storage** — Anyone with database or storage access can decrypt all files, defeating the purpose of client-side encryption entirely. Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-create-a-file-encryption-system-before-storage-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-create-a-file-encryption-system-before-storage-in-flutterflow
