EncryptCodecencryptcodec
Blog/Security
SecurityMarch 12, 2026 · 7 min read

API Key Security — Storage, Rotation, and Best Practices

API keys are the most common way services authenticate to each other — and they're also one of the most commonly leaked credentials on the internet. GitHub alone scans for exposed secrets and sends thousands of alerts per day. If you're shipping APIs or consuming third-party services, you need a clear strategy for how you generate, store, and rotate these keys.

Where API Keys Get Leaked (and Why It Keeps Happening)

The most common causes:

  1. Committed to git.env files, hardcoded strings, config files
  2. Exposed in client-side code — bundled into frontend JS, visible in browser devtools
  3. Logged by accident — request headers or bodies written to log files
  4. Shared over Slack/email — then forgotten about
  5. Left in old branches or PRs — even after deletion, git history is permanent

Most of these come down to developer habits. .env files committed to git, keys copy-pasted into Slack, keys bundled into Docker images — none of this requires a sophisticated attacker. Fix the habit, then back it up with the right tooling.

How to Generate API Keys

A good API key is:

  • Long enough — at least 128 bits of entropy (32 hex characters or ~22 base64 characters)
  • Random — from a cryptographically secure random source, not Math.random()
  • Unpredictable — no sequential IDs, timestamps, or user data baked in

A common format: a prefix for identification + a random segment. For example: sk_live_ + 32 random bytes as hex. The prefix lets you grep logs, write regex scanners, and quickly identify what leaked.

const crypto = require('crypto');

function generateApiKey(prefix = 'sk_live') {
const secret = crypto.randomBytes(32).toString('hex');
return `${prefix}_${secret}`;
}

console.log(generateApiKey());
// sk_live_3a9f2c1b8e4d7f0a6b5c2d9e1f4a8b3c7d0e2f5a1b6c9d4e7f0a3b8c1d2e5f

How to Store API Keys Server-Side

Never store raw API keys in your database. If your database is breached, every key is immediately compromised.

The right approach: store a hash of the key, show the raw key to the user exactly once (at creation), and verify future requests by hashing the incoming key and comparing.

This is the same model password storage uses — and for the same reason.

Use SHA-256 for this (not bcrypt — API keys are already high-entropy, so the slow hash isn't needed):

const crypto = require('crypto');

function hashApiKey(rawKey) {
return crypto.createHash('sha256').update(rawKey).digest('hex');
}

// On key creation:
const rawKey = generateApiKey();
const hashedKey = hashApiKey(rawKey);
// Store hashedKey in DB, return rawKey to user once

// On incoming request:
function verifyApiKey(incomingKey, storedHash) {
const hash = hashApiKey(incomingKey);
return crypto.timingSafeEqual(
  Buffer.from(hash),
  Buffer.from(storedHash)
);
}

Note the use of timing-safe comparison in each example. This prevents timing attacks where an attacker could infer a valid key by measuring how long comparisons take.

Key Rotation — When and How

Rotation schedule:

  • Internal service-to-service keys: rotate every 90 days
  • Third-party API keys you consume: rotate every 6–12 months, or per vendor recommendation
  • After any personnel change with access: immediately
  • After any suspected exposure: immediately, no exceptions

How to rotate without downtime:

  1. Generate new key, add it alongside the old one
  2. Update consumers to use the new key
  3. Monitor for any traffic still using the old key
  4. Revoke the old key after a safe window (24–72 hours)

Never do a hard cutover — always allow a transition window.

Environment Variable Management

For most apps, environment variables are the minimum viable approach. A few rules:

  • .env files are for local dev only — never commit them, always add to .gitignore
  • In production, inject secrets via your platform (Railway, Fly.io, Heroku config vars, ECS task definitions, Kubernetes secrets)
  • For anything serious: use a dedicated secrets manager — AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, or Azure Key Vault

The real reason to use a secrets manager isn't the feature list — it's that secrets in env vars get baked into Docker images, printed in CI logs, and inherited by every subprocess on the machine. A secrets manager keeps secrets out of the build artifact entirely.

Scope and Least Privilege

Every API key should do exactly what it needs to do — nothing more:

  • Read-only keys for services that only read data
  • Scoped keys per environment (separate prod and staging keys)
  • Short-lived tokens where the API supports it (OAuth 2.0 client credentials over static keys)
  • IP allowlisting if your infrastructure has stable egress IPs

When a key leaks (not if — when), a scoped key limits the blast radius. A key that can only read your product catalog is a much smaller problem than one with full admin access.

Most API key breaches aren't sophisticated — they're a missed .gitignore or a key that was never rotated. The highest-leverage changes are:

  1. Hash before storing — treat keys like passwords in your database
  2. Use a secrets manager in production — stop putting secrets in env vars baked into Docker images
  3. Scope every key — match permissions to actual need
  4. Rotate on a schedule — automate it so it actually happens

If you're generating API keys today and want a quick, secure starting point, the EncryptCodec Secret Generator gives you cryptographically secure random secrets in the format you need — no dependencies, no setup.

Share this post

Generate a secure API key or secret

Free, browser-based — no signup required.

Frequently Asked Questions

Related posts