What Encryption Is Used for Credential Storage in Craft Agents?

Craft Agents uses AES-256-GCM (Advanced Encryption Standard with Galois/Counter Mode) to encrypt all credentials at rest, combining hardware-bound key derivation and authenticated encryption to ensure both confidentiality and integrity.

Craft Agents, the open-source framework maintained in the craft-ai-agents/craft-agents-oss repository, protects sensitive authentication data using industry-standard cryptographic primitives. All user credentials—including OAuth tokens, API keys, and bearer tokens—are stored in a dedicated encrypted file at ~/.craft-agent/credentials.enc. The implementation relies on AES-256-GCM encryption to prevent unauthorized access while detecting tampering through cryptographic authentication tags.

AES-256-GCM Encryption Architecture

The credential storage system implements a layered security model that binds encryption keys to the specific host machine while using authenticated encryption to protect data integrity.

Encrypted File Layout

The credential file follows a structured binary format defined in packages/shared/src/credentials/backends/secure-storage.ts. The file begins with a 64-byte header containing a magic string ("CRAFT01\0"), reserved flags, and a 32-byte PBKDF2 salt. Following the header, the encrypted payload stores the JSON-encoded credential data using a random 12-byte initialization vector (IV) and a 16-byte authentication tag appended to the ciphertext. This format ensures that decryption can only succeed with both the correct key and the original authentication parameters. See the header documentation at lines 15-24 for the complete specification.

Machine-Specific Key Derivation

Rather than storing keys in plaintext, Craft Agents derives the 256-bit AES key from stable machine identifiers using PBKDF2 (Password-Based Key Derivation Function 2). The derivation process uses:

  • 100,000 iterations of PBKDF2 with SHA-256
  • A 32-byte salt stored in the file header
  • A hardware-bound identifier: hardware UUID on macOS, MachineGuid on Windows, or /var/lib/dbus/machine-id on Linux

The key generation logic resides in SecureStorageBackend.getEncryptionKey() at lines 22-28. This machine-binding prevents simple copy-paste attacks, as moving the encrypted file to another device renders the data inaccessible.

Authenticated Encryption Implementation

The encryption layer uses Node.js crypto primitives to implement AES-256-GCM. During encryption, createCipheriv('aes-256-gcm', key, iv) generates a cipher stream, and cipher.getAuthTag() produces the 16-byte authentication tag. For decryption, SecureStorageBackend.tryDecrypt() (lines 64-71) uses createDecipheriv('aes-256-gcm', key, iv) and verifies the tag via decipher.setAuthTag() before recovering plaintext. If the authentication tag fails verification, the decryption aborts, indicating potential tampering.

Credential Storage Structure

The system supports multiple credential types—including global API keys, workspace-scoped OAuth tokens, and source-specific bearer tokens—all defined in packages/shared/src/credentials/types.ts (lines 1-5). These values are serialized to JSON and encrypted as a single payload, ensuring that all sensitive data remains protected under the AES-256-GCM envelope regardless of credential type.

Migration and Corruption Handling

The loadStoreSync() function (lines 41-55) implements resilience logic for legacy migrations. On initialization, the backend attempts decryption with the current machine-bound key derivation; if that fails, it falls back to a legacy hostname-based key (v1) to support upgrades from earlier versions. If both attempts fail, the system treats the file as corrupted and safely deletes it, preventing crash loops from invalid data.

Secure Storage Implementation Example

The following example demonstrates how the SecureStorageBackend class encrypts credentials transparently:

import { SecureStorageBackend } from './packages/shared/src/credentials/backends/secure-storage';
import { CredentialId, StoredCredential } from './packages/shared/src/credentials/types';

async function saveExample() {
  const backend = new SecureStorageBackend();
  const id: CredentialId = { type: 'source_oauth', workspaceId: 'ws-123', sourceId: 'slack' };
  const cred: StoredCredential = {
    value: 'xoxb-1234567890-abcdef',    // Slack bot token
    clientId: 'my-client-id',
    clientSecret: 'my-client-secret',
    source: 'native',
  };
  await backend.set(id, cred);
}

The backend.set() call automatically re-encrypts the entire credential store with AES-256-GCM and persists it to ~/.craft-agent/credentials.enc, handling IV generation, authentication tagging, and key derivation internally.

Summary

  • AES-256-GCM provides authenticated encryption with 256-bit keys and tamper detection via 16-byte authentication tags.
  • PBKDF2 key derivation uses 100,000 iterations and machine-specific identifiers (hardware UUID, MachineGuid, or dbus machine-id) to bind credentials to the host.
  • File format includes a 64-byte header with salt storage and a payload containing IV, ciphertext, and authentication tag.
  • Resilience features include legacy key fallback for migrations and safe deletion of corrupted stores.
  • Source locations: Core logic in packages/shared/src/credentials/backends/secure-storage.ts with type definitions in packages/shared/src/credentials/types.ts.

Frequently Asked Questions

What encryption algorithm does Craft Agents use for credential storage?

Craft Agents uses AES-256-GCM (Advanced Encryption Standard with Galois/Counter Mode). This algorithm provides both confidentiality through 256-bit encryption and integrity protection through built-in message authentication, preventing both unauthorized reading and undetected modification of stored credentials.

How does Craft Agents derive the encryption key without storing it?

The system derives keys dynamically using PBKDF2 with 100,000 iterations of SHA-256. The input combines a version string with a stable machine identifier—hardware UUID on macOS, MachineGuid on Windows, or /var/lib/dbus/machine-id on Linux. This approach ensures the encryption key never exists in persistent storage and cannot be recreated on a different machine.

What happens if the credentials file is moved to another computer or becomes corrupted?

If the file is moved to another machine, decryption fails because the hardware-derived key differs. The loadStoreSync() function attempts a fallback to legacy hostname-based keys for migration purposes, but if both attempts fail, the system treats the file as corrupted and safely deletes it. This prevents crash loops while maintaining security.

Where is the encryption implementation located in the source code?

The encryption logic resides in packages/shared/src/credentials/backends/secure-storage.ts, specifically within the SecureStorageBackend class. Key methods include getEncryptionKey() (lines 22-28) for key derivation, tryDecrypt() (lines 64-71) for authenticated decryption, and loadStoreSync() (lines 41-55) for file handling. Credential type definitions are in packages/shared/src/credentials/types.ts (lines 1-5).

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →