# What Encryption Method Is Used for Credential Storage in Craft Agents? AES-256-GCM Explained

> Learn how Craft Agents secure your credentials. Discover AES-256-GCM encryption, PBKDF2 key derivation, and authenticated encryption tags for robust credential storage.

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

---

**Craft Agents encrypts all user credentials using AES-256-GCM (Advanced Encryption Standard with 256-bit keys in Galois/Counter Mode), storing them in a machine-bound file at `~/.craft-agent/credentials.enc` with PBKDF2 key derivation and authenticated encryption tags.**

The `craft-ai-agents/craft-agents-oss` repository implements a secure storage backend that ensures OAuth tokens, API keys, and bearer tokens never persist in plaintext. According to the source code 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), the system combines hardware-specific identifiers with strong key derivation to protect credentials at rest.

## AES-256-GCM Encryption Architecture

Craft Agents employs **AES-256-GCM** to provide both confidentiality and integrity verification. This authenticated encryption mode generates a 16-byte authentication tag during encryption that is verified during decryption, preventing tampering and ensuring data authenticity.

### Encrypted File Format and Header Structure

The credential store file uses a structured binary format consisting of a 64-byte header followed by the encrypted payload. As documented in the [source header](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/credentials/backends/secure-storage.ts#L15-L24), the layout includes:

- A magic string (`"CRAFT01\0"`) identifying the file format
- Reserved flags for future extensions  
- A 32-byte PBKDF2 salt stored in plaintext (bytes 8-39)
- Padding to align the header to 64 bytes

Following the header, the encrypted payload contains a random 12-byte initialization vector (IV), the AES-256-GCM ciphertext of the JSON-encoded credential store, and the 16-byte Galois Message Authentication Code (GMAC) tag.

### Key Derivation via PBKDF2

The **256-bit encryption key** is never stored on disk; instead, it is deterministically derived for each host machine. In `SecureStorageBackend.getEncryptionKey()` ([lines 22-28](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/credentials/backends/secure-storage.ts#L22-L28)), the implementation:

1. Collects a stable machine identifier (hardware UUID on macOS, `MachineGuid` on Windows, or `/var/lib/dbus/machine-id` on Linux)
2. Hashes this identifier with a version string
3. Runs **PBKDF2 with 100,000 iterations**, SHA-256, and the 32-byte salt from the file header to produce the AES key

This machine-binding prevents credential files from being decrypted on unauthorized hardware, mitigating copy-paste attacks.

## Implementation in SecureStorageBackend

The `SecureStorageBackend` class 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) handles all cryptographic operations using Node.js's native `crypto` module.

### Encryption and Decryption Flow

For **encryption**, the system generates a cryptographically random 12-byte IV and initializes a cipher via `createCipheriv('aes-256-gcm', key, iv)`. After encrypting the JSON credential payload, it retrieves the authentication tag using `cipher.getAuthTag()` and appends both the ciphertext and tag to the file.

For **decryption**, `SecureStorageBackend.tryDecrypt()` ([lines 64-71](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/credentials/backends/secure-storage.ts#L64-L71)) creates a decipher instance via `createDecipheriv('aes-256-gcm', key, iv)`, supplies the stored authentication tag via `decipher.setAuthTag()`, and recovers the plaintext. If the authentication tag verification fails—indicating tampering or key mismatch—the decryption throws an error before any data is returned.

### Migration and Fallback Handling

When loading credentials, `loadStoreSync()` ([lines 41-55](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/credentials/backends/secure-storage.ts#L41-L55)) implements a secure migration path:

1. Attempts decryption using the current machine-specific key derivation
2. Falls back to a legacy hostname-based key (v1) if the first attempt fails, supporting seamless upgrades
3. If both attempts fail, treats the file as corrupted and safely deletes it to prevent stale credential exposure

## Practical Usage Example

The following TypeScript example demonstrates storing a Slack OAuth token using the secure storage backend:

```typescript
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);
}

```

When `backend.set()` executes, it automatically serializes the credential store to JSON, encrypts the payload using AES-256-GCM with a fresh IV, and atomically writes the result to `~/.craft-agent/credentials.enc`.

## Summary

- **AES-256-GCM** provides authenticated encryption for all credentials, ensuring both confidentiality and integrity via a 16-byte authentication tag.
- **PBKDF2 key derivation** uses 100,000 iterations and machine-specific hardware IDs to generate encryption keys that cannot be replicated on other devices.
- **Structured file format** includes a 64-byte header with magic bytes, flags, and a 32-byte salt, followed by the encrypted JSON payload.
- **SecureStorageBackend** handles encryption/decryption 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), with fallback logic for legacy key migration and corruption detection.

## Frequently Asked Questions

### Why does Craft Agents use AES-256-GCM instead of other encryption modes?

AES-256-GCM provides **authenticated encryption with associated data (AEAD)**, which simultaneously guarantees data confidentiality and detects any unauthorized modifications. Unlike CBC or ECB modes, GCM generates an authentication tag during encryption that is verified during decryption, preventing padding oracle attacks and ensuring credential integrity.

### How is the encryption key protected if it is not stored on disk?

The encryption key is **deterministically derived** from stable hardware identifiers unique to each machine (such as the hardware UUID on macOS or `MachineGuid` on Windows). Using PBKDF2 with 100,000 iterations and a random salt, the key is recomputed on-demand when the agent starts. This means an attacker cannot decrypt the credential file by copying it to another machine, as the derived key will differ.

### What happens if the `credentials.enc` file becomes corrupted?

According to the implementation in `loadStoreSync()`, the system first attempts decryption with the current machine key, then falls back to a legacy hostname-based key for migration purposes. If both attempts fail, the backend **safely deletes the corrupted file** and starts with an empty credential store, preventing the application from crashing or exposing stale data.

### Can I transfer my encrypted credentials to another machine?

No, the encryption is **intentionally machine-bound**. Because the AES-256-GCM key incorporates hardware-specific identifiers, decrypting the `credentials.enc` file on a different device will fail authentication. To migrate credentials, you must re-authenticate or export/import the credentials through the application's explicit migration tools rather than copying the raw encrypted file.