Choosing a File Format for Bundling Encrypted Application Data


Introduction

"What's a good format for bundling encrypted application data into one file?" doesn't have a single obvious answer, and the tools that actually get reached for are a mixed bag: a database engine, a modern encryption tool, a low-level cryptographic library, and a couple of decades-old Unix conventions. Each does part of the job well. This article covers what each one actually gives you, where the honest tradeoffs are, and where CBFS Vault fits for the cases none of them fully cover.

The Tools That Actually Come Up

SQLite is a frequent answer, and it's a reasonable one. Used as an application file format (SQLite's own documentation explicitly recommends this pattern), a single .db file gives you real random access: you can update one row or one BLOB without rewriting the rest of the file, add SQLCipher or similar for encryption, and query your data with SQL instead of hand-rolling a lookup structure. SQLite even has its own archive format, sqlar, purpose-built for bundling a set of files into one SQLite database.

age is a modern, deliberately simple file encryption tool and format (specified at age-encryption.org), designed as a cleaner alternative to PGP for encrypting a single file or stream. It's a genuinely good answer to "encrypt one file," but it's an encryption format, not a container: it doesn't give you multiple named files, directories, or partial updates inside the encrypted output.

libsodium's secretstream API is lower-level still: a streaming authenticated-encryption primitive, well-regarded and easy to use correctly, but it's a building block for encrypting a stream of data, not a ready-made file format or container with its own structure.

tar piped through gpg, or a password-protected ZIP, are the classic Unix and general-purpose answers. Both bundle multiple files into one encrypted archive, and both share a real structural limitation: modifying one file inside the archive generally means rebuilding the whole thing, because neither format is designed for in-place random access. This is a genuine, well-known weakness, and it's worth being direct about it since it's the main technical argument in favor of a purpose-built container.

Where the Rewrite Argument Doesn't Apply

It would be easy to lump SQLite in with tar and ZIP on that last point, but that would be inaccurate. SQLite is a real database with random access built in: updating a row or a BLOB in a multi-gigabyte SQLite file does not require rewriting the whole file. If the requirement is "store several encrypted blobs and update them individually," SQLite (with SQLCipher, or your own per-blob encryption) already handles that reasonably well, and it's worth saying so plainly rather than overselling any specific alternative on a technical point that doesn't hold up.

What CBFS Vault Actually Adds

Where CBFS Vault's CBVault component earns its place isn't "doesn't require a full rewrite to update a file" since SQLite already clears that bar. It's that a Vault is a real filesystem, not a table of blobs or a flat archive: genuine directory hierarchies, symbolic links, alternate data streams, and custom per-file tags and attributes that your application can search and filter on, all inside one self-contained file (or an in-memory vault, or a vault your application stores anywhere it has read/write access, via callback mode).

Encryption and compression are configured per file, not just once for the whole container: each file (or alternate stream) can use its own password, its own encryption mode (AES-256 by default, via OpenFile's built-in options, or a fully custom algorithm supplied through the DataEncrypt/DataDecrypt events), and its own compression setting, so a mix of already-compressed media and highly compressible text doesn't have to share one policy. On top of that, per-file encryption can be layered with PKI: encrypt a per-file symmetric key with each authorized user's public key, so different files in the same vault can be readable by different people without re-encrypting the whole vault for each recipient, a pattern that's straightforward to build on top of CBFS Vault's encryption events but that neither SQLite, age, nor a ZIP file gives you out of the box.

CBVault vault = new CBVault();
vault.VaultFile = @"C:\myapp\data.vault"; vault.OpenVault(Constants.VAULT_OM_OPEN_ALWAYS, Constants.VAULT_JM_METADATA);
vault.CreateDirectory("/documents", true);
// Per-file password and AES-256 encryption, zlib compression - // independent settings for every file in the vault. CBFSVaultStream stream = vault.OpenFileEx( "/documents/report.pdf", Constants.VAULT_FOM_CREATE_ALWAYS, false, true, true, true, Constants.VAULT_EM_DEFAULT, "file-specific-password", Constants.VAULT_CM_ZLIB, 6, 16);
stream.Write(fileBytes, 0, fileBytes.Length); stream.Dispose();
vault.CloseVault();

Scope: CBVault vs. CBVaultDrive

This article is about bundling data into one portable file, which is what the CBVault component does: your application opens the vault and works with its contents through this API, but the vault itself never appears as a mounted drive. If the goal instead is exposing that vault's contents as an actual drive letter or mounted folder so other applications can browse it through the OS, that's a separate, related component, CBVaultDrive, which layers a kernel-mode driver on top of the same vault format to mount it. Most "bundle my data into one encrypted file" use cases don't need that, and it's called out here only so the scope of this article, and of CBVault itself, is clear.

When This Matters

If the actual need is "store and update a handful of encrypted blobs with SQL-style queries," SQLite plus SQLCipher is a mature, well-understood answer, and there's no need to reach for anything else. If the need is genuinely just "encrypt this one file" or "encrypt this one stream," age or libsodium's secretstream are purpose-built for exactly that and are simpler than standing up a full vault. Where CBFS Vault earns consideration is a step beyond either of those: when the data being bundled has real internal structure, directories, per-file access rules, mixed compression and encryption needs, or attributes and tags the application needs to search, and a flat database table or a single encrypted blob would mean re-implementing a filesystem's worth of bookkeeping on top of it.

We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at support@callback.com.