All posts

macOS Keychain for Developers: A Practical Guide

We tried using the macOS security CLI for a month. The goal: stop storing API keys in .env files and use the Keychain instead — encrypted, hardware-backed, free. How hard could it be?

It took four minutes to store the first secret. Eleven minutes to figure out why we couldn't read it back. And after a month of fighting with security find-generic-password flags, we gave up and built something better.

But the instinct was right. The macOS Keychain is the best credential store most developers never use. The problem was never the Keychain. The problem was the interface.

Two keychains, one name

Most developers don't realize macOS has two fundamentally different keychain systems. The distinction matters for credential management.

Legacy Login Keychain File on disk Password-based unlock upgrade to Modern Data Protection Keychain Secure Enclave backed Touch ID / biometric auth

The login keychain is the legacy system. A file on disk (~/Library/Keychains/login.keychain-db), encrypted with your macOS login password. Unlock it once and everything inside is accessible until it locks again. This is what the security CLI uses by default. This is also the wrong choice for developer secrets.

The Data Protection Keychain is the modern system, introduced with the T2 chip and Apple Silicon. It's backed by the Secure Enclave — a physically separate processor on your Mac's SoC. Keys generated in the Secure Enclave never leave the chip. Not into RAM, not into swap, not into a crash dump. The Enclave performs encryption and decryption internally, releasing plaintext only after biometric verification.

When you store a secret with Data Protection Keychain access controls requiring biometric auth, here's what happens at the hardware level:

  1. The Secure Enclave generates a per-item encryption key
  2. Your secret is encrypted with that key and stored in the keychain database
  3. When you request the secret, the Enclave checks your fingerprint against its stored template
  4. Only after a match does the Enclave decrypt and release the value — through a hardware-isolated channel

This isn't "encryption at rest" in the way most tools use the term. This is hardware-enforced access control where the decryption key literally cannot be extracted — not by Apple, not with physical access to the machine.

Why the security CLI fails developers

The native security command can interact with the Keychain. In theory. In practice, it fights you at every step.

Problems with the security CLI
Secret values land in your shell history. There's no Touch ID support — only password-based auth. Error messages assume familiarity with Apple's Security framework internals. And there's no namespace hierarchy for organizing secrets across projects.

Storing a secret:

# Store an API key (the value is now in your shell history)
$ security add-generic-password -s "myapp" -a "STRIPE_KEY" -w "sk_live_4eC39HqL..."

# Try to update it — surprise, you can't. It errors.
$ security add-generic-password -s "myapp" -a "STRIPE_KEY" -w "sk_live_NEW..."
security: SecKeychainItemCreateFromContent: The specified item already exists in the keychain.

# Delete first, then re-add
$ security delete-generic-password -s "myapp" -a "STRIPE_KEY"
$ security add-generic-password -s "myapp" -a "STRIPE_KEY" -w "sk_live_NEW..."

Reading a secret:

# This prompts for your Keychain password, not Touch ID
$ security find-generic-password -s "myapp" -a "STRIPE_KEY" -w
sk_live_4eC39HqL...

# And yes, it prints the raw value right to stdout
# Hope nobody is watching your terminal

Listing secrets:

# Dump the entire keychain. Good luck parsing this.
$ security dump-keychain
keychain: "/Users/jasper/Library/Keychains/login.keychain-db"
version: 512
class: "genp"
attributes:
    ...
    "svce"="myapp"
    "acct"="STRIPE_KEY"
    ...

The problems compound. Secrets in shell history. No namespace hierarchy — good luck organizing 47 secrets across 8 projects. Defaults to the login keychain (password-based), not the Data Protection Keychain (biometric). And error messages written for Apple Security framework experts.

What a developer-friendly macOS Keychain interface looks like

We built NoxKey to wrap the Keychain APIs with an interface that makes sense for development workflows. Same Keychain, same Secure Enclave, same hardware guarantees — without the pain.

Side by side:

Storing a secret

security CLI
# Value in shell history
# No Touch ID, no namespacing
security add-generic-password \
  -s "myapp" -a "STRIPE_KEY" \
  -w "sk_live_4eC39..."
NoxKey
# From clipboard — no shell history
# Touch ID enforced, namespaced
noxkey set company/payments/STRIPE_KEY \
  --clipboard

Reading a secret

security CLI
# Raw value printed to stdout
# Password auth only
security find-generic-password \
  -s "myapp" -a "STRIPE_KEY" -w
NoxKey
# Encrypted handoff, Touch ID
# Value never visible in terminal
eval "$(noxkey get \
  company/payments/STRIPE_KEY)"

That eval pattern is deliberate. noxkey get doesn't print the secret. It returns a source command pointing to a self-deleting encrypted script. The raw value never appears in your terminal, never lands in logs, never shows up in shell history.

Listing secrets

security CLI
# Wall of XML-ish output
security dump-keychain | grep -A4 "svce"
NoxKey
# Clean hierarchy, no values shown
noxkey ls company/
# company/payments/STRIPE_KEY
# company/api/OPENAI_KEY
# company/db/PROD_URL  strict

Updating a secret

security CLI
# Delete + re-add dance
security delete-generic-password \
  -s "myapp" -a "STRIPE_KEY"
security add-generic-password \
  -s "myapp" -a "STRIPE_KEY" \
  -w "sk_live_NEW..."
NoxKey
# Just set it again
noxkey set company/payments/STRIPE_KEY \
  --clipboard

How NoxKey uses the macOS Keychain APIs

Under the hood
NoxKey uses Apple's Security framework directly — the same APIs that Safari and iCloud Keychain use. Every item is stored as a kSecClassGenericPassword in the Data Protection Keychain with access control flags set to .biometryCurrentSet. The org/project/KEY naming maps to the Keychain's service and account fields with a consistent prefix, so NoxKey items never collide with anything else in your Keychain.

The .biometryCurrentSet flag is important: if you add or remove a fingerprint, all existing items require re-authentication. A stolen laptop with a new fingerprint enrolled can't access your secrets.

The CLI binary is code-signed and hardened, which means macOS enforces that only NoxKey can access the items it created. And when an AI agent requests a secret, NoxKey detects it via process-tree walking and switches to encrypted handoff mode automatically.

Credential management options compared

.env files security CLI 1Password CLI NoxKey
Encryption at rest None Keychain (password) AES-256 (cloud) Secure Enclave
Auth model None Password, unlock-all Master password (biometric unlock available) Touch ID, per-access
Secret in shell history N/A Yes No No
Namespace/hierarchy Per-file Flat Vaults org/project/KEY
Network required No No Yes (sync) No
Cost Free Free $36/yr Free
Works offline Yes Yes Partial Yes
AI agent guardrails None None None Process-tree detection

1Password is excellent for team secrets and consumer passwords. Vault and Doppler are the right call for infrastructure-scale secret management. But for a solo developer or small team on macOS who wants API keys encrypted, biometrically locked, and locally stored — the Keychain is the answer. It's been there the whole time.

The limitation you should know about

This is macOS only. The Secure Enclave is Apple hardware. The Data Protection Keychain is an Apple API. If your team is cross-platform, this isn't a universal solution — it's a local one.

We're fine with that trade-off. Our secrets are on our machines, for our development workflows. They don't need to sync to a server. They don't need to work on Windows. They need to be encrypted, authenticated, and fast. The Keychain delivers all three. And when those secrets flow to AI coding tools, the Keychain-backed approach means they stay protected.

Getting started with macOS Keychain in 60 seconds

# Install NoxKey — download from https://noxkey.ai

# Import your existing .env file
noxkey import myorg/project .env
# → Touch ID once, all keys stored

# Verify they're there
noxkey ls myorg/project/

# Use a secret
eval "$(noxkey get myorg/project/DATABASE_URL)"

# Delete the .env file. You don't need it anymore.
rm .env

That last step is the important one. Once your secrets are in the Keychain, the .env file is just a liability sitting on disk. Delete it.

Your Mac already has the best credential store. You just need the right interface.
Key Takeaway
The macOS Keychain — specifically the Data Protection Keychain backed by the Secure Enclave — is a hardware-encrypted, biometrically authenticated credential store already on your Mac. The security CLI makes it painful to use, but with the right interface, it replaces .env files, eliminates secrets from shell history, and gives you per-access Touch ID authentication. Free, offline, local. No cloud sync required.