Skip to content
Back to skills

Encryption Aes Gcm

ASecurity

AES-256-GCM encryption for sensitive credentials (BYOD/BYOK). Node crypto, IV + authTag storage, multi-field colon-separated IVs. Triggers on "encryption", "decrypt", "AES-256-GCM", "BYOD", "BYOK", "credentials".

  • 666 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added February 8, 2026
developmenttypescriptgonodeapidatabasesecurity

Works with

  • cli
  • api

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill encryption-aes-gcm --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Encryption Aes Gcm?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Encryption Aes Gcm
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-encryption-aes-gcm/badge)](https://www.skillsdirectory.com/skills/majiayu000-encryption-aes-gcm)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: encryption-aes-gcm
description: AES-256-GCM encryption for sensitive credentials (BYOD/BYOK). Node crypto, IV + authTag storage, multi-field colon-separated IVs. Triggers on "encryption", "decrypt", "AES-256-GCM", "BYOD", "BYOK", "credentials".
---

# AES-256-GCM Encryption

Project uses AES-256-GCM for encrypting user credentials (BYOD Convex keys, BYOK API keys). Node crypto module, requires `"use node"` directive.

## Core Pattern

```typescript
// From convex/lib/encryption.ts
"use node";

import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";

const ALGORITHM = "aes-256-gcm";
```

All encryption functions in files with `"use node"` at top (Convex Node runtime required).

## Environment Key

```typescript
// From convex/lib/encryption.ts
function getEncryptionKey(): Buffer {
  const key = process.env.BYOD_ENCRYPTION_KEY;
  if (!key) {
    throw new Error("BYOD_ENCRYPTION_KEY environment variable not set");
  }
  // 32 bytes (64 hex chars) - use directly
  if (key.length === 64) {
    return Buffer.from(key, "hex");
  }
  // Otherwise hash to 32 bytes
  const crypto = require("node:crypto");
  return crypto.createHash("sha256").update(key).digest();
}
```

**Key requirement**: `BYOD_ENCRYPTION_KEY` in Convex environment. Accepts 64 hex chars or any string (hashed).

## Encryption Function

```typescript
// From convex/lib/encryption.ts
export async function encryptCredential(
  plaintext: string,
): Promise<{ encrypted: string; iv: string; authTag: string }> {
  const key = getEncryptionKey();
  const iv = randomBytes(16);

  const cipher = createCipheriv(ALGORITHM, key, iv);
  let encrypted = cipher.update(plaintext, "utf8", "hex");
  encrypted += cipher.final("hex");

  const authTag = cipher.getAuthTag();

  return {
    encrypted,
    iv: iv.toString("hex"),
    authTag: authTag.toString("hex"),
  };
}
```

**Returns three parts**: encrypted data, IV (16 bytes), authTag (for GCM authentication).

**Always generate new IV** per encryption (never reuse).

## Decryption Function

```typescript
// From convex/lib/encryption.ts
export async function decryptCredential(
  encrypted: string,
  iv: string,
  authTag: string,
): Promise<string> {
  const key = getEncryptionKey();

  const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(iv, "hex"));
  decipher.setAuthTag(Buffer.from(authTag, "hex"));

  let decrypted = decipher.update(encrypted, "hex", "utf8");
  decrypted += decipher.final("utf8");

  return decrypted;
}
```

**Must set authTag before decrypting** - validates data integrity. Throws if tampered.

## Multi-Field Storage (BYOK Pattern)

Store multiple encrypted keys with shared IV/authTag arrays:

```typescript
// From convex/byok/saveCredentials.ts
const FIELD_MAP = {
  vercelGateway: "encryptedVercelKey",
  openRouter: "encryptedOpenRouterKey",
  groq: "encryptedGroqKey",
  deepgram: "encryptedDeepgramKey",
};

const KEY_INDEX = {
  vercelGateway: 0,
  openRouter: 1,
  groq: 2,
  deepgram: 3,
};

// Parse colon-separated string: "iv1:iv2:iv3:iv4"
function parseParts(str: string | undefined): string[] {
  if (!str) return ["", "", "", ""];
  const parts = str.split(":");
  while (parts.length < 4) parts.push("");
  return parts;
}
```

**Encryption storage**:

```typescript
// From convex/byok/saveCredentials.ts (saveApiKey action)
const encrypted = await encryptCredential(args.apiKey);

// Get existing IVs/authTags
const existing = await ctx.runQuery(internal.byok.credentials.getConfigInternal, {
  userId: user._id,
});

const ivParts = parseParts(existing?.encryptionIVs);
const authTagParts = parseParts(existing?.authTags);

// Update specific index
const idx = KEY_INDEX[args.keyType];
ivParts[idx] = encrypted.iv;
authTagParts[idx] = encrypted.authTag;

// Store as colon-separated
await ctx.runMutation(internal.byok.credentials.upsertConfig, {
  userId: user._id,
  [FIELD_MAP[args.keyType]]: encrypted.encrypted,
  encryptionIVs: ivParts.join(":"), // "v_iv:or_iv:groq_iv:dg_iv"
  authTags: authTagParts.join(":"),
});
```

**Decryption retrieval**:

```typescript
// From convex/byok/saveCredentials.ts (revalidateKey action)
const existing = await ctx.runQuery(internal.byok.credentials.getConfigInternal, {
  userId: user._id,
});

const encryptedKey = existing[FIELD_MAP[args.keyType]];
const ivParts = parseParts(existing.encryptionIVs);
const authTagParts = parseParts(existing.authTags);

const idx = KEY_INDEX[args.keyType];
const iv = ivParts[idx];
const authTag = authTagParts[idx];

if (!iv || !authTag) {
  throw new ConvexError("Unable to decrypt key. Please re-add it.");
}

const apiKey = await decryptCredential(encryptedKey, iv, authTag);
```

## Database Schema

```typescript
// userApiKeys table (BYOK)
{
  userId: v.id("users"),
  encryptedVercelKey: v.optional(v.string()),
  encryptedOpenRouterKey: v.optional(v.string()),
  encryptedGroqKey: v.optional(v.string()),
  encryptedDeepgramKey: v.optional(v.string()),
  encryptionIVs: v.string(),  // "iv1:iv2:iv3:iv4"
  authTags: v.string(),        // "tag1:tag2:tag3:tag4"
  lastValidated: v.optional(v.object({...})),
}

// userDatabaseConfig table (BYOD)
{
  userId: v.id("users"),
  encryptedDeploymentUrl: v.string(),
  encryptedDeployKey: v.string(),
  encryptionIV: v.string(),    // Single IV: "urlIV:keyIV"
  authTags: v.string(),         // Single authTags: "urlTag:keyTag"
}
```

**BYOK**: 4 keys → 4 indexes → colon-separated IVs/authTags
**BYOD**: 2 fields → colon-separated `urlIV:keyIV` and `urlTag:keyTag`

## Security Rules

1. **Never log plaintext** - only log key types, not values:
   ```typescript
   logger.warn("Failed to validate API key", {
     tag: "BYOK",
     keyType,  // ✅ Log type
     error: String(error),
   });
   // ❌ DON'T: apiKey: apiKey
   ```

2. **Never return encrypted credentials to client**:
   ```typescript
   // From convex/byod/credentials.ts (getConfig query)
   // Never return encrypted credentials to client
   return {
     _id: config._id,
     connectionStatus: config.connectionStatus,
     lastConnectionTest: config.lastConnectionTest,
     // ❌ DON'T include: encryptedDeploymentUrl, encryptedDeployKey
   };
   ```

3. **Use internal queries for encrypted data**:
   ```typescript
   // From convex/byod/credentials.ts
   export const getConfigInternal = internalQuery({
     args: { userId: v.id("users") },
     handler: async (ctx, args): Promise<Doc<"userDatabaseConfig"> | null> => {
       return await ctx.db
         .query("userDatabaseConfig")
         .withIndex("by_user", (q) => q.eq("userId", args.userId))
         .first();
     },
   });
   ```

4. **Always verify authTag** - `decipher.final()` throws if tampered.

5. **Fail secure on validation errors**:
   ```typescript
   // From convex/byok/saveCredentials.ts
   try {
     encrypted = await encryptCredential(args.apiKey);
   } catch (error) {
     logger.error("BYOK encryption failed", { tag: "BYOK", error: String(error) });
     throw new ConvexError(
       "BYOK is not available right now. Please contact support.",
     );
   }
   ```

## Use Cases

**BYOK (Bring Your Own Key)**: User-provided API keys for Vercel Gateway, OpenRouter, Groq, Deepgram
- File: `convex/byok/saveCredentials.ts`
- Actions: `saveApiKey`, `removeApiKey`, `revalidateKey`
- Table: `userApiKeys`

**BYOD (Bring Your Own Database)**: User Convex deployment credentials
- File: `convex/byod/credentials.ts`
- Table: `userDatabaseConfig`
- Fields: `encryptedDeploymentUrl`, `encryptedDeployKey`

## Error Handling

```typescript
// Encryption failure (missing env key)
throw new ConvexError("BYOK is not available right now. Please contact support.");

// Decryption failure (missing IV/authTag)
throw new ConvexError("Unable to decrypt key. Please re-add it.");

// Validation failure (invalid API key)
throw new ConvexError("Invalid API key. Please check the key is correct.");
```

## Key Files

- `convex/lib/encryption.ts` - Core encrypt/decrypt functions
- `convex/byok/saveCredentials.ts` - Multi-key BYOK pattern
- `convex/byok/credentials.ts` - BYOK queries/mutations
- `convex/byod/credentials.ts` - BYOD config management
- `convex/byok/constants.ts` - Field mappings, key indexes

## Avoid

- ❌ Reusing IVs (always generate new with `randomBytes(16)`)
- ❌ Storing plaintext anywhere (logs, DB, client responses)
- ❌ Using algorithms other than `aes-256-gcm` (project standard)
- ❌ Forgetting `"use node"` directive (encryption requires Node runtime)
- ❌ Missing authTag validation (set before decrypt, verify on final)
- ❌ Hardcoding encryption keys in code (use env vars)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.