Java's javax.crypto.Cipher class is the standard way to encrypt data. Learn AES-GCM encryption, key generation, IV handling, and the common mistakes that make Java crypto insecure.
Java's Cipher class is the standard way to encrypt data in Java applications. It is also easy to get wrong. Wrong mode, no IV, ECB by default. The most common Java crypto mistake in code review is calling Cipher.getInstance("AES"), which silently gives you ECB mode with PKCS5 padding. ECB encrypts each 16-byte block independently, so identical plaintext blocks produce identical ciphertext blocks. The resulting pattern leakage is well known enough to have a nickname: the "ECB penguin."
The fix is to always specify the full transformation string: AES/GCM/NoPadding. This article walks through a complete, runnable AES-GCM encryption and decryption example using the Java Cryptography Architecture (JCA), covering key generation, IV handling, and the three mistakes that account for most Java crypto vulnerabilities.
You can test AES encryption interactively with our Block Cipher tool.
The JCA provides the javax.crypto.Cipher class for encryption and decryption. You obtain a Cipher instance by calling Cipher.getInstance() with a transformation string. The transformation specifies the algorithm, mode, and padding.
```java // CORRECT: explicit mode and padding Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// WRONG: defaults to AES/ECB/PKCS5Padding on most providers Cipher cipher = Cipher.getInstance("AES"); ```
The Oracle JCA documentation states that every Java implementation is required to support AES/GCM/NoPadding with 128-bit and 256-bit keys, plus ChaCha20-Poly1305. These are the two recommended authenticated encryption modes for new code.
GCM (Galois/Counter Mode) is preferred because it provides authenticated encryption. It ensures both confidentiality (the ciphertext is unreadable without the key) and integrity (tampered ciphertext fails to decrypt instead of producing corrupted plaintext). NIST Special Publication 800-38D standardizes GCM and specifies a 96-bit (12-byte) IV as the recommended size, with a 128-bit authentication tag for general-purpose use.
Key generation. Use KeyGenerator to produce a secret key. For AES-256, request 256 bits:
``java
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey key = keyGen.generateKey();
``
IV generation. The IV (Initialization Vector) for GCM must be 12 bytes and must never be reused with the same key. Generate it fresh for every encryption call using SecureRandom:
``java
byte[] iv = new byte[12];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(iv);
``
Never use java.util.Random for IV generation. It is predictable. SecureRandom uses system entropy and is cryptographically secure. On modern Java (17+), SecureRandom does not block on Linux because it reads from /dev/urandom by default.
GCMParameterSpec. Pass the IV and tag length to the Cipher via GCMParameterSpec:
``java
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
``
The first argument is the authentication tag length in bits. 128 is the NIST-recommended default. Shorter tags (down to 32 bits) are permitted only in specifically justified, constrained scenarios.
Here is a complete, runnable example that encrypts and decrypts a string using AES-256-GCM:
```java import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import java.security.SecureRandom; import java.nio.charset.StandardCharsets; import java.util.Arrays;
public class AesGcmExample {
public static void main(String[] args) throws Exception { // Generate a 256-bit AES key KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(256); SecretKey key = keyGen.generateKey();
String plaintext = "Attack at dawn"; byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
// Encrypt byte[] ciphertext = encrypt(plaintextBytes, key); System.out.println("Ciphertext length: " + ciphertext.length + " bytes");
// Decrypt byte[] decrypted = decrypt(ciphertext, key); System.out.println("Decrypted: " + new String(decrypted, StandardCharsets.UTF_8)); }
public static byte[] encrypt(byte[] plaintext, SecretKey key) throws Exception { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// Generate a fresh 12-byte IV for every encryption byte[] iv = new byte[12]; new SecureRandom().nextBytes(iv);
GCMParameterSpec spec = new GCMParameterSpec(128, iv); cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] ciphertext = cipher.doFinal(plaintext);
// Prepend IV to ciphertext so decryptor can use it byte[] output = new byte[iv.length + ciphertext.length]; System.arraycopy(iv, 0, output, 0, iv.length); System.arraycopy(ciphertext, 0, output, iv.length, ciphertext.length);
return output; }
public static byte[] decrypt(byte[] input, SecretKey key) throws Exception { // Extract the 12-byte IV from the front byte[] iv = Arrays.copyOfRange(input, 0, 12); byte[] ciphertext = Arrays.copyOfRange(input, 12, input.length);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); GCMParameterSpec spec = new GCMParameterSpec(128, iv); cipher.init(Cipher.DECRYPT_MODE, key, spec);
return cipher.doFinal(ciphertext); } } ```
The IV is prepended to the ciphertext in the output. This is the standard pattern. The IV is not secret. It only needs to be unique for each encryption under the same key. The decryptor extracts the IV from the first 12 bytes and uses it to initialize the Cipher for decryption.
The OWASP Cryptographic Storage Cheat Sheet recommends this exact pattern: AES-GCM with a random 12-byte IV prepended to the ciphertext, using a 128-bit authentication tag.
Mistake 1: Using ECB mode. Calling Cipher.getInstance("AES") without specifying a mode defaults to ECB on the SunJCE provider. ECB encrypts each block independently, leaking patterns. Always use the full transformation string AES/GCM/NoPadding.
Mistake 2: Reusing IVs. The most damaging mistake in GCM is reusing a nonce under the same key. NIST SP 800-38D requires that the (key, IV) pair never repeat. A single reuse lets an attacker recover the GCM authentication subkey and forge arbitrary ciphertexts that pass integrity checks. In practice, this happens when developers hardcode a fixed IV "for reproducibility" or reuse a static Cipher instance without re-initializing the IV.
Mistake 3: No authentication. Using AES/CBC/PKCS5Padding without a separate HMAC provides confidentiality but not integrity. An attacker can modify the ciphertext in transit, and decryption will produce corrupted plaintext without any error. CBC is also vulnerable to padding oracle attacks when the application distinguishes padding-invalid errors from other decryption failures. GCM solves both problems by providing authenticated encryption in a single pass.
Mistake 4: Using java.util.Random for keys or IVs. java.util.Random is a linear congruential generator. Its output is predictable if the seed is known (and the seed is often the current time in milliseconds). Always use SecureRandom for any cryptographic value. As safeguard.sh's analysis of Java crypto mistakes notes, the insecure path requires less typing than the secure one, which is exactly why the bug survives code review.
AES-GCM has a hard limit on how much data can be encrypted under a single key: approximately 2^39 - 256 bits (about 64 GB) according to NIST SP 800-38D. For most applications this is not a concern, but for encrypting very large files or streams, you should use a key derivation approach that rotates keys before hitting this limit.
GCM also requires that the IV be unique per encryption. If you generate IVs randomly with SecureRandom, the probability of a collision becomes non-negligible after roughly 2^48 encryptions under the same key (birthday bound). For high-volume systems, consider a counter-based IV generation scheme that guarantees uniqueness without randomness.
The JCA's Cipher class does not reset its state after doFinal() for AEAD modes like GCM. This is intentional, to prevent forgery attacks due to key and IV uniqueness requirements. You must call init() again for each new encryption operation.
When you call Cipher.getInstance("AES") without specifying a mode, the SunJCE provider defaults to AES/ECB/PKCS5Padding. ECB mode encrypts each 16-byte block independently, leaking patterns in the plaintext. Always specify the full transformation string, such as AES/GCM/NoPadding.
Use AES-GCM. GCM provides authenticated encryption, meaning it verifies both confidentiality and integrity. CBC provides only confidentiality and is vulnerable to padding oracle attacks when the application distinguishes padding errors. GCM is standardized in NIST SP 800-38D and is required to be supported by every Java implementation.
12 bytes (96 bits). NIST SP 800-38D specifies 96 bits as the recommended IV size for GCM. Java's SunJCE provider generates a 12-byte IV via SecureRandom automatically when you do not supply a GCMParameterSpec, but it is better to generate and manage the IV explicitly so you can prepend it to the ciphertext for decryption.
No. Reusing an IV with the same key is catastrophic in GCM. A single reuse lets an attacker recover the authentication subkey and forge ciphertexts that pass integrity checks. Generate a fresh random IV for every encryption call using SecureRandom.
AES-GCM has been supported since Java 8 (released in 2014). Java 26, the current LTS as of 2026, supports AES/GCM/NoPadding with both 128-bit and 256-bit keys, plus ChaCha20-Poly1305 as an alternative authenticated encryption mode.
Block Cipher (AES / DES)
Encrypt and decrypt with AES-128, AES-256, DES, and Triple DES using GCM, CBC, and ECB modes. AES uses the Web Crypto API.
Base64 Encode / Decode
Encode text to Base64 or decode Base64 payloads with UTF-8-safe handling.
JWT Decoder
Decode and view JSON Web Tokens to inspect headers, payload, and signature.
Checksum Calculator
Calculate Luhn, CRC32, MD5, and SHA1 checksums for data validation and integrity checking.
The Difference Between Encoding, Encryption, and Hashing
Base64 is not encryption. This guide defines encoding, encryption, and hashing precisely, runs the same input through each, and explains when to use which in production systems.
How SHA-256 Works: A Step-by-Step Walkthrough for Developers
SHA-256 is defined in NIST FIPS 180-4. This walkthrough explains padding, message schedule expansion, the 64-round compression function, and why you should never use SHA-256 for passwords.