Seedvault Documentation

repository·android17·Indexed 23 days ago

https://github.com/seedvault-app/seedvault

A backup application for the Android Open Source Project (AOSP) that enables users to back up and restore application data and files to encrypted flash drives or network storage. It features BIP39 mnemonic phrase encryption, content-defined chunking via the FastCDC algorithm, and a hierarchical key derivation model. The documentation covers repository structure, data formats using Tink and zstd, snapshot management, and the security threat model for storing backups in untrusted locations.

Tokens
9.1K
Snippets
5
Records
48
Agent score
83%

What's inside Seedvault

  1. Overview of Local Contacts Backup

    android17

    Local Contacts Backup is an application designed to back up contacts stored locally on the device using the Android system's backup API.

    Important Note: This application explicitly excludes contacts that are synchronized via sync accounts (for example, contacts synced via DAVx⁵). It is intended specifically for contacts that reside only on the device's local storage.

  2. Use Seedvault Storage for backup implementation

    android17
    Seedvault Storage is a library designed for performing storage backups. While it is the core component of the Seedvault app, it is architected to be used by other applications that need to provide storage backup functionality. For a complete understanding of the underlying architecture and implementation details, refer to the design document.
  3. Compare Seedvault to restic

    android17

    While inspired by restic, Seedvault is optimized for Android app backups through several simplifications:

    FeatureSeedvault Implementation
    ConfigurationNo config file needed. Repository ID is the folder name; version is the first byte of all files.
    Key ManagementNo keys files. Re-uses BIP39 recovery code; the derived key is stored in the device key store.
    Blob StorageBlobs are saved directly rather than combined into pack files.
    IndexingNo indexes. A mapping of chunk ID to storage ID of blobs is stored directly in the snapshot.
    StructureNo tree blobs. Android app backups are flat (list of apps and APKs); metadata is stored in the snapshot.
    ConcurrencyNo locks. Single app usage is enforced by binding the repository to the app/user/device.
    EncryptionUses AES-GCM via Google's Tink library.
    SnapshotsSnapshots use a .snapshot extension and names start with the content hash.
  4. Seedvault Cryptography Overview

    android17

    Seedvault's cryptographic design prioritizes simplicity and security, primarily focusing on concealing file content. While it does not currently hide file sizes, it does encrypt file names and paths.

    Key Components:

    • Master Key: Derived from a BIP39 mnemonic. The first 256 bits are used as an AES key for app data. The second 256 bits are imported into the Android Keystore to act as a master key for deriving other keys via HKDF (RFC 5869).
    • Primitives: Uses AES-GCM and SHA256 for hardware acceleration on modern ARMv8 CPUs.
    • Chunk ID Calculation: Uses a keyed hash (derived via HKDF with info "Chunk ID calculation") to prevent leaking file content through public hashes.
    • Stream Encryption: Uses the tink library's AesGcmHkdfStreaming implementation. A stream key is derived via HKDF with info "stream key".
  5. How removing old backups works

    android17

    Seedvault allows for pruning old backups to manage storage capacity.

    The pruning process:

    1. Selection: The user (or an automated scheme) selects backups for deletion.
    2. Inspection: To determine which to delete, backup snapshots are downloaded and inspected. File names can be derived from their timeStart timestamp.
    3. Reference Counting: When a snapshot is selected for deletion, the reference counter of every chunk included in that snapshot is decremented. Note that a single chunk might be referenced multiple times by one snapshot, but the counter tracks the number of snapshots referencing it.
    4. Deletion: The backup snapshot file and any chunks with a reference count of 0 are deleted from the storage.
  6. Understand the Seedvault key derivation process

    android17

    Seedvault uses a hierarchical key derivation process starting from a BIP39 seed (12 words, 128-bit entropy) obtained via SecureRandom. This entropy is transformed into a 512-bit seed key using PBKDF with SHA512.

    The 512-bit seed key is split into two functional halves:

    1. App data encryption key (256-bit): The first half. It is used to encrypt app data retrieved from AOSP and is locked in Android's Keystore. Note: This usage is deprecated.
    2. Main key (256-bit): The second half. This is used to derive application-specific keys via HKDF:
      • Stream key: Derived using HKDF with info "stream key". This is used by the Tink library to derive subkeys for individual streams using salt, nonce, and counters.
      • Chunk ID calculation: Derived using HKDF with info "Chunk ID calculation". This generates deterministic HMAC-SHA256 hashes over chunk contents to prevent leaking file information to third parties.
  7. Understand the Seedvault data format

    android17

    All files in the repository follow a specific structure for versioning and security. Every file starts with a version byte (currently 0x02), followed by an encrypted and authenticated Tink payload.

    Payload Structure

    When the payload is decrypted, the first four bytes represent the compressed plaintext size as a signed 32-bit integer. This indicates where the compressed data ends and padding begins.

    Format Diagram:

    ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
    ┃         ┃ encrypted tink payload (with 40 bytes header) ┃
    ┃ version ┃ ┏━ plaintext ━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┓     ┃
    ┃ 1 byte  ┃ ┃ size uint32 ┃ compressed ┃  padding   ┃     ┃
    ┃  (0x02) ┃ ┃   4 bytes   ┃ plaintext  ┃ (optional) ┃     ┃
    ┃         ┃ ┗━━━━━━━━━━━━┻━━━━━━━━━━━━┻━━━━━━━━━━━━┛     ┃
    ┗━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
    • Compression: Uses the zstd algorithm in its default configuration.
    • Blob Payloads: Include raw bytes of compressed chunks and are always padded.
    • Snapshot Payloads: Include compressed protobuf encoding and are not padded.
  8. How making a backup works

    android17

    A backup run scans files using Android's MediaProvider and ExternalStorageProvider based on user preferences.

    The process follows these steps:

    1. Scanning: Files are checked against the local files cache. If a file's content-modification indicators (size, lastModified, and generation for media) haven't changed and its chunks are present in storage, it is added to the snapshot without re-uploading.
    2. Chunking: New or modified files are processed by a chunker. Large files are split into chunks, while very small files are grouped into zip chunks.
    3. Uploading: Each chunk is hashed (keyed), compressed, and encrypted (authenticated) before being written to storage. New chunks are added to the chunks cache.
    4. Finalization: Once all files are processed, the backup snapshot is finalized with file metadata (including chunk IDs) and written (encrypted) to storage. Reference counters for the included chunks are then incremented.

    Resuming failures: If a backup fails, the next run can auto-resume. Chunks uploaded during the failed run remain in storage with a reference count of 0 and are available for the next attempt.

  9. Understand the Seedvault Threat Model

    android17

    Seedvault is designed to securely store backups in untrusted locations (e.g., shared systems or cloud storage).

    Security Guarantees

    • Confidentiality: Unencrypted content of stored files and metadata (excluding file size and creation time) cannot be accessed without the repository's recovery code. Everything is encrypted and authenticated.
    • Integrity: Modifications to data (due to hardware failure or tampering) can be detected. Tampered data will not be decrypted.

    Security Assumptions

    • The device creating the backup is trusted.
    • The user keeps the recovery code secret.
    • The storage location is not protected against file deletion (an attacker with write access can delete backups).
    • Cryptographic primitives (AES-GCM-256 and SHA-256) remain secure.
    • Brute-force attacks against cryptographic protections remain infeasible.
  10. Understand Seedvault storage backup terminology

    android17

    To work with Seedvault storage, it is important to understand the following core abstractions:

    • Backup snapshot (or short backup): A file containing metadata about a collection of files at a specific point in time.
    • Backup storage: An abstract location where snapshots and chunks are saved (e.g., a flash drive or cloud storage).
    • Backup run: The actual process of performing a backup.
    • Chunks: Smaller pieces that large files are split into by a chunker.
    • Zip chunks: Combinations of small files used to improve transfer efficiency.
    • Files cache: Local cache used to speed up operations by tracking file metadata.
    • Chunks cache: Local cache used to track available chunks in the backup storage.
  11. How restoring from backup works

    android17

    Restoring allows a user to recover files from a selected backup snapshot.

    The restoration process:

    1. Selection: The user selects a snapshot based on time or name.
    2. Re-assembly: Seedvault iterates through the files in the snapshot, downloading, authenticating, decrypting, and decompressing each chunk to re-assemble the original file.
    3. Security: To prevent chunk-swapping attacks, the chunk ID is included in the associated data of the authenticated encryption (AEAD).
    4. File Placement: Files are restored to their original directory and name with attributes like lastModified restored where possible. If a file already exists, Seedvault uses Android's Storage Access Framework to append a (1) suffix or adds one manually.

    Note: Restoring to storage that is already in use is not supported. Directory metadata restoration is not implemented in the first iteration.