How to decrypt AES text
- Decrypt is selected and a sample is already loaded. Its password is
correct horse battery staple, so you can see a successful run first. - Paste your string into Encrypted Base64. Standard or URL-safe Base64 both work, with or without
=, and line breaks are ignored. - With the Password key type, enter the password and, if the sender changed it, the same PBKDF2 iterations (default 310,000). For Raw key, paste the 16-, 24- or 32-byte key as Base64 or hex.
- Press Decrypt, or Enter in the password field. The message appears under Decrypted text. If the content is not UTF-8 text, it is shown as Base64 and Download saves the original bytes.
A successful run reports “Decrypted and authenticated”: the GCM tag matched, so the text is exactly what the sender encrypted.
Base64 decrypt or Base64 decode?
Many people search for “Base64 decrypt” when they have a plain Base64 string. Base64 is an encoding with no key, so such a string only needs decoding, and the Base64 decoder does that. A quick test: if decoding gives readable text or a recognizable file, it was never encrypted. If it gives random-looking bytes and someone gave you a password, you have ciphertext, and this page is the right one, provided it was made in the format below. Base64 is not encryption explains the difference.
Test it with a known value
This string was encrypted outside the browser with the Python encrypt() function from the format documentation, using the password correct horse battery staple:
SD8wB+5V2GRfupy/yEY1xTUE8d5z9fwbzQ0gxeRzbtym1I4O7A9Q2NYnbYxEPYgvwJ4Q7DU5q/sugOx+B1Ro3ujkDXvnl/y2XYuji9A=
It decrypts to Meet me at the usual place at 10. The 104 characters decode to 77 bytes: 16 of salt, 12 of IV, 33 of ciphertext and 16 of tag. The breakdown under the result shows the same split for any string you paste.
Error messages and what they mean
| Message | Usual cause |
|---|---|
| Decryption failed: wrong password or key, wrong mode, or the data was changed | Wrong password, a different iteration count, raw key used for a password blob or the reverse, or a character lost in copying |
| Too short to be a ciphertext from this tool | The string was cut off, or it is not from this tool at all |
| An AES key must be 16, 24 or 32 bytes | The raw key is the wrong length; check whether it is Base64 or hex |
| This browser does not support 192-bit AES keys | Chrome and Edge allow only 128- and 256-bit keys; decrypt with Python or Node.js instead |
| Iterations must be between 1,000 and 10,000,000 | The iterations field holds an impossible value |
GCM does not distinguish a wrong password from tampered data: either way the tag does not match and nothing is output. Unauthenticated modes such as CBC can return garbage instead of an error.
Strings this tool cannot open
The format here is fixed: Base64( salt[16] ‖ iv[12] ‖ ciphertext ‖ tag[16] ) for passwords, Base64( iv[12] ‖ ciphertext ‖ tag[16] ) for raw keys, AES-GCM, PBKDF2-HMAC-SHA256 with 310,000 iterations. Other tools write other formats, and the first characters often give them away:
| Starts with | Made by | Decrypt with |
|---|---|---|
U2FsdGVkX1 | openssl enc or CryptoJS with a passphrase (Salted__ header) | openssl enc -d -a with the original cipher and KDF options |
gAAAAA | a Fernet token (Python cryptography) | Fernet(key).decrypt(token) |
-----BEGIN PGP MESSAGE----- | OpenPGP | gpg --decrypt |
-----BEGIN AGE ENCRYPTED FILE----- | age | age --decrypt |
More recognizable openings are collected in Common Base64 prefixes.
Decrypting in Node.js and Python
The same format works without a browser. In Node.js:
const crypto = require("crypto");
function decrypt(b64, password) {
const data = Buffer.from(b64, "base64");
const salt = data.subarray(0, 16), iv = data.subarray(16, 28);
const tag = data.subarray(data.length - 16), ct = data.subarray(28, data.length - 16);
const key = crypto.pbkdf2Sync(password, salt, 310000, 32, "sha256");
const d = crypto.createDecipheriv("aes-256-gcm", key, iv);
d.setAuthTag(tag);
return Buffer.concat([d.update(ct), d.final()]).toString("utf8");
}
function encrypt(text, password) {
const salt = crypto.randomBytes(16), iv = crypto.randomBytes(12);
const key = crypto.pbkdf2Sync(password, salt, 310000, 32, "sha256");
const c = crypto.createCipheriv("aes-256-gcm", key, iv);
const ct = Buffer.concat([c.update(text, "utf8"), c.final()]);
return Buffer.concat([salt, iv, ct, c.getAuthTag()]).toString("base64");
}
For a raw-key string in Python, skip the salt and PBKDF2 and start the IV at byte 0:
import base64
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def decrypt_raw(b64, key_b64):
data = base64.b64decode(b64)
iv, ct = data[:12], data[12:]
return AESGCM(base64.b64decode(key_b64)).decrypt(iv, ct, None).decode("utf-8")
The password version for Python is on the AES encrypt page. Encryption of this kind is meant for passing a secret from one person to another; once it has arrived, move it into a password manager or secrets manager instead of keeping the ciphertext around.
FAQ
Can I decrypt Base64 without a password?
If it is only Base64, yes, because there is nothing to decrypt: paste it into the Base64 decoder. If it is AES ciphertext in Base64, no. Without the password or key the bytes cannot be recovered, and no tool can guess a strong password.
Why does it say wrong password when the password is right?
Check the PBKDF2 iterations first; they are not stored in the ciphertext, so a sender who changed them must tell you the number. Also make sure the string was copied completely and that it really comes from this tool’s format.
Can this tool decrypt text from OpenSSL or CryptoJS?
No. Strings starting with U2FsdGVkX1 carry OpenSSL’s Salted__ header and use a different key derivation and usually CBC mode. Decrypt them with openssl enc -d and the same options that encrypted them.
What happens to the decrypted text?
It is shown on the page and nowhere else. Decryption runs in your browser with WebCrypto, and the page’s Content Security Policy blocks every network request except Cloudflare’s page-view count, so neither the password nor the message is sent.