Input
Encryption
AES-256-GCM with PBKDF2 key derivation (600,000 iterations). Everything happens in your browser — message and password are never sent anywhere.
Message
Format & decrypting without this tool
Your encrypted text stays readable even if this website is gone. The format is open and uses only standard algorithms:
cryptor:v2:<iterations>:Base64( salt[16] ‖ iv[12] ‖ ciphertext ‖ GCM tag[16] )
key = PBKDF2-HMAC-SHA256( password as UTF-8, salt, iterations, 32 bytes )
AES-256-GCM, additional authenticated data = header incl. last colon, e.g. "cryptor:v2:600000:"
Node.js 18+, no packages. Save as decrypt.mjs, run node decrypt.mjs message.txt — it asks for the password.
import { pbkdf2Sync, createDecipheriv } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { createInterface } from 'node:readline/promises';
const text = readFileSync( process.argv[2], 'utf8' ).replace( /\s+/g, '' );
const [ , header, iterations, b64 ] = /^(cryptor:v2:(\d+):)(.+)$/.exec( text );
const raw = Buffer.from( b64, 'base64' );
const rl = createInterface( { input: process.stdin, output: process.stderr } );
const password = await rl.question( 'Password: ' );
rl.close();
const key = pbkdf2Sync( password, raw.subarray( 0, 16 ), Number( iterations ), 32, 'sha256' );
const decipher = createDecipheriv( 'aes-256-gcm', key, raw.subarray( 16, 28 ) );
decipher.setAAD( Buffer.from( header ) );
decipher.setAuthTag( raw.subarray( -16 ) );
process.stdout.write( Buffer.concat( [ decipher.update( raw.subarray( 28, -16 ) ), decipher.final() ] ) );
Python 3 with pip install cryptography. Save as decrypt.py, run python3 decrypt.py message.txt.
import base64, getpass, re, sys
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
text = re.sub(r'\s+', '', open(sys.argv[1], encoding='utf-8').read())
header, iterations, b64 = re.fullmatch(r'(cryptor:v2:(\d+):)(.+)', text).groups()
raw = base64.b64decode(b64)
password = getpass.getpass('Password: ').encode('utf-8')
key = PBKDF2HMAC(hashes.SHA256(), 32, raw[:16], int(iterations)).derive(password)
plain = AESGCM(key).decrypt(raw[16:28], raw[28:], header.encode('ascii'))
sys.stdout.write(plain.decode('utf-8'))
PHP 7.1+ with the OpenSSL extension. Save as decrypt.php, run php decrypt.php message.txt.
<?php
$text = preg_replace( '/\s+/', '', file_get_contents( $argv[1] ) );
preg_match( '/^(cryptor:v2:(\d+):)(.+)$/', $text, $m );
$raw = base64_decode( $m[3], true );
fwrite( STDERR, 'Password: ' );
$password = rtrim( fgets( STDIN ), "\r\n" );
$key = hash_pbkdf2( 'sha256', $password, substr( $raw, 0, 16 ), (int) $m[2], 32, true );
$plain = openssl_decrypt( substr( $raw, 28, -16 ), 'aes-256-gcm', $key, OPENSSL_RAW_DATA,
substr( $raw, 16, 12 ), substr( $raw, -16 ), $m[1] );
echo $plain === false ? "Wrong password or damaged data\n" : $plain;
This tool still decrypts the formats it produced earlier — paste them as usual:
- v1 (January – October 2026): bare Base64 without the
cryptor:v2:header, same layout, no additional authenticated data. 100,000 iterations until 15 March 2026, 300,000 after that. - Legacy (before 2026, starts with
U2FsdGVkX1): OpenSSL-compatible AES-256-CBC with MD5 key derivation. Without this tool:
openssl enc -d -aes-256-cbc -md md5 -a -in message.txt