AppSafe/playground

NPM PACKAGE / HANDS-ON EXAMPLE

See the package work.

A small, public playground for @asafarim/appsafe. Encrypt with a shared password or for a public key, then decrypt with the matching secret — the same calls you would ship in your own app.

Try the playgroundNo server / no upload
Web Crypto APIAES-256-GCMPBKDF2 or ECDH P-256TypeScript
appsafe-demo

// password mode

await encryptBytes(file, password);

 

// public-key mode

await encryptBytesForRecipients(file, publicKey);

// stays in this browser tab

01 / LIVE PLAYGROUND

Use the public package directly.

This demo has no owner gate because its purpose is to document the reusable npm API. Every transform and every generated key stays in browser memory.

Not sent over the network
Encrypted payloadwaiting
Encrypt text to inspect the payload bytes.

Payloads are self-describing: inspectAppSafePayload() reports the mode, so decrypting with the wrong method fails with MODE_MISMATCH instead of guessing.

02 / TWO METHODS

Same core, different custody.

Both methods finish with AES-256-GCM over an authenticated, versioned header. They differ in where the key comes from and who must keep a secret.

PASSWORD MODE / ENVELOPE v1

One shared secret encrypts and decrypts.

  1. PasswordTyped or injected at runtime.
  2. Random salt + PBKDF2-HMAC-SHA-256600,000 iterations by default.
  3. AES-256-GCM keyDerived per payload; never stored.
  4. Encrypt with a random 96-bit IVThe header is authenticated as additional data.
ASAFE5v11salt16iv12iterations4ciphertext + tagn + 16
Keep secret
  • The password (anyone holding it can decrypt and re-encrypt).
Safe to commit
  • Encrypted .appsafe payloads.

PUBLIC-KEY MODE / ENVELOPE v2

A public key encrypts; only its private key decrypts.

  1. Random 256-bit content keyEncrypts the data once with AES-256-GCM.
  2. Ephemeral P-256 key × recipient public keyECDH shared secret, fresh for each recipient.
  3. HKDF-SHA-256 wrap keyBound to both public keys and the envelope version.
  4. Wrap the content key per recipientAES-256-GCM; the whole header is then authenticated.
  5. Private key unwrapsSelected by its fingerprint in the header.
ASAFE5v21alg1count1iv12recipient × n141 eachciphertext + tagn + 16
Keep secret
  • Each private key (.appsafe/key.txt).
Safe to commit
  • Public keys (.appsafe/key.pub).
  • Key fingerprints.
  • Encrypted .appsafe payloads.

When to choose each method

ConcernPassword modePublic-key mode
Who can encryptAnyone with the passwordAnyone with a public key
Who can decryptAnyone with the passwordOnly holders of a listed private key
Secret materialThe passwordPrivate keys only
Safe to commitCiphertextCiphertext and public keys
Multiple peopleEveryone shares one secretUp to 16 recipients, each with their own key
CI and deploymentInject the password; the job can also re-encryptEncrypt-only jobs need no secret; decrypting jobs get a private key
RotationChoose a new password and re-encrypt every artifactGenerate a new key, re-encrypt for it, verify, then retire the old key
Recovery if the secret is lostUnrecoverableUnrecoverable unless another recipient's key still works
Best forPersonal use, quick sharing, small teamsTeams, CI, committed private apps, least-privilege access

03 / FAILURE LAB

Wrong inputs fail closed.

Authentication covers the header, every recipient entry, and the ciphertext. Run the checks to see the exact error codes your app should handle.

Each check creates fresh, throwaway keys and payloads in this tab, then tries to break them. Every failure must be a typed AppSafeCryptoError — never partial plaintext.

  • Wrong passwordpassword modeINVALID_PASSWORD_OR_DATAnot run
  • Tampered password payloadpassword modeINVALID_PASSWORD_OR_DATAnot run
  • Wrong private keypublic-key modeNO_MATCHING_KEYnot run
  • Tampered public-key payloadpublic-key modeINVALID_KEY_OR_DATAnot run
  • Truncated payloadeither modeINVALID_PAYLOADnot run
  • Password used on a public-key payloadeither modeMODE_MISMATCHnot run
  • Malformed public keypublic-key modeINVALID_KEYnot run

04 / COPYABLE RECIPES

A few calls are enough.

Install the package, supply a password or keys you manage, and keep the returned bytes wherever your app needs them.

Password / text round-trip
import { decryptText, encryptText } from "@asafarim/appsafe";

const encrypted = await encryptText("private note", password);
const plaintext = await decryptText(encrypted, password);
Password / file round-trip
import { decryptBytes, encryptBytes } from "@asafarim/appsafe";

const input = new Uint8Array(await file.arrayBuffer());
const encrypted = await encryptBytes(input, password);
const plaintext = await decryptBytes(encrypted, password);
Public key / generate a key pair
import { generateKeyPair } from "@asafarim/appsafe";

const { publicKey, privateKey, fingerprint } = await generateKeyPair();
// publicKey  -> safe to commit or share (for example .appsafe/key.pub)
// privateKey -> secret: store it privately, never commit or log it
Public key / file round-trip
import {
  decryptBytesWithPrivateKey,
  encryptBytesForRecipients,
} from "@asafarim/appsafe";

const input = new Uint8Array(await file.arrayBuffer());
// Anyone with the public key can encrypt...
const encrypted = await encryptBytesForRecipients(input, publicKey);
// ...only the private-key holder can decrypt.
const plaintext = await decryptBytesWithPrivateKey(encrypted, privateKey);
Public key / multiple recipients
import { encryptTextForRecipients } from "@asafarim/appsafe";

const encrypted = await encryptTextForRecipients(note, [
  ownerPublicKey,
  deployPublicKey,
]);
// Each recipient decrypts with its own private key.
Detect the mode, then decrypt
import {
  AppSafeCryptoError,
  decryptBytes,
  decryptBytesWithPrivateKey,
  inspectAppSafePayload,
} from "@asafarim/appsafe";

try {
  const info = inspectAppSafePayload(payload);
  const plaintext = info.mode === "password"
    ? await decryptBytes(payload, password)
    : await decryptBytesWithPrivateKey(payload, privateKey);
} catch (error) {
  if (error instanceof AppSafeCryptoError) {
    console.warn(error.code); // e.g. NO_MATCHING_KEY, never the key itself
  }
}
CLI / encrypt a private app folder
# Public-key mode for a private app folder (CLI)
appsafe init --mode public-key   # config: .appsafe/key.pub + .appsafe/key.txt
appsafe keygen                   # private key is added to .gitignore
appsafe encrypt                  # needs only the public key
appsafe decrypt                  # needs the private key
appsafe rekey                    # migrate password artifacts to the configured mode
A / BROWSER CORE

Both methods

pnpm add @asafarim/appsafe

Password and public-key encryption, key generation, and payload inspection run anywhere Web Crypto supports AES-GCM, PBKDF2, HKDF, and ECDH P-256: evergreen browsers, Node 20+, and Deno.

B / CLI ONLY

Files and folders

pnpm add -D @asafarim/appsafe-cli

Key files, folder archives, per-target modes, .gitignore management, and rekey migration need a filesystem, so they live in the Node.js CLI.

C / FORMAT

Not Age-compatible

ECDH-P256+HKDF-SHA256+A256GCM

Uses the same recipient/identity model as Age, built only from Web Crypto primitives. Age files and keys cannot be read or written.