# How OpenHuman Implements Local-First Encryption to Ensure User Data Privacy

> OpenHuman uses a two-layer local-first encryption approach with ChaCha20-Poly1305 and AES-256-GCM. Discover how this ensures your AI memory and secrets remain private, never touching the filesystem.

- Repository: [Tiny Humans/openhuman](https://github.com/tinyhumansai/openhuman)
- Tags: deep-dive
- Published: 2026-05-12

---

**OpenHuman protects sensitive user data through a two-layer local-first encryption strategy that uses ChaCha20-Poly1305 for secrets and AES-256-GCM for AI memory, ensuring plaintext never touches the filesystem.**

OpenHuman is an open-source AI framework designed to keep user data private by processing and storing information locally before any remote synchronization. According to the `tinyhumansai/openhuman` source code, the system achieves this through a robust local-first encryption architecture that protects credentials and AI memory at rest. By combining modern authenticated encryption standards with strict key management, the system guarantees that user privacy is maintained even if the underlying storage is compromised.

## Layer 1: Secret Store for Configuration and API Tokens

The Secret Store, implemented in [`src/openhuman/security/secrets.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/security/secrets.rs), handles encryption for API keys, tokens, and configuration values using a transparent encryption layer that operates automatically during read and write operations.

### ChaCha20-Poly1305 AEAD Protocol

Each secret is encrypted using the **ChaCha20-Poly1305** authenticated encryption algorithm. When `SecretStore::encrypt` is called, the system generates a fresh **12-byte nonce** for every encryption operation to prevent replay attacks and ensure semantic security. The resulting ciphertext is prefixed with the marker `enc2:` and hex-encoded for safe storage in JSON configuration files.

### Key Management and File Permissions

The encryption key is a random **256-bit value** generated by a cryptographically secure pseudo-random number generator (CSPRNG). This key is stored in `{data_dir}/.secret_key` with strict Unix permissions **`0600`** (read/write owner only). To minimize I/O overhead and survive transient Windows sharing violations, the key file contents are cached process-wide after the initial read, preventing repeated filesystem access during intensive operations.

### Automatic Legacy Migration

The system maintains backward compatibility while upgrading security postures. The `SecretStore::decrypt_and_migrate` method automatically detects legacy `enc:` prefixed values (which used insecure XOR encryption) and transparently re-encrypts them using the modern `enc2:` ChaCha20-Poly1305 format. This ensures older configurations are gradually upgraded without user intervention or risk of accidental exposure.

```rust
// Encrypt a secret (e.g., API token)
use openhuman::security::SecretStore;
use std::path::Path;

let store = SecretStore::new(Path::new("/home/alice/.openhuman"), true);
let encrypted = store.encrypt("my-super-secret-token")?; // → "enc2:ab12…"
println!("Stored value: {}", encrypted);

// Decrypt (and migrate) a secret
let (plain, maybe_new) = store.decrypt_and_migrate(&encrypted)?;
println!("Plaintext: {}", plain);
if let Some(new_val) = maybe_new {
    // Persist the upgraded `enc2:` value back to config
    // write_config("apiToken", &new_val);
}

```

## Layer 2: User Data Encryption for AI Memory

For protecting AI memory and arbitrary user content, OpenHuman uses a password-based encryption system located in [`src/openhuman/encryption/core.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/encryption/core.rs) with a public API exposed through [`src/openhuman/encryption/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/encryption/ops.rs) and JSON-RPC schema registration in [`src/openhuman/encryption/schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/encryption/schemas.rs).

### Argon2id Key Derivation

When a user sets a password, the system derives a **256-bit encryption key** using the **Argon2id** algorithm. Rather than storing the key directly, OpenHuman persists only a random salt in `{data_dir}/encryption.key`. The actual encryption key is deterministically derived from the user's password and this salt at runtime, ensuring the key material never exists on disk in plaintext, even when the application is not running.

### AES-256-GCM Implementation

User data encryption uses **AES-256-GCM** via the `EncryptionKey::encrypt` method. Each encryption operation generates a unique **12-byte nonce** to guarantee that identical plaintexts produce distinct ciphertexts. The system returns an `EncryptedPayload` containing the ciphertext, nonce, and authentication tag, while the salt remains managed separately in the key file to facilitate key regeneration on demand.

### RPC Interface and Operations

The high-level JSON-RPC methods **`ai_encrypt`** and **`ai_decrypt`** defined in [`src/openhuman/encryption/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/encryption/ops.rs) provide the primary interface for the application. These helpers handle loading the salt from `encryption.key`, deriving the Argon2id key, and executing the cryptographic operations via `encrypt_string` and `decrypt_string` functions respectively.

```rust
// Initialize user-level encryption
use openhuman::encryption::core::{ai_init_encryption, ai_encrypt, ai_decrypt};

// First run – creates a random salt and writes `encryption.key`
ai_init_encryption("my-strong-password".to_string()).await?;

// Encrypt arbitrary user data
let cipher = ai_encrypt("my-strong-password".to_string(),
                        "some private note".to_string()).await?;
println!("Encrypted note: {}", cipher);

// Decrypt later
let plain = ai_decrypt("my-strong-password".to_string(), cipher).await?;
assert_eq!(plain, "some private note");

```

## Security Guarantees

This dual-layer local-first approach provides several critical security properties:

- **Data never leaves the device unencrypted**: The core side-car stores only ciphertext, ensuring plaintext exists only in memory during active operations.
- **Strong cryptographic primitives**: ChaCha20-Poly1305 and AES-256-GCM provide authenticated encryption resistant to tampering and unauthorized access.
- **Key protection**: Secret store keys use filesystem permissions `0600` and process caching; user data keys rely on Argon2id derivation without persistent key storage.
- **Graceful migration**: Legacy XOR-encrypted values are automatically upgraded to prevent accidental exposure of outdated secrets.

## Summary

- OpenHuman uses **ChaCha20-Poly1305** in [`src/openhuman/security/secrets.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/security/secrets.rs) to encrypt API keys and configuration values with per-message nonces, hex-encoded `enc2:` prefixes, and automatic legacy migration from insecure XOR formats.
- **AES-256-GCM** protects AI memory and user data in [`src/openhuman/encryption/core.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/encryption/core.rs), using keys derived via **Argon2id** from user passwords and random salts stored separately in `encryption.key`.
- Encryption keys are never stored in plaintext; the secret store uses permission-restricted `0600` files with process-wide caching, while user data relies entirely on password derivation.
- The RPC interface in [`src/openhuman/encryption/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/encryption/ops.rs) exposes `ai_encrypt` and `ai_decrypt` for seamless application-level integration, with schema definitions in [`src/openhuman/encryption/schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/encryption/schemas.rs).
- All ciphertext is prefixed with version markers (`enc2:`) to distinguish modern AEAD encryption from legacy formats, enabling transparent security upgrades.

## Frequently Asked Questions

### What encryption algorithms does OpenHuman use for local data?

OpenHuman uses **ChaCha20-Poly1305** for configuration secrets and API tokens in the Secret Store, and **AES-256-GCM** for AI memory and user data. Both are authenticated encryption with associated data (AEAD) algorithms that provide confidentiality and integrity protection against tampering.

### How are encryption keys stored without compromising security?

For the Secret Store, a random 256-bit key is stored in `.secret_key` with `0600` filesystem permissions and cached process-wide to prevent leakage. For user data, only a random salt is stored in `encryption.key`; the actual encryption key is derived at runtime using **Argon2id** from the user's password and the stored salt, ensuring the key never persists on disk.

### Can OpenHuman decrypt data from older versions?

Yes. The `SecretStore::decrypt_and_migrate` function in [`src/openhuman/security/secrets.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/security/secrets.rs) automatically detects legacy `enc:` prefixed values (using XOR encryption) and re-encrypts them using the modern `enc2:` ChaCha20-Poly1305 format during the decryption process, preventing accidental use of weak cryptography.

### Is the encryption truly local-first?

Yes. According to the `tinyhumansai/openhuman` source code, all encryption occurs on the local device before any data reaches the filesystem. The architecture ensures that user data remains private and never exists in plaintext outside the secure encryption boundaries of the local application, with ciphertext being the only form stored persistently.