Tools
Phrase Encryptor
Encrypt and decrypt text with a passphrase using AES-256-GCM authenticated encryption. Useful for quickly obfuscating a message you want to send — paste the ciphertext back in with the same passphrase to recover the original.
How it works
- AES-GCM with a 256-bit key.
- Key derived from your passphrase with PBKDF2-SHA-256, 200k iterations, and a random 16-byte salt.
- A fresh 12-byte IV is generated per encryption. Salt + IV are bundled with the ciphertext, so the same passphrase can decrypt it later.
- Everything runs in your browser. Your message and passphrase never hit the network.
About Phrase Encrypt / Decrypt
Phrase Encrypt/Decrypt takes whatever you type into the message box and, on Encrypt, replaces it in place with ciphertext protected by a passphrase you supply. Paste that ciphertext back in later, or hand it to someone who has the same passphrase, click Decrypt, and the original text reappears in the same box. The textarea doubles as both input and output, so there is no separate result panel — a Copy button grabs whatever is currently in the box. Both a message and a passphrase are required; leave either blank and clicking Encrypt or Decrypt shows an error instead of running.
Under the hood it uses the browser's native Web Crypto API, crypto.subtle, not a bundled JS crypto library. Encryption is AES-GCM with a 256-bit key. The key is derived from your passphrase with PBKDF2-SHA-256 at 200,000 iterations, salted with 16 random bytes generated fresh on every run, plus a fresh 12-byte IV per encryption. That salt, IV, and a one-byte format version are packed ahead of the ciphertext and the whole envelope is base64-encoded, so the same passphrase can decrypt it later without storing anything separately. Because GCM is authenticated, a wrong passphrase or a tampered ciphertext fails with an error instead of returning corrupted plaintext.
Reach for this to pass a short secret, such as an API key, a note, or a config value, through a channel you do not fully trust (chat, a shared doc, a paste bin) without standing up a backend. Share the passphrase separately, over a different channel, so only the ciphertext travels through the untrusted one. Everything runs client-side in the browser; the message and passphrase are never sent over the network, which matters if you are pasting something sensitive. It is a demonstration of the pattern, not a credential vault. For anything you need to store long-term, use your platform's actual secrets manager instead of this format.
Examples
Message:
Meet at 9pm, north gate.
Passphrase:
correct horse battery staple
Ciphertext (after clicking Encrypt):
AeDsG/6muErDEPUd/7VKJd5Ive8dBQYwf6LJb0rNNJGUsshF3O4UrI5J8yU2vfZmPYHm2FqYayfsbhgVSmAVooU4Cg7GPasting this exact ciphertext back into the Message box with the passphrase correct horse battery staple and clicking Decrypt returns Meet at 9pm, north gate. — verified byte-for-byte against the tool's own encrypt/decrypt functions. Encrypting the same message and passphrase again produces a different ciphertext string, because the salt and IV are freshly randomized per run; that's expected, not a bug.
The ciphertext above decodes to a 69-byte envelope:
byte 0 version 0x01
bytes 1-16 salt e0ec1bfea6b84ac310f51dffb54a25de
bytes 17-28 IV 48bdef1d0506307fa2c96f4a
bytes 29-68 ciphertext + 16-byte GCM tag (40 bytes)Envelope size is always the plaintext length plus 45 bytes: a 1-byte version, a 16-byte salt, a 12-byte IV, and a 16-byte GCM authentication tag. There's no padding, so the ciphertext length always reveals the exact plaintext length to anyone who sees it — a property of AES-GCM in general, not specific to this tool.
const enc = new TextEncoder()
const salt = crypto.getRandomValues(new Uint8Array(16))
const iv = crypto.getRandomValues(new Uint8Array(12))
const baseKey = await crypto.subtle.importKey(
'raw', enc.encode(passphrase), 'PBKDF2', false, ['deriveKey'],
)
const key = await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 200_000, hash: 'SHA-256' },
baseKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt'],
)
const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, enc.encode(text))The same crypto.subtle calls the tool makes internally — no library needed, works in any modern browser or in Node.js. You'd still need to prepend the salt and IV to the ciphertext and base64-encode the result yourself to make it decryptable later, which is exactly what this tool automates.
Frequently asked questions
What encryption algorithm does this tool use?
AES-GCM with a 256-bit key. The key itself is derived from your passphrase using PBKDF2-SHA-256 at 200,000 iterations, with a random 16-byte salt and a random 12-byte IV generated fresh for every encryption and bundled into the output.
Why does encrypting the same text twice produce a different ciphertext?
Because the salt and IV are regenerated with crypto.getRandomValues() on every Encrypt click, even for identical input and passphrase. This is expected AES-GCM behavior, not a bug — decrypting any of those outputs with the correct passphrase returns the same original text.
Can this tool decrypt AES ciphertext produced by OpenSSL, CyberChef, or crypto-js?
No. It wraps its output in its own envelope — a version byte, then the salt, the IV, and the ciphertext, all base64-encoded — so it can only decrypt what it (or an exact reimplementation of that layout) produced. Tools using a different format, padding scheme, or key-derivation function will not interoperate with it in either direction.
Does my text or passphrase get sent to a server?
No. Encryption and decryption run entirely in the browser using the native Web Crypto API (crypto.subtle); there is no server request involved, so the message and passphrase never leave the page.
Is this safe to use for storing real passwords or secrets long-term?
No. It is a demonstration of correct AES-GCM usage, not a secrets-storage product — there is no passphrase recovery mechanism, and the ciphertext format is specific to this tool. For anything you need to keep long-term, use a dedicated secrets manager instead.