UUID Studio

AES-GCM Decrypt

Decrypt AES-GCM envelopes produced by this tool.

  • 🔒 No data stored or uploaded
  • âš¡ 100% client-side
  • 🆓 Free, no account

Need more than one tool at a time? Open the full Workbench - or press Ctrl+K to jump to any tool.

Hash, HMAC, AES-GCM/CBC + RSA-OAEP, codecs, JWT decoding, UUID v4, and secure random - all client-side. JWTs use Base64URL (three segments), not a single MIME Base64 block - use Decode JWT below, not raw Base64 decode.

About AES-GCM Decrypt

AES is a symmetric block cipher: the same key decrypts, and anyone holding the key can reverse the operation. AES itself only transforms one 128-bit block, so the mode of operation - here AES-GCM - is what determines how a message of any length is handled, and it matters far more to the security of the result than the key size does.

AES-GCM is an authenticated mode: a single operation both encrypts the data and produces a 128-bit authentication tag. Decryption verifies that tag first and fails outright if the ciphertext, the nonce or the associated data has been altered, so tampering is detected rather than silently decrypted into garbage.

This is the mode to choose for new work. It needs no separate MAC, no padding, and it is hardware-accelerated on essentially every modern CPU. TLS 1.3 dropped every non-authenticated mode and kept AEAD modes like this one.

Its one sharp edge is nonce reuse. Encrypting two different messages with the same key and the same nonce does not merely leak that the messages relate - it leaks the XOR of the plaintexts and, worse, allows an attacker to recover the authentication subkey and forge tags for that key. Generate a fresh random nonce for every single message and never derive one from a counter you might reset.

Everything on this page happens in your browser through the Web Crypto API. Keys, plaintext and ciphertext are never uploaded, which is what makes it safe to decrypt a real value here while you are debugging.

How to use the AES-GCM Decrypt

  1. Paste the ciphertext, usually Base64 or hex - make sure you know which, because decoding it the wrong way is the most common cause of failure.
  2. Provide the key exactly as bytes. If your key is Base64 or hex, decode it rather than pasting the printable text.
  3. Supply the nonce (12 bytes / 96 bits). It is not secret and is usually prepended to the ciphertext or sent alongside it.
  4. Supply the 16-byte authentication tag. Some libraries append it to the ciphertext, others return it separately - if decryption fails, this split is the first thing to check.

AES-GCM Decrypt in code

The same operation this tool performs, in the languages you are most likely to need it.

JavaScript (browser / Web Crypto)
const enc = new TextEncoder();
const key = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]
);
// A fresh 12-byte nonce per message. Never reuse one with the same key.
const iv = crypto.getRandomValues(new Uint8Array(12));

const ct = await crypto.subtle.encrypt(
  { name: "AES-GCM", iv }, key, enc.encode("secret message")
);
// Web Crypto appends the 16-byte auth tag to the ciphertext for you.

const pt = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ct);
console.log(new TextDecoder().decode(pt));
Node.js
import { randomBytes, createCipheriv, createDecipheriv } from "node:crypto";

const key = randomBytes(32);            // AES-256
const iv = randomBytes(12);             // 96-bit nonce, fresh every time

const c = createCipheriv("aes-256-gcm", key, iv);
const ct = Buffer.concat([c.update("secret message", "utf8"), c.final()]);
const tag = c.getAuthTag();             // 16 bytes, returned separately

const d = createDecipheriv("aes-256-gcm", key, iv);
d.setAuthTag(tag);                      // MUST be set before final()
const pt = Buffer.concat([d.update(ct), d.final()]);
console.log(pt.toString("utf8"));
// d.final() throws if the tag doesn't verify - let it throw, don't catch
// and continue with whatever update() returned.
Python (cryptography)
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

key = AESGCM.generate_key(bit_length=256)
aead = AESGCM(key)
nonce = os.urandom(12)                  # fresh per message

ct = aead.encrypt(nonce, b"secret message", None)   # tag is appended
pt = aead.decrypt(nonce, ct, None)      # raises InvalidTag if modified
print(pt)
Java
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;

byte[] keyBytes = new byte[32], iv = new byte[12];
SecureRandom rng = new SecureRandom();
rng.nextBytes(keyBytes); rng.nextBytes(iv);

SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
c.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv));
byte[] ct = c.doFinal("secret message".getBytes("UTF-8"));
// ct already includes the 128-bit tag.

Cipher d = Cipher.getInstance("AES/GCM/NoPadding");
d.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
System.out.println(new String(d.doFinal(ct), "UTF-8"));
OpenSSL
# openssl enc does NOT support GCM properly - it cannot emit or check
# the auth tag. Use a library, or the newer -aead interface. This is a
# common source of "my GCM ciphertext won't decrypt" reports.
openssl list -cipher-algorithms | grep -i gcm

When you need this

  • Decrypting a value from a database or log to confirm what was actually stored.
  • Diagnosing why a ciphertext from another system will not open in yours.
  • Working out whether a ciphertext blob is Base64 or hex, and where the nonce and tag sit inside it.
  • Checking whether a decryption failure is the key, the nonce, or the encoding.
  • Recovering data encrypted by a system you no longer run.

Common problems and what causes them

"Unsupported state or unable to authenticate data" / InvalidTag / BadPaddingException
One of the key, nonce, tag or associated data is wrong, or the tag was not split off the ciphertext correctly. Web Crypto and Python append the 16-byte tag to the ciphertext; Node returns it separately via getAuthTag(). Mixing those two conventions is the single most common cause.
Key length is wrong
AES keys must be exactly 16, 24 or 32 bytes. A 32-character hex string is 16 bytes, not 32 - decode hex before use. Passing a password directly as a key is also wrong: derive a key with PBKDF2, scrypt or Argon2 first.
Losing the nonce
The nonce is not secret but it is required to decrypt. Store or transmit it with the ciphertext - the usual convention is to prepend its 12 bytes. Without it the ciphertext is unrecoverable.
Reusing a nonce with the same key
This breaks GCM completely: it leaks the XOR of the two plaintexts and lets an attacker recover the authentication subkey and forge valid tags. Use a fresh random 12-byte nonce per message, and if you use a counter, make sure it can never restart after a redeploy or restore.
Base64 vs hex vs raw bytes
The same ciphertext looks completely different in each encoding, and Base64 has URL-safe and standard variants that differ in two characters. Confirm which encoding each side uses before concluding the crypto is broken.
Using "AES" as the algorithm name in Java or .NET
Cipher.getInstance("AES") means AES/ECB/PKCS5Padding. ECB encrypts identical plaintext blocks to identical ciphertext blocks, which leaks structure badly. Always name the mode explicitly.

FAQ

Why does my AES-GCM decryption fail?
Work through it in this order: the tag was not split off the ciphertext correctly (Web Crypto and Python append it, Node returns it separately from getAuthTag - mixing those conventions is the most common cause); the nonce is wrong or was not stored; additional authenticated data differs; the key is wrong; or the ciphertext was re-encoded in transport.
What does an authentication failure actually mean?
That the ciphertext, nonce, tag or associated data does not match what was authenticated at encryption time. It may mean tampering, but far more often it means a configuration mismatch. Either way, do not use the output - GCM's whole value is that it refuses to hand you unverified plaintext.
Should I use AES-GCM or AES-CBC?
AES-GCM for anything new: it authenticates as well as encrypts, needs no padding, and is faster on modern hardware. Choose CBC only when an existing format or API requires it, and then always add an HMAC over the IV and ciphertext and verify it before decrypting.
Is the nonce secret?
No. The nonce is public and is normally stored or sent alongside the ciphertext. What matters is that it is never repeated for a given key.
Is AES-128 strong enough, or should I use AES-256?
AES-128 has no practical attack and is fine for essentially all use. AES-256 gives a larger margin against future advances at a small performance cost. Both are dramatically safer than getting the mode or nonce handling wrong, which is where real failures come from.
Can I use a password as the key?
Not directly. Keys must be exactly 16, 24 or 32 random bytes. Run the password through a key derivation function - PBKDF2, scrypt or Argon2, with a random salt and a high iteration or cost parameter - and use its output as the key.
Why does the same plaintext give different ciphertext each time?
Because a fresh random nonce is used per operation, and that is exactly what you want. Identical ciphertext for identical plaintext would tell an attacker which messages repeat.
Is my key or plaintext sent to a server?
No. All AES operations here run in your browser via the Web Crypto API. Nothing is uploaded or logged, so you can safely test with a real value - check your network tab, or use the page offline.

Related reading