AES-256 Encrypt and Decrypt

Encrypt and decrypt text with AES-256-GCM and PBKDF2 password derivation.

🔒 Client · Offline

About AES encryption

This tool encrypts and decrypts text with AES-256-GCM. GCM gives secrecy and also integrity, so a change to the encrypted data makes the decryption fail instead of giving wrong text.

Your password becomes a key through PBKDF2, which repeats a hash many times. This makes a guess attack slow. A new random salt and a new random nonce are used for each operation, so the same text gives different output each time.

The output holds one envelope. The envelope has four parts, in this order:

version (1 byte) | salt (16 bytes) | iv (12 bytes) | ciphertext and tag

Version 1 is the only version. It fixes PBKDF2 with SHA-256 and 100000 iterations, and AES-256-GCM with a 16-byte tag. A change to these parameters needs a new version number.

An earlier release wrote no version byte. A random salt byte can hold the value 1, so the tool cannot read the layout from the first byte. The tool tries the version 1 layout first. GCM checks its tag, so a wrong layout always fails. The tool then tries the old layout. An old output therefore still decrypts.

The Encrypt and Decrypt buttons change the mode only. They never change your text. To test a round trip, use the Move output to input button. That button replaces the input with the output.

A failed decryption gives one of three errors. An invalid Base64 input reports a format error. A payload that is too short for a salt, an IV, and a tag reports an incomplete envelope. A tag check that fails reports a tag error.

The tag error names two causes: a wrong password, or data that changed after encryption. GCM gives one signal for both, so the tool cannot tell them apart. To find the cause, use the same password on an output that you know is good. If that output decrypts, the password is correct and the other data changed.

The strength comes from the password. A short password gives weak encryption, whatever the algorithm. Use the ID & Secret Generator to make a strong passphrase.

Decrypt a .enc file on the command line

The Download .enc button writes the raw envelope bytes. It does not write the Base64 text. Each command below was run against the output of this tool.

Node.js gives the full check. It reads the salt, the IV, and the tag from the envelope, and it stops with an error when the tag does not match. Save this file as decrypt.mjs:

import { createDecipheriv, pbkdf2Sync } from 'node:crypto'
import { readFileSync } from 'node:fs'

const env = readFileSync(process.argv[2])
const key = pbkdf2Sync(process.argv[3], env.subarray(1, 17), 100000, 32, 'sha256')
const decipher = createDecipheriv('aes-256-gcm', key, env.subarray(17, 29))
decipher.setAuthTag(env.subarray(env.length - 16))
const body = env.subarray(29, env.length - 16)
process.stdout.write(Buffer.concat([decipher.update(body), decipher.final()]).toString('utf8'))

Then run it:

node decrypt.mjs ciphertext.enc 'your password'

OpenSSL needs two steps. The openssl enc command refuses an AEAD cipher and prints enc: AEAD ciphers not supported. Step one derives the key with PBKDF2. Step two decrypts with AES-256-CTR, because GCM builds its keystream with CTR. The counter of the first data block of GCM is 2, so the CTR command takes the 12-byte IV plus 00000002.

PASSWORD='your password'
SIZE=$(wc -c ciphertext.enc | awk '{print $1}')
SALT=$(dd if=ciphertext.enc bs=1 skip=1 count=16 2>/dev/null | xxd -p -c 16)
IV=$(dd if=ciphertext.enc bs=1 skip=17 count=12 2>/dev/null | xxd -p -c 12)
dd if=ciphertext.enc bs=1 skip=29 count=$((SIZE - 45)) of=cipher.bin 2>/dev/null

KEY=$(openssl kdf -keylen 32 -kdfopt digest:SHA256 -kdfopt pass:"$PASSWORD" \
  -kdfopt hexsalt:"$SALT" -kdfopt iter:100000 PBKDF2 | tr -d ':' | tr 'A-Z' 'a-z')

openssl enc -aes-256-ctr -d -K "$KEY" -iv "${IV}00000002" -in cipher.bin

The OpenSSL flow recovers the text, but it does not check the tag. Use the Node.js command when you need the integrity check.