About the AES Decrypt
Paste text encrypted with toolo's AES Encrypt, enter the passphrase, and this AES decryption online tool gives you back the original message. If the passphrase is wrong or one character of the text changed, you get a clear error instead of garbage, because the text is protected with AES-256-GCM.
You only type the passphrase. The salt, IV and authentication tag are stored inside the Base64 string, so there's no key, IV or mode to set. Decryption runs in your browser with the Web Crypto API. Ciphertext made by other tools uses other layouts, so it won't open here; the Guide and In code tabs show how to decrypt it with OpenSSL or Node.js.
- Paste the whole string into Encrypted text (Base64). Line breaks added by an email client are dropped, a space is read as
+, and URL-safe Base64 with-and_works too. - Type the passphrase into Passphrase. Capitals and spaces count, so click the eye button if you're unsure what you typed.
- Click Decrypt or press Enter in the passphrase field. The button shows Decrypting... for a moment while 600,000 rounds of PBKDF2 rebuild the key.
- The message appears under Decrypted text, with a Copy button. Editing either field clears it again.
InputEncrypted text (Base64):
VEwCSyUuwun8qXT7VPaM/m7U6hhVrSpR1fYptsdudqVLuEAsYpV+RIIXEKngWCJ/0r/LaEU5mmYmyXIaAo/JuMz9K5vSdfjnGraTqoW7X3ZZ+34y12rZLXql6ocEGuir8rFDNw==
Passphrase: correct horse battery stapleOutputStaging DB password: HBe8ybAKknolnfHGCOyi1BlMz5Z91VYnInputEncrypted text (Base64):
VEwCjLZX28BsQqMpvdnir+KgEDOORtLmVH2/Jd6RNXLZCWM4HO0vXMtM44pxNACJy94KCwC6gNCG7u4=
Passphrase: Correct horse battery stapleOutputWrong password or the text was changedWith correct horse battery staple in lowercase this decrypts to Hello, world. Change any single character of the encrypted text and you get the same error.
InputEncrypted text (Base64):
U2FsdGVkX19Dls3GEDClILdmVzPrV5dF6UewoUV7/6nAhgRH8UqPyBhK3goCWzWolHtl2q9SLuH6w7R/JAeawrRXp9BK3hexv2R9MsHlo8c=
Passphrase: correct horse battery stapleOutputWrong password or the text was changedThe passphrase is right, but the format isn't toolo's. U2FsdGVkX1 is Base64 for Salted__, the header openssl enc writes. The OpenSSL command on the In code tab decrypts it.
Error messages and what they mean
When the text can't be decrypted, you'll see one of these:
- Enter both the encrypted text and the passphrase: one of the fields is empty.
- Not valid Base64: the string contains a character Base64 can't have, such as a quote or a period copied along with it, or it's missing a character at the end.
- Too short to be toolo AES output: the decoded bytes can't even hold the header, salt, IV and tag. Part of the string is missing, or it was never encrypted.
SGVsbG8=gives this, because it's only the word Hello in Base64. - Wrong password or the text was changed: the passphrase isn't exactly the one used to encrypt, the string was edited or cut short, or it came from a different tool.
Why a wrong passphrase and a changed text give the same error
AES-GCM stores a 16-byte authentication tag with the ciphertext. Decryption computes the tag again from the key and the ciphertext and compares the two. A wrong passphrase gives a wrong key, an edited byte gives different ciphertext, and either way the tags don't match, so Web Crypto returns nothing at all. That's what you want: no text is shown unless it's exactly what was encrypted with that passphrase.
The format, and texts from older versions
Current output (version 2) is the Base64 of the bytes 54 4C 02, a 16-byte salt, a 12-byte IV, then the AES-256-GCM ciphertext with its 16-byte tag at the end. The key comes from PBKDF2-HMAC-SHA256 with 600,000 iterations. Every version 2 string starts with VEwC.
Older toolo texts (version 1) have no header and use AES-256-CBC, a 16-byte IV and 100,000 PBKDF2 iterations. The decryptor recognizes them and still opens them. Version 1 has no authentication tag, so some edits to an old text can go unnoticed.
Decrypting AES from other tools
AES ciphertext doesn't describe itself. To decrypt output from another tool or library you need every setting it used:
- The mode: CBC, GCM, CTR or ECB. Decrypting with the wrong one fails or gives garbage.
- The key, exactly 16, 24 or 32 bytes and often written as hex. If you were given a password instead, you also need the key derivation function and its settings (salt, iterations, hash).
- The IV or nonce: 16 bytes for CBC and CTR, usually 12 for GCM. Many tools put it in front of the ciphertext.
- For GCM, the 16-byte tag. For CBC and ECB, the padding, which is almost always PKCS#7.
- The encoding of the ciphertext, Base64 or hex. Decoding hex as Base64 gives the wrong bytes.
Common errors in OpenSSL and Node.js
A string starting with U2FsdGVkX1 came from openssl enc or from CryptoJS with a passphrase. In CBC mode, a wrong IV only garbles the first 16 bytes of the result while the rest decrypts fine, which is a useful clue. The usual error messages:
bad decrypt(OpenSSL and Node.js): wrong key or password, wrong mode, or a different key derivation. Foropenssl enc, the-pbkdf2and-itervalues must match the ones used to encrypt.*** WARNING : deprecated key derivation used: the text was encrypted without-pbkdf2. Since OpenSSL 1.1.0 that old derivation uses SHA-256, so text from OpenSSL 1.0 needs-md md5to decrypt.wrong final block length: the ciphertext isn't a multiple of 16 bytes. It was cut short, or decoded with the wrong encoding.Invalid key lengthorInvalid initialization vector(Node.js): the key or IV has the wrong number of bytes for the cipher.Unsupported state or unable to authenticate data(Node.js): the GCM tag didn't match, which is the same check that gives this page's wrong password error.
Decrypt toolo output in your own code
These functions read version 2 output. In Python, the same byte offsets work with hashlib.pbkdf2_hmac for the key and AESGCM from the cryptography package.
const crypto = require("node:crypto");
function decryptToolo(base64, password) {
const data = Buffer.from(base64, "base64");
if (data.subarray(0, 3).toString("hex") !== "544c02") {
throw new Error("Not toolo version 2 output");
}
const salt = data.subarray(3, 19);
const iv = data.subarray(19, 31);
const tag = data.subarray(data.length - 16);
const ciphertext = data.subarray(31, data.length - 16);
const key = crypto.pbkdf2Sync(password, salt, 600000, 32, "sha256");
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
}async function decryptToolo(base64, password) {
const data = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
const salt = data.slice(3, 19);
const iv = data.slice(19, 31);
const material = await crypto.subtle.importKey(
"raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveKey"]
);
const key = await crypto.subtle.deriveKey(
{ name: "PBKDF2", salt, iterations: 600000, hash: "SHA-256" },
material,
{ name: "AES-GCM", length: 256 },
false,
["decrypt"]
);
// Web Crypto expects the 16-byte tag at the end of the ciphertext, as stored.
const plain = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, data.slice(31));
return new TextDecoder().decode(plain);
}Decrypt OpenSSL or raw-key AES-CBC output
For text made with openssl enc -aes-256-cbc, or by a tool that gave you a hex key and IV. The first command decrypts the OpenSSL example on the Examples tab.
# -pbkdf2 and -iter must match the values used to encrypt
openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 -a -A \
-pass pass:'correct horse battery staple' -in message.b64# 64 hex characters of key (32 bytes), 32 hex characters of IV (16 bytes)
openssl enc -d -aes-256-cbc -a -A \
-K 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f \
-iv 0f0e0d0c0b0a09080706050403020100 -in message.b64const crypto = require("node:crypto");
function decryptCbc(base64, keyHex, ivHex) {
const decipher = crypto.createDecipheriv(
"aes-256-cbc", Buffer.from(keyHex, "hex"), Buffer.from(ivHex, "hex")
);
return Buffer.concat([
decipher.update(Buffer.from(base64, "base64")),
decipher.final(), // a wrong key usually throws "bad decrypt"
]).toString("utf8");
}Can I decrypt AES without the key or password?
No. AES-256 has no known practical weakness, so the only way in is guessing the password. A long random passphrase can't be guessed, and 600,000 PBKDF2 rounds make each guess slow.
Why does it say wrong password when I'm sure it's right?
Check capital letters, a space at the start or end, and look-alikes such as 0 and O. Then check that the whole encrypted string was copied, because one missing or changed character gives the same error.
Can this page decrypt OpenSSL, CryptoJS or other AES output?
No. It reads only the format made by toolo's AES Encrypt. For other tools you need their mode, key or password derivation, and IV; the OpenSSL and Node.js snippets on this page cover the common AES-256-CBC cases.
Do I need the IV to decrypt AES?
Yes, for every mode except ECB. In toolo's format the IV is stored in the encrypted string, so the passphrase is all you need. Other tools often put the IV in front of the ciphertext or give it to you separately.
Is AES decryption the same as Base64 decoding?
No. Base64 decoding only turns the text into bytes, and those bytes are still encrypted. Decryption needs the passphrase. For text that is only encoded, use Base64 Decode.
Does it still open texts from the old toolo encryptor?
Yes. Older toolo texts, made with AES-CBC and 100,000 PBKDF2 iterations, are detected automatically and decrypted with those settings.
Is it safe to paste a secret and its passphrase here?
The encrypted text, the passphrase and the decrypted message stay in your browser and aren't sent to a server. The decrypted message is shown on screen, though, so on a shared or recorded screen close the tab when you're done.