How the TeslaMate Vault Module Encrypts and Stores Sensitive API Tokens
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, 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:
- Application configuration – Checks
:teslamate, TeslaMate.Vaultin the runtime config - Temporary file – Reads from
$TMPDIR/tm_encryption.keyif it exists - Import directory – Looks in the path defined by the
IMPORT_DIRenvironment variable (defaulting toimport)
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:
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 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:
# 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:
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.exwraps Cloak to provide transparent AES-256-GCM encryption for API tokens. - Key discovery follows a priority chain: application config,
$TMPDIR/tm_encryption.key, thenIMPORT_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.Binarytype fields to automatically encrypt on write and decrypt on read. - Persistence is handled by the migration at
priv/migrations/20220123131732_encrypt_api_tokens.exsand theget_encryption_key_from/1helper.
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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →