Claude Fable 5.1 & GPT-6 Astra packages are live

Encryption

Free

Encrypting data correctly — authenticated ciphers, nonce discipline, key management, and the primitives that must never be used.

236 lines9.1 KB Claude Security
targetModels
Claude Fable 5.1Claude Opus 5Claude Sonnet 5Claude 5 FamilyFuture Claude Models
name
encryption
category
Security
description
Encrypting data correctly — authenticated ciphers, nonce discipline, key management, and the primitives that must never be used.
license
MIT
author
Agent.md maintainers
last-verified
reviewed-by
unreviewed
<!-- Generated from models/_canonical by scripts/build-model-variants.js. Edit the canonical source, not this file. Behavioural profile for Claude: scripts/model-profiles.json -->

<critical_constraints> FORBIDDEN: Truncating code or writing placeholders such as "// ... existing code ..." or "# rest unchanged". Every edit is complete and applies as written. FORBIDDEN: Reporting a check as passed without showing the command and its output. REQUIRED: Reason through the rules below before the first edit; when two rules conflict, the one stated first wins.

  • Never use these:
  • Never reuse a nonce with the same key. For AES-GCM this is catastrophic: two messages under one nonce leak the XOR of the plaintexts and allow forgery of the authentication tag. - Generate with a CSPRNG — crypto.randomBytes(12) — or use a strictly increasing counter that cannot repeat across restarts or replicas. - 96 bits is correct for GCM. Longer nonces are hashed internally and gain nothing. - The nonce is not secret. Store it alongside the ciphertext. - After roughly 2³² messages under one key with random nonces, rotate the key — collision probability becomes non-negligible. </critical_constraints>

#Purpose

Rules for encrypting data at rest and in application code. Transport encryption is Security/https; password hashing is Security/authentication — hashing is not encryption and the two must never be confused.

The rule underneath everything: use an authenticated cipher, use a library, and never design a scheme. Cryptography fails silently — code that produces plausible ciphertext can be trivially breakable.


#Choose an authenticated cipher

<security_rules>

Encryption without authentication permits an attacker to modify ciphertext undetected. Always use AEAD.

UseAlgorithm
General purposeAES-256-GCM
No AES hardware accelerationChaCha20-Poly1305
Very large messages, nonce-misuse safetyAES-GCM-SIV, XChaCha20-Poly1305
Public-keyHybrid — X25519 for key agreement, then AEAD
js
import crypto from "node:crypto";

function encrypt(plaintext, key) {
  const iv = crypto.randomBytes(12);                    // 96-bit nonce for GCM
  const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
  const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
  return { iv, ciphertext: ct, tag: cipher.getAuthTag() };
}

function decrypt({ iv, ciphertext, tag }, key) {
  const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
  decipher.setAuthTag(tag);                             // must be set before final()
  return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
}

decipher.final() throws when the tag does not verify. Never catch that error and continue with the partial plaintext — a failed tag means the data is not authentic and must be discarded.

Never use these:

PrimitiveWhy
AES-ECBIdentical blocks produce identical ciphertext; patterns leak
AES-CBC without a MACPadding-oracle attacks recover plaintext
DES, 3DES, RC4, BlowfishBroken or deprecated
md5, sha1 for signaturesCollision-vulnerable
Any hand-written XOR or "custom" cipherBroken by construction
RSA with PKCS#1 v1.5 encryptionBleichenbacher; use OAEP

</security_rules>

#Nonce and IV discipline

<security_rules>

This is where correct algorithm choices most often fail in practice.

  • Never reuse a nonce with the same key. For AES-GCM this is catastrophic: two messages under one nonce leak the XOR of the plaintexts and allow forgery of the authentication tag.
  • Generate with a CSPRNGcrypto.randomBytes(12) — or use a strictly increasing counter that cannot repeat across restarts or replicas.
  • 96 bits is correct for GCM. Longer nonces are hashed internally and gain nothing.
  • The nonce is not secret. Store it alongside the ciphertext.
  • After roughly 2³² messages under one key with random nonces, rotate the key — collision probability becomes non-negligible.

</security_rules>

#Keys

<security_rules>

  • Generate with a CSPRNG: crypto.randomBytes(32) for AES-256.
  • Never derive a key directly from a password with a plain hash. Use a KDF — argon2id, scrypt, or PBKDF2 with a high iteration count and a random salt.
  • Never hard-code a key, commit one, or ship one in a client bundle. → Security/secret-management
  • Use envelope encryption: a KMS holds the key-encryption key, which wraps a per-record data-encryption key. The master key never leaves the KMS boundary.
js
// Envelope encryption: the master key never leaves the KMS.
const { Plaintext: dataKey, CiphertextBlob: wrappedKey } =
  await kms.generateDataKey({ KeyId: MASTER_KEY_ID, KeySpec: "AES_256" });

const record = encrypt(payload, dataKey);
dataKey.fill(0);                       // drop the plaintext key promptly

await db.secret.create({
  data: { ...record, wrappedKey, keyVersion: 3 },
});
  • Version your keys. Store a key identifier with each ciphertext so rotation does not require decrypting everything at once.
  • Separate keys by purpose. One key for encryption, a different one for signing. Reusing a key across algorithms invites cross-protocol attacks.

js
// Blind index: HMAC under a separate key makes equality lookups possible
// without weakening the cipher or storing a searchable plaintext.
const indexKey = await kms.decrypt(WRAPPED_INDEX_KEY);
const blindIndex = crypto
  .createHmac("sha256", indexKey)
  .update(email.trim().toLowerCase())   // normalise before hashing
  .digest("base64");

await db.user.findFirst({ where: { emailIndex: blindIndex } });

</security_rules>

#Encoding, comparison, randomness

<security_rules>

  • Base64 and hex are encodings, not encryption. A base64 string is plaintext.
  • Compare secrets with crypto.timingSafeEqual, never ===. Length-check first — it throws on mismatched lengths.
  • Use crypto.randomBytes / crypto.getRandomValues for anything security relevant. Math.random() is a predictable PRNG and must never generate tokens, identifiers, salts or nonces.
  • For hashing where speed is fine — checksums, cache keys — use sha256. For passwords, never.

</security_rules>

#What to encrypt

<security_rules>

Encryption is not free: it breaks indexing, search and sorting, and it moves the problem to key management.

  • Encrypt what regulation or blast radius demands: payment details, health data, government identifiers, credentials for third-party systems.
  • Prefer not storing the data at all. Nothing protects a field like its absence.
  • For lookups over encrypted values, store a separate blind index — an HMAC of the normalised value under a distinct key — rather than weakening the cipher.
  • Full-disk and database-level encryption protect against stolen media. They do not protect against an application-level compromise, because the application reads plaintext.

</security_rules>

#Anti-patterns

Anti-patternWhy it failsFix
AES-CBC without a MACPadding oracle recovers plaintextAES-256-GCM
AES-ECBLeaks structure through repeated blocksAny AEAD mode
Reusing a GCM nonceLeaks plaintext XOR; enables forgeryRandom 96-bit per message
Math.random() for an IV or saltPredictablecrypto.randomBytes
Key derived by sha256(password)No work factor; brute-forcedargon2id / scrypt
Ignoring a tag verification failureAccepts tampered dataDiscard on throw
Base64 treated as encryptionIt is an encodingEncrypt, then encode
One key for everything, foreverNo blast-radius limit, no rotation pathPer-purpose, versioned keys
Hand-rolled cipherBroken by constructionUse a vetted library
=== on secretsTiming oracletimingSafeEqual

#Checklist

  • All encryption uses an AEAD mode (AES-256-GCM or ChaCha20-Poly1305)
  • No ECB, unauthenticated CBC, DES, 3DES or RC4 anywhere
  • Nonces are CSPRNG-generated, 96-bit for GCM, never reused under a key
  • Authentication tag failures discard the data and are never swallowed
  • Keys are CSPRNG-generated and never hard-coded or committed
  • Password-derived keys use argon2id, scrypt or high-iteration PBKDF2
  • Envelope encryption with a KMS-held key-encryption key
  • Ciphertexts carry a key identifier so rotation is incremental
  • Distinct keys per purpose; no key reused across algorithms
  • Secret comparisons use timingSafeEqual
  • Encrypted-field lookups use a blind index, not a weakened cipher