AES Encryption Explained: How to Encrypt and Decrypt Data Securely
This guide has a free tool → Open AES Encryption Tool
AES Encryption Explained: How to Encrypt and Decrypt Data Securely
AES (Advanced Encryption Standard) is the most widely deployed symmetric encryption algorithm in the world. It protects HTTPS connections, VPN tunnels, encrypted hard drives, password managers, messaging apps, and countless other systems. Understanding how AES works - and how to use it correctly - is essential knowledge for any developer handling sensitive data.
This guide covers the AES specification, key sizes and modes of operation, proper key derivation, authenticated encryption, common mistakes that break security, and practical code examples in multiple languages.
---
What Is AES?
AES is a symmetric block cipher - "symmetric" means the same key is used for both encryption and decryption, and "block cipher" means it processes data in fixed-size chunks (blocks) rather than bit by bit.
The algorithm was originally developed by Belgian cryptographers Joan Daemen and Vincent Rijmen and submitted under the name "Rijndael" to a public competition run by the U.S. National Institute of Standards and Technology (NIST). After five years of analysis and public scrutiny, NIST selected it as the Advanced Encryption Standard in 2001, replacing the older DES (Data Encryption Standard).
AES is used in:
- TLS/HTTPS connections (AES-256-GCM is a mandatory cipher in TLS 1.3)
- WPA2 and WPA3 Wi-Fi encryption
- BitLocker (Windows disk encryption)
- FileVault (macOS disk encryption)
- Signal, WhatsApp, and iMessage (message encryption)
- AWS KMS, HashiCorp Vault, and other key management systems
- ZIP, 7-Zip, and PDF encryption
---
How AES Works Internally
AES operates on a 4x4 matrix of bytes called the state. It processes plaintext in 128-bit (16-byte) blocks through multiple rounds of mathematical transformations.
The AES State
The 16 bytes of a block are arranged in a 4x4 grid:
b0 b4 b8 b12
b1 b5 b9 b13
b2 b6 b10 b14
b3 b7 b11 b15Each round of AES applies four transformations to this state:
1. SubBytes
Each byte in the state is replaced by a value from a 256-entry lookup table called the S-box (substitution box). The S-box is constructed using modular arithmetic in the Galois Field GF(2^8), which makes AES resistant to linear and differential cryptanalysis.
Before: [0x53] -> After: [0xed] (S-box lookup)2. ShiftRows
Each row in the 4x4 state matrix is cyclically shifted by a different offset:
- Row 0: no shift
- Row 1: shift left by 1
- Row 2: shift left by 2
- Row 3: shift left by 3
This spreads bytes from each column across multiple columns, providing diffusion.
3. MixColumns
Each column of the state is treated as a polynomial and multiplied by a fixed matrix polynomial in GF(2^8). This mixes the four bytes of each column, further spreading changes throughout the state.
4. AddRoundKey
The state is XORed with a 128-bit round key derived from the original key through a process called key schedule expansion.
The final round omits MixColumns. The number of rounds depends on the key size.
---
Key Sizes: AES-128, AES-192, AES-256
| Variant | Key Size | Number of Rounds | Security Level |
|---|---|---|---|
| AES-128 | 128 bits (16 bytes) | 10 | Excellent - no practical attack known |
| AES-192 | 192 bits (24 bytes) | 12 | Very strong - rarely used in practice |
| AES-256 | 256 bits (32 bytes) | 14 | Strongest - required for classified data |
AES-128 provides 2^128 possible keys. Even using all computing power on Earth, brute-forcing it would take longer than the age of the universe. It is completely secure for civilian use.
AES-256 provides 2^256 possible keys - an astronomically larger keyspace. It is required by U.S. government standards (NIST, NSA Suite B) for TOP SECRET classification and is the recommended default for new applications.
The performance difference between AES-128 and AES-256 is approximately 40% more computation. On modern hardware with AES-NI (hardware acceleration), both are fast enough that the difference is negligible for most applications. Use AES-256 as your default.
---
Modes of Operation
AES is a block cipher - it encrypts exactly 16 bytes at a time. Real data is almost never exactly 16 bytes. Modes of operation define how to apply AES repeatedly to encrypt data of arbitrary length.
ECB (Electronic Codebook) - Never Use This
ECB is the simplest mode: encrypt each 16-byte block independently with the same key.
Block 1 -> AES(key, block1) -> Ciphertext1
Block 2 -> AES(key, block2) -> Ciphertext2
Block 3 -> AES(key, block3) -> Ciphertext3Critical flaw: Identical plaintext blocks produce identical ciphertext blocks. This leaks structural information about the plaintext. The canonical demonstration is the "ECB penguin" - encrypting a bitmap image with ECB preserves the visual outline of the image in the ciphertext.
Never use ECB mode. It is considered cryptographically broken for any real-world use.
CBC (Cipher Block Chaining) - The Traditional Choice
CBC XORs each plaintext block with the previous ciphertext block before encryption:
C0 = IV (Initialization Vector - random 16 bytes)
C1 = AES_Encrypt(key, P1 XOR C0)
C2 = AES_Encrypt(key, P2 XOR C1)
C3 = AES_Encrypt(key, P3 XOR C2)The Initialization Vector (IV) is a random 16-byte value that ensures two encryptions of the same plaintext produce different ciphertext, even with the same key.
Decryption:
P1 = AES_Decrypt(key, C1) XOR C0
P2 = AES_Decrypt(key, C2) XOR C1Properties of CBC:
- Each ciphertext block depends on all preceding plaintext blocks
- A single-bit change in plaintext changes all subsequent ciphertext blocks
- Requires padding to make data a multiple of 16 bytes (PKCS#7 padding is standard)
- Does not provide authentication - an attacker can modify ciphertext without being detected
- Decryption can be parallelized; encryption cannot
CBC's weakness: Without authentication, CBC is vulnerable to padding oracle attacks (POODLE, BEAST, Lucky Thirteen). If you use CBC, you must also authenticate the ciphertext with an HMAC.
CTR (Counter Mode)
CTR mode turns AES into a stream cipher. A counter value (nonce + counter) is encrypted with the key to produce a keystream, which is then XORed with the plaintext:
Keystream1 = AES_Encrypt(key, nonce || counter1)
Ciphertext1 = Plaintext1 XOR Keystream1Properties:
- Encryption and decryption are identical operations (XOR with keystream)
- Fully parallelizable - any block can be decrypted independently
- No padding needed
- Does not provide authentication on its own
GCM (Galois/Counter Mode) - The Modern Standard
GCM combines CTR-mode encryption with a Galois field-based message authentication code (GMAC). This provides Authenticated Encryption with Associated Data (AEAD).
Ciphertext = CTR_Encrypt(key, IV, plaintext)
Tag = GMAC(key, IV, ciphertext, associated_data)Properties of GCM:
- Provides both confidentiality (the ciphertext hides the plaintext) and integrity/authenticity (the authentication tag detects any tampering)
- Produces a 128-bit authentication tag alongside the ciphertext
- Supports Additional Authenticated Data (AAD) - data that is authenticated but not encrypted (useful for headers, metadata)
- Fully parallelizable
- Nonce must be unique for each encryption - reusing a nonce with GCM catastrophically breaks security
GCM is the recommended mode for new applications. It is the cipher suite used in TLS 1.3 (TLS_AES_256_GCM_SHA384), SSH, and most modern security protocols.
Mode Comparison
| Mode | Authentication | Parallel Encrypt | Parallel Decrypt | Padding Needed | Use Case |
|---|---|---|---|---|---|
| ECB | No | Yes | Yes | Yes | Never use |
| CBC | No (add HMAC) | No | Yes | Yes | Legacy systems |
| CTR | No (add HMAC) | Yes | Yes | No | Streaming data |
| GCM | Yes (built-in) | Yes | Yes | No | New applications |
---
The IV and Nonce: What They Are and How to Handle Them
Both CBC and GCM require a unique random value for each encryption operation:
- CBC uses an IV (Initialization Vector): 16 bytes, random, must be unique per message
- GCM uses a nonce (number used once): 12 bytes is the recommended size, must be unique per key
The IV and nonce are not secret. They must be transmitted alongside the ciphertext so the decryptor can use them. Typically, they are prepended to the ciphertext:
[12-byte nonce][ciphertext][16-byte authentication tag]The critical rule: Never reuse a nonce with the same key.
For GCM, nonce reuse is catastrophic. If two different messages are encrypted with the same key and nonce, an attacker can XOR the ciphertexts together to recover the XOR of the plaintexts, and can forge authentication tags. This completely breaks the encryption.
Generate a fresh cryptographically random nonce for every encryption operation:
// Correct - new random nonce every time
const nonce = crypto.getRandomValues(new Uint8Array(12));---
Keys vs Passwords: Key Derivation
AES requires a key of exactly 128, 192, or 256 bits. A human-chosen password is not a valid AES key - passwords are short, contain patterns, and occupy a tiny fraction of the possible key space.
To derive a cryptographically strong key from a password, use a Key Derivation Function (KDF) designed for this purpose.
PBKDF2 (Password-Based Key Derivation Function 2)
PBKDF2 applies a pseudorandom function (typically HMAC-SHA256) many times to a password and salt. The iteration count makes brute-force attacks slower.
// Browser Web Crypto API
const encoder = new TextEncoder();
const password = "my-password";
const salt = crypto.getRandomValues(new Uint8Array(16)); // random 16-byte salt
const keyMaterial = await crypto.subtle.importKey(
"raw",
encoder.encode(password),
"PBKDF2",
false,
["deriveKey"]
);
const key = await crypto.subtle.deriveKey(
{
name: "PBKDF2",
hash: "SHA-256",
salt: salt,
iterations: 600000 // NIST recommends 600,000+ iterations for SHA-256
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
false, // Not extractable
["encrypt", "decrypt"]
);PBKDF2 parameters:
- Salt: random 16 bytes, stored with the ciphertext - prevents precomputed dictionary attacks
- Iterations: higher is slower and more secure - 600,000 for SHA-256 is the current NIST recommendation
- Hash: SHA-256 or SHA-512
Argon2
Argon2 is the winner of the 2015 Password Hashing Competition. It is more resistant to GPU attacks than PBKDF2 because it requires configurable amounts of memory, which limits parallelism on commodity hardware.
Three variants:
- Argon2d: optimized against GPU attacks, not resistant to side-channel attacks
- Argon2i: optimized for password hashing, resistant to side-channel attacks
- Argon2id: hybrid, recommended for general use
Argon2 is not available natively in the browser Web Crypto API. Use it in server-side environments via Node.js packages like argon2 or @node-rs/argon2.
// Node.js with argon2 package
const argon2 = require('argon2');
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 64 * 1024, // 64 MB
timeCost: 3, // 3 iterations
parallelism: 1
});scrypt
scrypt is a memory-hard KDF designed to be expensive in both time and memory, making it resistant to both ASIC and GPU attacks.
// Node.js built-in crypto
const { scrypt } = require('crypto');
const { promisify } = require('util');
const scryptAsync = promisify(scrypt);
const salt = crypto.randomBytes(16);
const key = await scryptAsync(password, salt, 32, {
N: 131072, // CPU/memory cost factor - must be power of 2
r: 8, // Block size
p: 1 // Parallelization factor
});Which KDF to Use
| KDF | Recommended For | Memory Hard | Browser API |
|---|---|---|---|
| PBKDF2 | Browser-based, universal compatibility | No | Yes (Web Crypto) |
| Argon2id | Server-side, highest security | Yes | No (Node.js package) |
| scrypt | Server-side, good alternative | Yes | No (Node.js built-in) |
| bcrypt | Password storage only (not for AES key derivation) | Somewhat | No |
For browser-based encryption (like the ToolBox AES tool), PBKDF2 with 600,000+ iterations and SHA-256 is the appropriate choice because it is available natively. For server-side applications, prefer Argon2id.
---
Complete AES-256-GCM Implementation
JavaScript (Browser - Web Crypto API)
const AES = {
// Encode string to Uint8Array
encode: (str) => new TextEncoder().encode(str),
decode: (buf) => new TextDecoder().decode(buf),
// Convert Uint8Array to hex string
toHex: (buf) => Array.from(buf)
.map(b => b.toString(16).padStart(2, '0'))
.join(''),
// Convert hex string to Uint8Array
fromHex: (hex) => new Uint8Array(
hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))
),
// Convert Uint8Array to base64
toBase64: (buf) => btoa(String.fromCharCode(...buf)),
// Convert base64 to Uint8Array
fromBase64: (b64) => Uint8Array.from(atob(b64), c => c.charCodeAt(0)),
// Derive an AES-256-GCM key from a password
async deriveKey(password, salt) {
const keyMaterial = await crypto.subtle.importKey(
'raw',
this.encode(password),
'PBKDF2',
false,
['deriveKey']
);
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
hash: 'SHA-256',
salt,
iterations: 600000
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
},
// Encrypt plaintext with a password
// Returns base64-encoded: salt(16) + iv(12) + ciphertext + tag(16)
async encrypt(plaintext, password) {
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12));
const key = await this.deriveKey(password, salt);
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
this.encode(plaintext)
);
// Combine: salt + iv + ciphertext (includes 16-byte tag appended by GCM)
const combined = new Uint8Array(
salt.byteLength + iv.byteLength + ciphertext.byteLength
);
combined.set(salt, 0);
combined.set(iv, salt.byteLength);
combined.set(new Uint8Array(ciphertext), salt.byteLength + iv.byteLength);
return this.toBase64(combined);
},
// Decrypt base64-encoded ciphertext with a password
async decrypt(encodedData, password) {
const data = this.fromBase64(encodedData);
const salt = data.slice(0, 16);
const iv = data.slice(16, 28);
const ciphertext = data.slice(28);
const key = await this.deriveKey(password, salt);
const plaintext = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
ciphertext
);
return this.decode(plaintext);
}
};
// Usage
const encrypted = await AES.encrypt("Secret message", "my-password");
console.log("Encrypted:", encrypted);
const decrypted = await AES.decrypt(encrypted, "my-password");
console.log("Decrypted:", decrypted); // "Secret message"Node.js (Built-in Crypto Module)
const crypto = require('crypto');
function encrypt(plaintext, password) {
const salt = crypto.randomBytes(16);
const iv = crypto.randomBytes(12);
// Derive key using scrypt
const key = crypto.scryptSync(password, salt, 32, {
N: 131072,
r: 8,
p: 1
});
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const encrypted = Buffer.concat([
cipher.update(plaintext, 'utf8'),
cipher.final()
]);
const tag = cipher.getAuthTag(); // 16-byte authentication tag
// Return: salt(16) + iv(12) + tag(16) + ciphertext
return Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
}
function decrypt(encodedData, password) {
const data = Buffer.from(encodedData, 'base64');
const salt = data.slice(0, 16);
const iv = data.slice(16, 28);
const tag = data.slice(28, 44);
const ciphertext = data.slice(44);
const key = crypto.scryptSync(password, salt, 32, {
N: 131072,
r: 8,
p: 1
});
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
return Buffer.concat([
decipher.update(ciphertext),
decipher.final()
]).toString('utf8');
}
// Usage
const encrypted = encrypt("Secret message", "my-password");
console.log("Encrypted:", encrypted);
const decrypted = decrypt(encrypted, "my-password");
console.log("Decrypted:", decrypted);Python
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
from cryptography.hazmat.backends import default_backend
import os
import base64
def derive_key(password: str, salt: bytes) -> bytes:
"""Derive a 256-bit key from a password using scrypt."""
kdf = Scrypt(
salt=salt,
length=32,
n=2**17, # 131072 - CPU/memory cost
r=8,
p=1,
backend=default_backend()
)
return kdf.derive(password.encode())
def encrypt(plaintext: str, password: str) -> str:
"""Encrypt plaintext and return base64-encoded result."""
salt = os.urandom(16)
nonce = os.urandom(12)
key = derive_key(password, salt)
aesgcm = AESGCM(key)
# AESGCM.encrypt appends the 16-byte tag to the ciphertext automatically
ciphertext = aesgcm.encrypt(nonce, plaintext.encode(), None)
# Combine: salt + nonce + ciphertext (with tag)
combined = salt + nonce + ciphertext
return base64.b64encode(combined).decode()
def decrypt(encoded_data: str, password: str) -> str:
"""Decrypt base64-encoded ciphertext and return plaintext."""
data = base64.b64decode(encoded_data)
salt = data[:16]
nonce = data[16:28]
ciphertext = data[28:]
key = derive_key(password, salt)
aesgcm = AESGCM(key)
plaintext = aesgcm.decrypt(nonce, ciphertext, None)
return plaintext.decode()
# Usage
encrypted = encrypt("Secret message", "my-password")
print("Encrypted:", encrypted)
decrypted = decrypt(encrypted, "my-password")
print("Decrypted:", decrypted)Go
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"golang.org/x/crypto/pbkdf2"
)
func deriveKey(password, salt []byte) []byte {
return pbkdf2.Key(password, salt, 600000, 32, sha256.New)
}
func encrypt(plaintext, password string) (string, error) {
salt := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
return "", err
}
nonce := make([]byte, 12)
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
key := deriveKey([]byte(password), salt)
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
// Seal appends the authentication tag to the ciphertext
ciphertext := gcm.Seal(nil, nonce, []byte(plaintext), nil)
combined := append(salt, nonce...)
combined = append(combined, ciphertext...)
return base64.StdEncoding.EncodeToString(combined), nil
}
func decrypt(encoded, password string) (string, error) {
data, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
return "", err
}
salt := data[:16]
nonce := data[16:28]
ciphertext := data[28:]
key := deriveKey([]byte(password), salt)
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plaintext), nil
}
func main() {
encrypted, _ := encrypt("Secret message", "my-password")
fmt.Println("Encrypted:", encrypted)
decrypted, _ := decrypt(encrypted, "my-password")
fmt.Println("Decrypted:", decrypted)
}---
Common Mistakes That Break Security
Reusing the Same IV or Nonce
For CBC: reusing an IV with the same key leaks information about whether plaintext messages start with the same bytes. For GCM: reusing a nonce with the same key completely breaks both confidentiality and authentication. An attacker who observes two messages encrypted with the same key and nonce can recover both plaintexts.
// Wrong - hard-coded IV
const iv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
// Wrong - counter-based IV starting from 0 and never resetting
let counter = 0;
const iv = new Uint8Array(counter++);
// Right - random IV/nonce per encryption
const iv = crypto.getRandomValues(new Uint8Array(12));Using CBC Without Authentication
CBC mode does not provide integrity or authentication. An attacker who can modify the ciphertext can cause the decryption to produce different (attacker-chosen) plaintext, and padding oracle attacks can decrypt messages without knowing the key.
If you must use CBC, apply an HMAC-SHA256 over the IV and ciphertext and verify it before decrypting:
// Encrypt-then-MAC pattern with CBC
// 1. Encrypt with AES-CBC
// 2. Compute HMAC-SHA256 over (IV || ciphertext)
// 3. Transmit IV || HMAC || ciphertext
// 4. On decryption, verify HMAC first, then decryptOr better: switch to AES-GCM which provides authentication natively.
Weak Key Derivation
Never use a password directly as an AES key:
// Wrong - trivially bruteforceable
const key = new TextEncoder().encode("my-password").slice(0, 32);
// Wrong - SHA-256 of password is too fast to compute
const key = await crypto.subtle.digest('SHA-256', new TextEncoder().encode("my-password"));
// Right - PBKDF2 with high iteration count
const key = await deriveKey(password, salt); // as shown aboveUsing a weak KDF means an attacker who obtains the ciphertext can attempt billions of password guesses per second, trivially cracking common passwords.
Storing Keys in Source Code
// Never do this
const ENCRYPTION_KEY = "hardcoded-key-in-source-code-bad";
// Never do this either
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY; // still in the codebaseEncryption keys should be:
- Generated randomly, not chosen by humans
- Stored in a dedicated key management system (AWS KMS, Google Cloud KMS, HashiCorp Vault)
- Rotated on a schedule
- Never committed to version control
Ignoring the Authentication Tag in GCM
GCM produces a 16-byte authentication tag. If you do not verify this tag during decryption, you lose the integrity guarantee and become vulnerable to tampering.
Most cryptographic libraries handle this automatically - if the tag is invalid, decrypt throws an exception. But if you are working with raw bytes, verify the tag explicitly before using the decrypted data.
// The Web Crypto API throws if the tag is invalid - this is safe:
try {
const plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext);
// Tag was valid
} catch (e) {
// Tag was invalid - ciphertext was tampered with
console.error("Decryption failed - data may have been tampered with");
}---
When to Use AES (and When Not To)
Use AES Encryption For:
- Encrypting sensitive data stored in a database (credit card numbers, health records, PII)
- Encrypting files at rest on disk
- Encrypting data in transit when you control both endpoints
- Protecting configuration files containing credentials
- Client-side encryption before uploading files to cloud storage
- Implementing end-to-end encryption in messaging applications
Do Not Use AES For:
- Storing passwords - use bcrypt, Argon2, or scrypt instead. These are one-way functions; AES is reversible.
- Data integrity without encryption - use HMAC-SHA256 instead
- Asymmetric scenarios - if the sender and receiver do not share a secret, use RSA or elliptic curve cryptography to exchange a key, then use AES for the data
- TLS - let your HTTP server and the TLS library handle this; do not implement TLS yourself
---
AES and the GDPR / Compliance Perspective
Under GDPR and similar regulations, encrypted data that has been pseudonymized or anonymized is treated differently from plaintext personal data. Encrypting personal data at rest using AES-256 with proper key management:
- Satisfies the "appropriate technical measures" requirement (Article 25, 32)
- Means a data breach involving only the ciphertext (without the keys) may not require mandatory notification if the data cannot be decrypted
- Enables data portability - the data can be exported in encrypted form
Key management is critical: if the encryption keys are stored alongside the data, encryption provides little legal or practical protection.
---
Using the ToolBox AES Tool
The AES Encryption Tool lets you encrypt and decrypt text directly in your browser:
- Enter the plaintext you want to encrypt in the input field
- Enter a password or passphrase
- Choose AES-256 encryption
- Click Encrypt to see the ciphertext (base64 encoded)
- To decrypt, paste the ciphertext, enter the same password, and click Decrypt
How it works under the hood:
- Uses the Web Crypto API - the same API used by browsers for HTTPS
- Derives a key from your password using PBKDF2 with SHA-256 and a random salt
- Encrypts using AES-256-GCM with a random 12-byte nonce
- The output includes the salt, nonce, and authentication tag alongside the ciphertext
- Nothing is sent to a server - all processing happens in your browser
This is appropriate for encrypting sensitive text that you want to share securely or store locally. For production applications handling critical data, use a battle-tested encryption library within your server-side code with proper key management.
---
Related Tools
- AES Encryption Tool - Encrypt and decrypt text in the browser
- Hash Generator - Generate SHA-256, SHA-512, MD5 and other hashes
- JWT Decoder - Decode and inspect JSON Web Tokens
- Base64 - Encode and decode Base64 (used for representing binary data as text)
- Password Generator - Generate cryptographically strong random passwords
- UUID Generator - Generate UUIDs (version 4 uses cryptographic randomness)
- SSL Certificate Checker - Inspect TLS certificates that use AES encryption
- HTTP Request Builder - Test APIs that return encrypted payloads
---
Try It Now
Encrypt and decrypt data with AES-256 instantly in your browser:
AES Encryption Tool - Free, private, no signup required. Your data and keys never leave your browser.
Related Tools
Free, private, no signup required
You might also like
Want higher limits, batch processing, and AI tools?