# How the TeslaMate Vault Module Encrypts and Stores Sensitive API Tokens

> Discover how the TeslaMate Vault module encrypts API tokens using AES-256-GCM with Cloak for secure storage and transparent decryption. Learn about this essential security feature.

- Repository: [TeslaMate/teslamate](https://github.com/teslamate-org/teslamate)
- Tags: internals
- Published: 2026-06-23

---

**The TeslaMate Vault module automatically encrypts sensitive API tokens using AES-256-GCM via the Cloak library before database persistence, decrypting them transparently on read operations.**

The **TeslaMate Vault** provides cryptographic protection for confidential values such as Tesla API tokens, ensuring they never exist in plaintext within the database. This Elixir module wraps the Cloak encryption library to provide transparent encryption at the Ecto schema level, handling key management, cipher configuration, and runtime decryption without requiring manual cryptographic operations in application code.

## Architecture and Key Management Strategy

The Vault operates as a supervised GenServer that initializes a cipher configuration during startup. Located in [`lib/teslamate/vault.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vault.ex), the module implements a three-stage key discovery process followed by AES-256-GCM cipher setup.

When the Vault process starts, the `init/1` function (lines 48-73) searches for encryption keys in the following priority order:

1. **Application configuration** – Checks `:teslamate, TeslaMate.Vault` in the runtime config
2. **Temporary file** – Reads from `$TMPDIR/tm_encryption.key` if it exists
3. **Import directory** – Looks in the path defined by the `IMPORT_DIR` environment variable (defaulting to `import`)

If no key exists in these locations, the system generates a cryptographically secure random key, logs a warning, and uses it for the current runtime session. The helper function `get_encryption_key_from/1` (lines 13-24) handles file reading with appropriate logging, falling back silently to the next source when files are missing.

## AES-256-GCM Cipher Configuration

Once a key is discovered or generated, the Vault configures its encryption cipher through `default_cipher/1` (lines 36-39). This function implements **AES-256-GCM** with the following specifications:

- **Key hashing**: The raw encryption key is hashed using SHA-256
- **IV length**: 12-byte initialization vectors
- **Tag**: "AES.GCM.V1" metadata tag for version tracking
- **Authentication**: GCM mode provides authenticated encryption, ensuring both confidentiality and integrity

The encrypted value format follows the Cloak specification, consisting of a structured header containing the key tag, IV, and authentication tag, followed by the ciphertext itself. This format, detailed in the module docstring (lines 15-31), enables safe decryption even when encrypting the same plaintext multiple times, as each operation generates unique IVs.

## Database Integration with Ecto

Database schemas utilize the Vault through the `TeslaMate.Vault.Encrypted.Binary` Ecto type. Rather than storing tokens as plaintext strings, schemas declare encrypted binary fields:

```elixir
field :access_token, TeslaMate.Vault.Encrypted.Binary
field :refresh_token, TeslaMate.Vault.Encrypted.Binary

```

Cloak intercepts write operations to automatically encrypt values before they reach the database, and decrypts them back to plaintext when reading records. This transparent encryption means the rest of the application logic works with decrypted strings without awareness of the underlying cryptographic storage.

The migration [`priv/migrations/20220123131732_encrypt_api_tokens.exs`](https://github.com/teslamate-org/teslamate/blob/main/priv/migrations/20220123131732_encrypt_api_tokens.exs) handles the transition of existing tokens to encrypted storage and manages key persistence, writing generated keys to the temporary location and import volume to survive Docker container restarts.

## Implementing and Configuring the Vault

To interact with the Vault programmatically or verify configuration, use the following patterns:

```elixir

# Start the Vault (normally supervised by the application)

{:ok, _pid} = TeslaMate.Vault.start_link([])

# Encrypt a sensitive value manually

{:ok, ciphertext} = TeslaMate.Vault.encrypt("tesla-api-token-secret")

# Decrypt a stored value

{:ok, plaintext} = TeslaMate.Vault.decrypt(ciphertext)

```

For production deployments, provide a stable encryption key to prevent data loss on container restart. Set the `ENCRYPTION_KEY` environment variable or configure it in [`config/runtime.exs`](https://github.com/teslamate-org/teslamate/blob/main/config/runtime.exs):

```elixir
config :teslamate, TeslaMate.Vault,
  key: System.get_env("ENCRYPTION_KEY")

```

The public function `encryption_key_provided?/0` allows operators to verify whether a persistent key is configured, useful for diagnostic checks and startup validation.

## Summary

- **The TeslaMate Vault** in [`lib/teslamate/vault.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vault.ex) wraps Cloak to provide transparent AES-256-GCM encryption for API tokens.
- **Key discovery** follows a priority chain: application config, `$TMPDIR/tm_encryption.key`, then `IMPORT_DIR`, with automatic generation as a fallback.
- **Cipher configuration** uses SHA-256 hashed keys with 12-byte IVs and the "AES.GCM.V1" tag format specified in `default_cipher/1`.
- **Ecto integration** uses `TeslaMate.Vault.Encrypted.Binary` type fields to automatically encrypt on write and decrypt on read.
- **Persistence** is handled by the migration at [`priv/migrations/20220123131732_encrypt_api_tokens.exs`](https://github.com/teslamate-org/teslamate/blob/main/priv/migrations/20220123131732_encrypt_api_tokens.exs) and the `get_encryption_key_from/1` helper.

## Frequently Asked Questions

### Where does TeslaMate store the encryption key?

TeslaMate searches for the encryption key in three locations in order: the application configuration (`:teslamate, TeslaMate.Vault`), a temporary file at `$TMPDIR/tm_encryption.key`, and the import directory defined by the `IMPORT_DIR` environment variable. If no key exists, the system generates a random key and logs instructions for persisting it via the `ENCRYPTION_KEY` environment variable.

### What encryption algorithm does the Vault module use?

The Vault module uses **AES-256-GCM** (Galois/Counter Mode) as implemented in the `default_cipher/1` function. The algorithm uses SHA-256 hashed keys, 12-byte initialization vectors, and includes authentication tags to ensure both data confidentiality and integrity. This configuration follows the Cloak library specification with the tag "AES.GCM.V1".

### How do I configure a persistent encryption key for Docker deployments?

Set the `ENCRYPTION_KEY` environment variable before starting the TeslaMate application, or add the configuration to [`config/runtime.exs`](https://github.com/teslamate-org/teslamate/blob/main/config/runtime.exs) with `config :teslamate, TeslaMate.Vault, key: System.get_env("ENCRYPTION_KEY")`. Without a persistent key, randomly generated keys are lost on container restart, rendering encrypted tokens permanently inaccessible.

### Can I manually encrypt or decrypt tokens outside of Ecto operations?

Yes, the Vault exposes `encrypt/1` and `decrypt/1` functions that operate on binary data. Start the Vault process with `TeslaMate.Vault.start_link([])`, then use these functions to transform plaintext secrets into ciphertext or recover original values from encrypted database fields.