AES key generator
AES-128, AES-192 and AES-256 keys in hex, generated in your browser, with the nonce rules spelled out.
Three key sizes, three round counts
AES is Rijndael with the parameters fixed by FIPS 197: a block size of 128 bits and a key of 128, 192 or 256 bits. The key size changes the number of rounds the cipher performs, from 10 at AES-128 to 14 at AES-256, and it changes nothing else about the structure.
The block size is the part most often misread. AES-256 does not encrypt 256-bit blocks. It encrypts 128-bit blocks with a 256-bit key, exactly like AES-128 does with a 128-bit key. Every AES variant has a 16-byte block, which is why ciphertext length behaves identically across all three.
The extra rounds cost throughput. AES-256 runs 40% more rounds per block than AES-128, and on hardware with AES-NI the measured difference in practice lands in that neighbourhood. For most applications the cipher is not the bottleneck and the choice is free.
One curiosity worth knowing so it does not surprise you in a security review: there are published related-key attacks against the AES-256 key schedule that do not exist for AES-128. They require the attacker to control relationships between multiple keys, which never happens when keys are independent and randomly generated, which is the only case this tool produces.
| Variant | Key bits | Key bytes | Hex characters | Rounds | Block size |
|---|---|---|---|---|---|
| AES-128 | 128 | 16 | 32 | 10 | 128 bits |
| AES-192 | 192 | 24 | 48 | 12 | 128 bits |
| AES-256 | 256 | 32 | 64 | 14 | 128 bits |
The IV is not the key
An initialisation vector, or a nonce in the counter-based modes, is a separate value from the key. It is generated fresh for every encryption, it is stored or transmitted in the clear alongside the ciphertext, and the recipient needs it to decrypt. It is not secret and keeping it secret buys nothing.
Its job is to make the same plaintext encrypt to different ciphertext each time. Without it, encrypting the same message twice under the same key produces identical output, and an observer learns that the two messages are the same without breaking anything.
The requirement differs by mode, and the difference is not decorative. In CBC the IV must be unpredictable, which in practice means random, because a predictable IV enables a chosen-plaintext attack. In CTR and GCM the requirement is uniqueness rather than unpredictability: a counter is perfectly acceptable, as long as it never repeats for a given key.
For GCM, NIST SP 800-38D recommends a 96-bit nonce, which is 12 bytes. Other lengths are permitted but are first compressed through GHASH, which costs performance and gives up the efficient construction the recommended size exists to enable. Use 12 bytes.
Reusing a GCM nonce destroys the key
This is the single most important thing on this page, and it is more consequential than any choice of key size. GCM is counter mode for confidentiality plus GHASH for authentication. Counter mode turns the block cipher into a keystream generator, and the nonce is what makes the keystream different each time.
Encrypt two different messages under the same key and the same nonce and both get the identical keystream. Exclusive-or the two ciphertexts and the keystream cancels, leaving the exclusive-or of the two plaintexts. That is a two-time pad, and it is recoverable with no key material at all.
The authentication break is worse and is the part people miss. A nonce repeat also leaks the GHASH authentication subkey, and once an attacker has that they can forge a valid authentication tag for any message under that key. The confidentiality damage is limited to the two messages that shared a nonce; the authenticity damage is total and permanent for the key. This is the attack usually called the forbidden attack, and the correct response to a suspected nonce repeat is to retire the key.
There are two safe ways to produce nonces. Use a counter when you have a single writer with durable state that survives restarts and cannot be rolled back by a snapshot restore. Use 96 random bits when you do not, accepting the birthday bound: NIST SP 800-38D limits a key to 2 to the power 32 invocations when nonces are generated randomly, which is where the collision probability becomes non-negligible. If you can guarantee neither, choose a construction built for it. XChaCha20-Poly1305 has a 192-bit nonce, which makes random nonces safe at any realistic volume, and AES-GCM-SIV in RFC 8452 is designed so that a repeated nonce leaks only whether two plaintexts were identical.
A virtual machine snapshot restored twice is the classic way a counter repeats without anyone noticing. If your nonce state can be rolled back, treat it as random-nonce territory rather than counter territory.
Choosing a mode, briefly
If you are picking a mode you almost certainly want an authenticated one. AES-GCM where AES-NI is available and nonce management is under control, ChaCha20-Poly1305 where it is not, since it is fast in software and takes the same 32-byte key.
ECB is not a mode you should use for anything. Identical plaintext blocks produce identical ciphertext blocks, so the structure of the data survives encryption in a way that is visible to the naked eye on an image. CBC without a separate message authentication code is unauthenticated, which means an attacker can alter the ciphertext and the decryption will hand your application the altered result. If you find yourself constructing encrypt-then-MAC by hand, use an authenticated mode instead.
Whatever you choose, store a key identifier with every ciphertext. Without it, rotating the key means being unable to read anything encrypted before the rotation.
Doing this in your own code
Shell
# 128-bit key: 16 bytes, 32 hex characters.
openssl rand -hex 16
# 256-bit key: 32 bytes, 64 hex characters.
openssl rand -hex 32
# The same 256-bit key in base64: 44 characters, the last of which is padding.
openssl rand -base64 32 A 64-character hex string is 32 bytes, not 64. Reading the text as UTF-8 and feeding those 64 bytes to a cipher is one of the most common key-handling bugs there is.
Node
import { randomBytes, createCipheriv, createDecipheriv } from "node:crypto";
const key = randomBytes(32); // AES-256. Bytes, not a string.
const iv = randomBytes(12); // 96-bit GCM nonce, fresh for every message.
const c = createCipheriv("aes-256-gcm", key, iv);
const ct = Buffer.concat([c.update(plaintext, "utf8"), c.final()]);
const tag = c.getAuthTag(); // 16 bytes
const d = createDecipheriv("aes-256-gcm", key, iv);
d.setAuthTag(tag);
const pt = Buffer.concat([d.update(ct), d.final()]); Store the nonce and the tag next to the ciphertext. Neither is secret and both are required to decrypt.
Python (cryptography)
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
key = AESGCM.generate_key(bit_length=256) # 32 bytes
aes = AESGCM(key)
nonce = os.urandom(12) # never reuse this with this key
ct = aes.encrypt(nonce, b"plaintext", b"associated data")
pt = aes.decrypt(nonce, ct, b"associated data") encrypt() appends the 16-byte tag to the ciphertext, so only the nonce has to be stored separately.
Go
key := make([]byte, 32)
rand.Read(key) // crypto/rand
block, _ := aes.NewCipher(key)
gcm, _ := cipher.NewGCM(block) // 12-byte nonce
nonce := make([]byte, gcm.NonceSize())
rand.Read(nonce)
ct := gcm.Seal(nonce, nonce, plaintext, nil) // nonce prefixed to the output Deriving a key from a password
import hashlib, os
# WRONG. The key inherits the entropy of the password, not of the digest,
# and SHA-256 runs at tens of billions of guesses per second on one GPU.
key = hashlib.sha256(password.encode()).digest()
# RIGHT. A password-based KDF with a work factor and a per-user salt.
from argon2.low_level import hash_secret_raw, Type
key = hash_secret_raw(
secret=password.encode(), salt=os.urandom(16),
time_cost=3, memory_cost=64 * 1024, parallelism=4,
hash_len=32, type=Type.ID,
)
# HKDF is the other case: expanding one already-uniform key into several.
# It has no work factor, so it must never be pointed at a password. Questions
Is AES-256 worth using over AES-128?
It is the usual default and the cost is small, so yes, but not because AES-128 is weak. AES-128 has no practical attack and its keyspace is unreachable. The real arguments for 256 are compliance requirements that name it and durability against a future quantum attacker, since Grover's algorithm would halve the effective strength of a symmetric key.
Does the IV need to be kept secret?
No. It is normally prepended to the ciphertext and travels in the clear. What it must be is correct: unique for every encryption under a given key, and additionally unpredictable in CBC mode. Uniqueness is the property that matters, and it is the one that gets broken.
What actually happens if I reuse a GCM nonce?
Two things. The keystream repeats, so exclusive-oring the two ciphertexts reveals the exclusive-or of the plaintexts. And the GHASH authentication subkey leaks, after which an attacker can forge valid tags for arbitrary messages under that key. The second is unrecoverable: the key has to be replaced.
Can I use a password as an AES key?
Not directly. AES takes exactly 16, 24 or 32 bytes and a password is not that. Hashing the password to the right length does not fix it either, because the key then carries only the entropy of the password. Use Argon2id, scrypt or PBKDF2 with a per-use salt, and see the encryption key page for the reasoning.
How many messages can one AES-GCM key encrypt?
With random 96-bit nonces, NIST SP 800-38D caps a key at 2 to the power 32 invocations, which is where nonce collision probability stops being negligible. With a reliable counter you are limited instead by the counter width. Either way, plan for key rotation and store a key identifier with each ciphertext so it is possible.
Should the key be stored as hex or as bytes?
Store whatever your secret manager holds cleanly, usually hex or base64, and decode it to bytes in exactly one place in your code. The bug to avoid is feeding the 64 characters of a hex-encoded 256-bit key to the cipher as 64 bytes of text. It sometimes works, and the result is incompatible with every implementation that decoded correctly.