AES Encryption in Java: CBC, GCM Modes & SecretKey Generation
AES (Advanced Encryption Standard) is the industry-standard symmetric block cipher used to protect data at rest and in transit. Java's JCA exposes AES through the Cipher class. This article covers key generation, CBC mode with padding, and the preferred GCM mode which provides authenticated encryption.
AES Fundamentals: Key Sizes & Modes
| Property | Detail |
|---|---|
| Block size | Always 128 bits (16 bytes) |
| Key sizes | 128, 192, or 256 bits |
| Recommended key size | 256-bit (strongest; 128-bit is also considered secure) |
| IV (Initialisation Vector) | Required for CBC (16 bytes) and GCM (12 bytes recommended) |
| Recommended mode | GCM: provides confidentiality + integrity + authenticity |
| Avoid | ECB mode: deterministic, leaks patterns; CBC without authentication |
Generating a SecretKey
Use KeyGenerator to create a random AES key, or reconstruct a key from raw bytes using SecretKeySpec.
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
// Generate a new random 256-bit AES key
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256, new SecureRandom());
SecretKey key = keyGen.generateKey();
// Serialise to bytes (for storage/transmission)
byte[] keyBytes = key.getEncoded();
// Reconstruct from bytes
SecretKey reconstructed = new SecretKeySpec(keyBytes, "AES");
AES-GCM: Authenticated Encryption (Preferred)
GCM (Galois/Counter Mode) provides authenticated encryption: it simultaneously encrypts and computes an authentication tag. Decryption verifies the tag; any tampering causes an AEADBadTagException.
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
import java.util.Base64;
public class AesGcm {
private static final int GCM_IV_LENGTH = 12; // 96 bits
private static final int GCM_TAG_LENGTH = 128; // bits
/** Encrypt plaintext with AES-GCM. Returns IV + ciphertext (base64). */
public static String encrypt(String plaintext, SecretKey key) throws Exception {
// 1. Generate a random 12-byte IV (never reuse an IV with the same key!)
byte[] iv = new byte[GCM_IV_LENGTH];
new SecureRandom().nextBytes(iv);
// 2. Initialise cipher
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
// 3. Optionally add AAD (Additional Authenticated Data — not encrypted, but authenticated)
// cipher.updateAAD("context-info".getBytes());
// 4. Encrypt
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// 5. Prepend IV to ciphertext for transmission
byte[] combined = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(ciphertext, 0, combined, iv.length, ciphertext.length);
return Base64.getEncoder().encodeToString(combined);
}
/** Decrypt AES-GCM ciphertext. Throws AEADBadTagException if tampered. */
public static String decrypt(String encoded, SecretKey key) throws Exception {
byte[] combined = Base64.getDecoder().decode(encoded);
// Extract IV and ciphertext
byte[] iv = new byte[GCM_IV_LENGTH];
byte[] ciphertext = new byte[combined.length - GCM_IV_LENGTH];
System.arraycopy(combined, 0, iv, 0, iv.length);
System.arraycopy(combined, iv.length, ciphertext, 0, ciphertext.length);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_LENGTH, iv));
return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
}
public static void main(String[] args) throws Exception {
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(256);
SecretKey key = kg.generateKey();
String encrypted = encrypt("Secret message!", key);
System.out.println("Encrypted: " + encrypted);
String decrypted = decrypt(encrypted, key);
System.out.println("Decrypted: " + decrypted); // Secret message!
}
}
AES-CBC Mode: With PKCS5 Padding
CBC (Cipher Block Chaining) is older and widely supported but provides only confidentiality: it has no built-in integrity check. If you use CBC, always add a separate HMAC for authentication (Encrypt-then-MAC).
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
import java.util.Base64;
public class AesCbc {
public static byte[] encrypt(byte[] plaintext, SecretKey key) throws Exception {
// 1. Random 16-byte IV
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
// 2. Encrypt with PKCS5Padding (auto-pads to 16-byte block boundary)
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] ciphertext = cipher.doFinal(plaintext);
// 3. Prepend IV
byte[] result = new byte[16 + ciphertext.length];
System.arraycopy(iv, 0, result, 0, 16);
System.arraycopy(ciphertext, 0, result, 16, ciphertext.length);
return result;
}
public static byte[] decrypt(byte[] ivPlusCiphertext, SecretKey key) throws Exception {
byte[] iv = new byte[16];
byte[] ciphertext = new byte[ivPlusCiphertext.length - 16];
System.arraycopy(ivPlusCiphertext, 0, iv, 0, 16);
System.arraycopy(ivPlusCiphertext, 16, ciphertext, 0, ciphertext.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
return cipher.doFinal(ciphertext);
}
}
Deriving a Key from a Password
When the encryption key must come from a user password, use PBKDF2 to derive a key: never use a raw password as a key. See also the SecureRandom & PBKDF2 article for full details.
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public static SecretKey deriveKey(char[] password, byte[] salt) throws Exception {
PBEKeySpec spec = new PBEKeySpec(
password,
salt,
310_000, // iterations (OWASP 2023 recommendation for PBKDF2-SHA256)
256 // key length in bits
);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] keyBytes = skf.generateSecret(spec).getEncoded();
spec.clearPassword(); // wipe password from memory
return new SecretKeySpec(keyBytes, "AES");
}
Best Practices Summary
| Topic | Recommendation |
|---|---|
| Mode | Always use AES-GCM; avoid ECB and raw CBC |
| Key size | 256-bit; 128-bit is acceptable |
| IV | Random per-encryption; never reuse with the same key |
| IV size (GCM) | 12 bytes (96 bits): optimal for GCM counter |
| Key storage | KeyStore or external secrets manager |
| Password-derived key | Use PBKDF2 / bcrypt / Argon2: never hash the password directly |
| Encoding | Base64 for text transport; raw bytes for binary protocols |
| AAD | Bind ciphertext to context (user ID, file name) using GCM's AAD |