# How Are Credentials Encrypted and Stored in Craft Agents?

> Discover how Craft Agents encrypts and stores credentials using AES-256-GCM and PBKDF2-SHA-256. Learn about secure credential management in Craft Agents OSS.

- Repository: [Craft Ai Agents/craft-agents-oss](https://github.com/craft-ai-agents/craft-agents-oss)
- Tags: how-to-guide
- Published: 2026-07-03

---

**Craft Agents stores sensitive credentials in an AES-256-GCM encrypted file at `~/.craft-agent/credentials.enc`, deriving a 256-bit encryption key from a stable machine identifier using PBKDF2-SHA-256 with 100,000 iterations.**

The craft-ai-agents/craft-agents-oss repository implements a secure credential management system that protects API keys, OAuth tokens, and service account credentials from unauthorized access. Instead of storing secrets in plain-text configuration files, Craft Agents encrypts all sensitive data using authenticated encryption standards and manages access through a high-level TypeScript API.

## Encryption Architecture and Key Derivation

### AES-256-GCM Implementation

The encryption layer uses **AES-256-GCM** (Galois/Counter Mode) to provide both confidentiality and authentication. The encrypted file follows a specific binary format implemented in [`packages/shared/src/credentials/backends/secure-storage.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/credentials/backends/secure-storage.ts):

- **Header**: A fixed 16-byte header containing the random salt and metadata
- **Payload**: A concatenated structure of `IV (12 bytes) | Authentication Tag (16 bytes) | Ciphertext`

The credential file resides in the user’s home directory at `~/.craft-agent/credentials.enc`. For workspace-specific credentials, the system stores files in sub-directories under this path.

### Key Derivation with PBKDF2

The system derives the encryption key using **PBKDF2-SHA-256** with the following parameters:

- **Base secret**: A stable machine identifier retrieved from `node-machine-id`
- **Salt**: Random salt stored in the file header
- **Iterations**: 100,000 (`PBKDF2_ITERATIONS`)
- **Key length**: 32 bytes (`KEY_SIZE`)

This derivation ensures that the encryption key is cryptographically separate from the raw machine identifier and resistant to brute-force attacks.

### Decryption Flow and Legacy Support

When opening the store, the system reads the header to extract the salt, re-derives the key, and slices the IV, authentication tag, and ciphertext for the AES-GCM cipher. If decryption fails—typically after a hardware migration—the implementation falls back to a **legacy key derivation path** that used the raw machine ID without PBKDF2. This backward compatibility handles upgrades from older versions while surfacing a `decryption_failed` health issue to prompt credential re-entry.

## Credential Data Model and Scoping

### StoredCredential Interface

Credentials are represented by the generic `StoredCredential` interface defined in [`packages/shared/src/credentials/types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/credentials/types.ts). This flexible structure accommodates:

- OAuth refresh tokens and bearer tokens
- API keys (Anthropic, OpenAI, etc.)
- AWS IAM credentials
- GCP service-account JSON

The type system classifies credentials by **`CredentialType`**, supporting scopes including **global**, **llm-connection**, **workspace**, **source**, and **messaging**.

### Key Naming Convention

Each credential entry is stored under a unique key built from its type and scope using the delimiter `::`. The conversion functions `credentialIdToAccount` and `accountToCredentialId` in [`packages/shared/src/credentials/types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/credentials/types.ts) handle the mapping between `CredentialId` objects and string keys.

Common key patterns include:

- `anthropic_api_key::global` – A global API key accessible across all workspaces
- `source_oauth::workspace123::source456` – An OAuth token scoped to a specific source within a workspace
- `messaging_bearer::workspace123::telegram` – A Telegram bot token scoped to a workspace

## The CredentialManager API

The high-level **`CredentialManager`** class in [`packages/shared/src/credentials/manager.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/credentials/manager.ts) abstracts all interactions with the encrypted store. The manager automatically lazy-loads the encryption key on first use and caches it in memory for the process lifetime.

**Retrieving credentials**:

```typescript
import { CredentialManager } from '@craft-agents/shared/credentials';
import { CredentialId } from '@craft-agents/shared/credentials/types';

const manager = new CredentialManager();

const sourceId: CredentialId = {
  type: 'source_oauth',
  workspaceId: 'ws-001',
  sourceId: 'source-xyz',
};

const cred = await manager.get(sourceId);
if (cred) {
  console.log('OAuth access token:', cred.value);
}

```

**Storing credentials**:

```typescript
const anthKeyId: CredentialId = { type: 'anthropic_api_key' };
await manager.set(anthKeyId, { value: 'sk-abc123' });

```

**Health monitoring**:

```typescript
const health = await manager.checkHealth();
if (!health.healthy) {
  health.issues.forEach(i => console.warn(i.message));
}

```

## Storage Lifecycle and Migration

### Initialization and Updates

The credential store follows a strict lifecycle:

1. **First run**: If `credentials.enc` does not exist, the manager creates an empty JSON object, encrypts it with a freshly derived key, and writes the file
2. **Subsequent runs**: The file is read, decrypted, and parsed into a plain-object map of credential entries
3. **Updates**: Any call to `set()` re-encrypts the entire map and performs an **atomic file overwrite** to prevent corruption during writes

### Machine Migration Handling

When the machine ID changes (e.g., after a hardware swap), decryption of the old file fails. The manager attempts the legacy key derivation, detects the mismatch, and surfaces a `decryption_failed` health issue. This signals the user to re-enter credentials rather than failing silently with a decryption error.

## Working with Credentials in Code

The following example demonstrates the complete workflow for initializing the manager, storing a global API key, and retrieving a workspace-scoped token:

```typescript
import { CredentialManager } from '@craft-agents/shared/credentials';
import { CredentialId } from '@craft-agents/shared/credentials/types';

// Initialize the manager (automatically loads encrypted file)
const manager = new CredentialManager();

// Store a global Anthropic API key
await manager.set(
  { type: 'anthropic_api_key' },
  { value: 'sk-ant-api03-...' }
);

// Retrieve a source-scoped OAuth token
const sourceCred: CredentialId = {
  type: 'source_oauth',
  workspaceId: 'workspace-123',
  sourceId: 'github-source-1'
};

const token = await manager.get(sourceCred);
console.log('Retrieved credential:', token?.value);

```

## Summary

- **Encryption**: Credentials are protected using AES-256-GCM authenticated encryption in [`packages/shared/src/credentials/backends/secure-storage.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/credentials/backends/secure-storage.ts)
- **Key derivation**: A 256-bit key is derived via PBKDF2-SHA-256 (100,000 iterations) from a stable machine identifier and random salt
- **Storage location**: Encrypted files live at `~/.craft-agent/credentials.enc` with support for workspace-specific sub-directories
- **Data model**: The `StoredCredential` interface supports multiple credential types scoped via `::` delimited keys defined in [`packages/shared/src/credentials/types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/credentials/types.ts)
- **API**: The `CredentialManager` class in [`packages/shared/src/credentials/manager.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/credentials/manager.ts) provides `get()`, `set()`, and `checkHealth()` methods with automatic key caching
- **Atomicity**: All writes re-encrypt the entire credential map and atomically overwrite the file to prevent data corruption

## Frequently Asked Questions

### What encryption algorithm does Craft Agents use for credential storage?

Craft Agents uses **AES-256-GCM** (Galois/Counter Mode) authenticated encryption. The implementation in [`packages/shared/src/credentials/backends/secure-storage.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/credentials/backends/secure-storage.ts) combines a 256-bit key derived via PBKDF2-SHA-256 with a random 12-byte IV and 16-byte authentication tag to ensure both confidentiality and integrity of stored credentials.

### What happens to my credentials if I migrate Craft Agents to a new machine?

When you move to a new machine, the stable machine identifier changes, causing decryption to fail. The system attempts a legacy key derivation for backward compatibility, but if that fails, the `CredentialManager` surfaces a `decryption_failed` health issue. You must re-enter your credentials, as the encrypted file is bound to the original hardware identifier.

### How does Craft Agents prevent credential corruption during writes?

The `CredentialManager` ensures atomic updates by re-encrypting the entire credential map into memory and then performing an atomic file overwrite. This prevents partial writes or corruption if the process terminates during the save operation, ensuring the `credentials.enc` file always contains a valid, complete encrypted state.

### What types of credentials can be stored in the encrypted file?

The `StoredCredential` interface supports various credential types including OAuth refresh tokens, bearer tokens, API keys (such as Anthropic or OpenAI), AWS IAM credentials, and GCP service-account JSON. Credentials are classified by `CredentialType` into scopes such as global, workspace-specific, source-specific, or messaging channels.