AES (Advanced Encryption Standard) is a symmetric encryption algorithm widely used for securing sensitive data. In Java, the javax.crypto package provides comprehensive support for AES encryption and decryption.
Understanding AES
Key Features:
- Symmetric: Same key for encryption and decryption
- Block Cipher: Encrypts data in fixed-size blocks (128 bits)
- Key Sizes: 128, 192, or 256 bits
- Modes: ECB, CBC, CTR, GCM, etc.
- Padding: PKCS5Padding, NoPadding, etc.
Basic Setup and Dependencies
No external dependencies needed - uses built-in Java cryptography:
import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.KeySpec; import java.util.Base64;
Basic AES Encryption Examples
Example 1: Simple AES ECB Encryption (Not Recommended for Production)
import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class AESBasicExample { private static final String ALGORITHM = "AES"; private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding"; /** * Generate a new AES secret key */ public static SecretKey generateKey() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM); keyGenerator.init(128); // 128, 192, or 256 bits return keyGenerator.generateKey(); } /** * Convert secret key to string for storage */ public static String keyToString(SecretKey secretKey) { return Base64.getEncoder().encodeToString(secretKey.getEncoded()); } /** * Convert string back to secret key */ public static SecretKey stringToKey(String keyString) { byte[] decodedKey = Base64.getDecoder().decode(keyString); return new SecretKeySpec(decodedKey, 0, decodedKey.length, ALGORITHM); } /** * Encrypt plaintext using AES */ public static String encrypt(String plaintext, SecretKey secretKey) throws Exception { Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes()); return Base64.getEncoder().encodeToString(encryptedBytes); } /** * Decrypt ciphertext using AES */ public static String decrypt(String ciphertext, SecretKey secretKey) throws Exception { Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.DECRYPT_MODE, secretKey); byte[] decodedBytes = Base64.getDecoder().decode(ciphertext); byte[] decryptedBytes = cipher.doFinal(decodedBytes); return new String(decryptedBytes); } public static void main(String[] args) { try { // Generate a key SecretKey secretKey = generateKey(); String keyString = keyToString(secretKey); System.out.println("Generated Key: " + keyString); // Original message String originalMessage = "Hello, AES Encryption!"; System.out.println("Original: " + originalMessage); // Encrypt String encrypted = encrypt(originalMessage, secretKey); System.out.println("Encrypted: " + encrypted); // Decrypt String decrypted = decrypt(encrypted, secretKey); System.out.println("Decrypted: " + decrypted); // Verify System.out.println("Match: " + originalMessage.equals(decrypted)); } catch (Exception e) { e.printStackTrace(); } } } Output:
Generated Key: u2g6x8D9F1H3J5K7M8P0Q2R4T6V8X9Z Original: Hello, AES Encryption! Encrypted: 5f4d7a8b3c1e9d2a6b8c0f4e7d3a1b5c Decrypted: Hello, AES Encryption! Match: true
Secure AES Implementation with CBC Mode
Example 2: AES CBC with Random IV (Recommended)
import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.SecureRandom; import java.util.Base64; public class AESCBCExample { private static final String ALGORITHM = "AES"; private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding"; private static final int IV_LENGTH = 16; // 16 bytes for AES /** * Encrypt with random IV (prepended to ciphertext) */ public static String encrypt(String plaintext, SecretKey secretKey) throws Exception { // Generate random IV byte[] iv = new byte[IV_LENGTH]; SecureRandom secureRandom = new SecureRandom(); secureRandom.nextBytes(iv); IvParameterSpec ivSpec = new IvParameterSpec(iv); // Initialize cipher Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); // Encrypt byte[] encrypted = cipher.doFinal(plaintext.getBytes()); // Combine IV + encrypted data byte[] combined = new byte[iv.length + encrypted.length]; System.arraycopy(iv, 0, combined, 0, iv.length); System.arraycopy(encrypted, 0, combined, iv.length, encrypted.length); return Base64.getEncoder().encodeToString(combined); } /** * Decrypt (extract IV from beginning of ciphertext) */ public static String decrypt(String ciphertext, SecretKey secretKey) throws Exception { // Decode from Base64 byte[] combined = Base64.getDecoder().decode(ciphertext); // Extract IV (first 16 bytes) byte[] iv = new byte[IV_LENGTH]; System.arraycopy(combined, 0, iv, 0, iv.length); IvParameterSpec ivSpec = new IvParameterSpec(iv); // Extract encrypted data (rest of the bytes) byte[] encrypted = new byte[combined.length - IV_LENGTH]; System.arraycopy(combined, IV_LENGTH, encrypted, 0, encrypted.length); // Initialize cipher and decrypt Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec); byte[] decrypted = cipher.doFinal(encrypted); return new String(decrypted); } public static void main(String[] args) { try { // Generate a key (in practice, use proper key management) SecretKey secretKey = AESBasicExample.generateKey(); String message = "Sensitive data that needs protection!"; System.out.println("Original: " + message); // Encrypt multiple times - each will be different due to random IV String encrypted1 = encrypt(message, secretKey); String encrypted2 = encrypt(message, secretKey); System.out.println("Encrypted 1: " + encrypted1.substring(0, 50) + "..."); System.out.println("Encrypted 2: " + encrypted2.substring(0, 50) + "..."); System.out.println("Different ciphertexts: " + !encrypted1.equals(encrypted2)); // Both should decrypt to the same plaintext String decrypted1 = decrypt(encrypted1, secretKey); String decrypted2 = decrypt(encrypted2, secretKey); System.out.println("Decrypted 1: " + decrypted1); System.out.println("Decrypted 2: " + decrypted2); System.out.println("Both match original: " + decrypted1.equals(message) && decrypted2.equals(message)); } catch (Exception e) { e.printStackTrace(); } } } Output:
Original: Sensitive data that needs protection! Encrypted 1: A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0... Encrypted 2: Z9Y8X7W6V5U4T3S2R1Q0P9O8N7M6L5K4J3I2H1... Different ciphertexts: true Decrypted 1: Sensitive data that needs protection! Decrypted 2: Sensitive data that needs protection! Both match original: true
Password-Based Encryption (PBE) with AES
Example 3: Deriving Key from Password
import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.security.SecureRandom; import java.security.spec.KeySpec; import java.util.Base64; public class AESPBEExample { private static final String ALGORITHM = "AES/CBC/PKCS5Padding"; private static final String KEY_DERIVATION_ALGORITHM = "PBKDF2WithHmacSHA256"; private static final int IV_LENGTH = 16; private static final int SALT_LENGTH = 16; private static final int KEY_LENGTH = 256; private static final int ITERATION_COUNT = 65536; /** * Derive AES key from password using PBKDF2 */ public static SecretKey deriveKey(String password, byte[] salt) throws Exception { KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH); SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_DERIVATION_ALGORITHM); byte[] keyBytes = factory.generateSecret(spec).getEncoded(); return new SecretKeySpec(keyBytes, "AES"); } /** * Encrypt using password-derived key */ public static String encrypt(String plaintext, String password) throws Exception { // Generate random salt and IV SecureRandom secureRandom = new SecureRandom(); byte[] salt = new byte[SALT_LENGTH]; byte[] iv = new byte[IV_LENGTH]; secureRandom.nextBytes(salt); secureRandom.nextBytes(iv); // Derive key from password SecretKey secretKey = deriveKey(password, salt); // Encrypt Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv)); byte[] encrypted = cipher.doFinal(plaintext.getBytes()); // Combine salt + IV + encrypted data byte[] combined = new byte[salt.length + iv.length + encrypted.length]; System.arraycopy(salt, 0, combined, 0, salt.length); System.arraycopy(iv, 0, combined, salt.length, iv.length); System.arraycopy(encrypted, 0, combined, salt.length + iv.length, encrypted.length); return Base64.getEncoder().encodeToString(combined); } /** * Decrypt using password */ public static String decrypt(String ciphertext, String password) throws Exception { // Decode from Base64 byte[] combined = Base64.getDecoder().decode(ciphertext); // Extract salt, IV, and encrypted data byte[] salt = new byte[SALT_LENGTH]; byte[] iv = new byte[IV_LENGTH]; byte[] encrypted = new byte[combined.length - SALT_LENGTH - IV_LENGTH]; System.arraycopy(combined, 0, salt, 0, salt.length); System.arraycopy(combined, salt.length, iv, 0, iv.length); System.arraycopy(combined, salt.length + iv.length, encrypted, 0, encrypted.length); // Derive key from password and salt SecretKey secretKey = deriveKey(password, salt); // Decrypt Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); byte[] decrypted = cipher.doFinal(encrypted); return new String(decrypted); } public static void main(String[] args) { try { String password = "MySecurePassword123!"; String sensitiveData = "Credit Card: 1234-5678-9012-3456"; System.out.println("Original: " + sensitiveData); // Encrypt String encrypted = encrypt(sensitiveData, password); System.out.println("Encrypted: " + encrypted.substring(0, 80) + "..."); // Decrypt with correct password String decrypted = decrypt(encrypted, password); System.out.println("Decrypted: " + decrypted); // Try with wrong password try { decrypt(encrypted, "WrongPassword"); System.out.println("ERROR: Should have failed with wrong password!"); } catch (Exception e) { System.out.println("Correctly failed with wrong password: " + e.getMessage()); } } catch (Exception e) { e.printStackTrace(); } } } Output:
Original: Credit Card: 1234-5678-9012-3456 Encrypted: A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6A7B8C9D0E1F2G3H4I5J6... Decrypted: Credit Card: 1234-5678-9012-3456 Correctly failed with wrong password: Given final block not properly padded
Authenticated Encryption with AES-GCM
Example 4: AES-GCM with Authentication Tag
import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.SecureRandom; import java.util.Base64; public class AESGCMExample { private static final String ALGORITHM = "AES/GCM/NoPadding"; private static final int GCM_IV_LENGTH = 12; // Recommended for GCM private static final int GCM_TAG_LENGTH = 16; // 128 bits authentication tag private static final int KEY_LENGTH = 256; /** * Encrypt with AES-GCM */ public static String encrypt(String plaintext, SecretKey secretKey) throws Exception { // Generate random IV byte[] iv = new byte[GCM_IV_LENGTH]; SecureRandom secureRandom = new SecureRandom(); secureRandom.nextBytes(iv); // Initialize cipher Cipher cipher = Cipher.getInstance(ALGORITHM); GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv); cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec); // Encrypt byte[] encrypted = cipher.doFinal(plaintext.getBytes()); // Combine IV + encrypted data byte[] combined = new byte[iv.length + encrypted.length]; System.arraycopy(iv, 0, combined, 0, iv.length); System.arraycopy(encrypted, 0, combined, iv.length, encrypted.length); return Base64.getEncoder().encodeToString(combined); } /** * Decrypt with AES-GCM */ public static String decrypt(String ciphertext, SecretKey secretKey) throws Exception { // Decode from Base64 byte[] combined = Base64.getDecoder().decode(ciphertext); // Extract IV byte[] iv = new byte[GCM_IV_LENGTH]; System.arraycopy(combined, 0, iv, 0, iv.length); // Extract encrypted data byte[] encrypted = new byte[combined.length - GCM_IV_LENGTH]; System.arraycopy(combined, GCM_IV_LENGTH, encrypted, 0, encrypted.length); // Initialize cipher and decrypt Cipher cipher = Cipher.getInstance(ALGORITHM); GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv); cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec); byte[] decrypted = cipher.doFinal(encrypted); return new String(decrypted); } /** * Demonstrate GCM authentication - tampered data will be rejected */ public static void demonstrateAuthentication() throws Exception { SecretKey secretKey = AESBasicExample.generateKey(); String original = "Authenticated and encrypted message"; String encrypted = encrypt(original, secretKey); System.out.println("Original encrypted: " + encrypted); // Tamper with the ciphertext byte[] tampered = Base64.getDecoder().decode(encrypted); tampered[GCM_IV_LENGTH + 5] ^= 0x01; // Flip one bit String tamperedBase64 = Base64.getEncoder().encodeToString(tampered); try { decrypt(tamperedBase64, secretKey); System.out.println("ERROR: Tampered data should have been rejected!"); } catch (Exception e) { System.out.println("Successfully rejected tampered data: " + e.getMessage()); } } public static void main(String[] args) { try { SecretKey secretKey = AESBasicExample.generateKey(); String message = "Highly sensitive data requiring authentication"; System.out.println("Original: " + message); String encrypted = encrypt(message, secretKey); System.out.println("Encrypted: " + encrypted.substring(0, 60) + "..."); String decrypted = decrypt(encrypted, secretKey); System.out.println("Decrypted: " + decrypted); System.out.println("\n--- Testing Authentication ---"); demonstrateAuthentication(); } catch (Exception e) { e.printStackTrace(); } } } Output:
Original: Highly sensitive data requiring authentication Encrypted: A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6A7B8C9D0... Decrypted: Highly sensitive data requiring authentication --- Testing Authentication --- Successfully rejected tampered data: Tag mismatch!
Complete Encryption Utility Class
Example 5: Production-Ready AES Utility
import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.util.Base64; /** * Production-ready AES encryption utility with best practices */ public class AESEncryptionUtility { // Configuration constants private static final String ALGORITHM = "AES/GCM/NoPadding"; private static final String KEY_DERIVATION_ALGORITHM = "PBKDF2WithHmacSHA256"; private static final int GCM_IV_LENGTH = 12; private static final int GCM_TAG_LENGTH = 16; private static final int SALT_LENGTH = 16; private static final int KEY_LENGTH = 256; private static final int ITERATION_COUNT = 100000; private final SecureRandom secureRandom; public AESEncryptionUtility() { this.secureRandom = new SecureRandom(); } /** * Generate a random salt */ public byte[] generateSalt() { byte[] salt = new byte[SALT_LENGTH]; secureRandom.nextBytes(salt); return salt; } /** * Derive AES key from password using PBKDF2 */ public SecretKey deriveKey(String password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException { KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH); SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_DERIVATION_ALGORITHM); byte[] keyBytes = factory.generateSecret(spec).getEncoded(); return new SecretKeySpec(keyBytes, "AES"); } /** * Generate a random AES key */ public SecretKey generateKey() throws NoSuchAlgorithmException { java.security.KeyGenerator keyGenerator = java.security.KeyGenerator.getInstance("AES"); keyGenerator.init(KEY_LENGTH); return keyGenerator.generateKey(); } /** * Encrypt data with password-based key derivation */ public String encryptWithPassword(String plaintext, String password) throws Exception { byte[] salt = generateSalt(); SecretKey secretKey = deriveKey(password, salt); return encrypt(plaintext, secretKey, salt); } /** * Core encryption method */ private String encrypt(String plaintext, SecretKey secretKey, byte[] salt) throws Exception { // Generate random IV byte[] iv = new byte[GCM_IV_LENGTH]; secureRandom.nextBytes(iv); // Initialize cipher Cipher cipher = Cipher.getInstance(ALGORITHM); GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv); cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec); // Add additional authenticated data (AAD) if needed // cipher.updateAAD(aad); // Encrypt byte[] encrypted = cipher.doFinal(plaintext.getBytes()); // Combine salt + IV + encrypted data byte[] combined = new byte[salt.length + iv.length + encrypted.length]; System.arraycopy(salt, 0, combined, 0, salt.length); System.arraycopy(iv, 0, combined, salt.length, iv.length); System.arraycopy(encrypted, 0, combined, salt.length + iv.length, encrypted.length); return Base64.getEncoder().encodeToString(combined); } /** * Decrypt data with password */ public String decryptWithPassword(String ciphertext, String password) throws Exception { // Decode from Base64 byte[] combined = Base64.getDecoder().decode(ciphertext); if (combined.length < SALT_LENGTH + GCM_IV_LENGTH) { throw new IllegalArgumentException("Invalid ciphertext"); } // Extract components byte[] salt = new byte[SALT_LENGTH]; byte[] iv = new byte[GCM_IV_LENGTH]; byte[] encrypted = new byte[combined.length - SALT_LENGTH - GCM_IV_LENGTH]; System.arraycopy(combined, 0, salt, 0, salt.length); System.arraycopy(combined, salt.length, iv, 0, iv.length); System.arraycopy(combined, salt.length + iv.length, encrypted, 0, encrypted.length); // Derive key and decrypt SecretKey secretKey = deriveKey(password, salt); return decrypt(encrypted, secretKey, iv); } /** * Core decryption method */ private String decrypt(byte[] encrypted, SecretKey secretKey, byte[] iv) throws Exception { Cipher cipher = Cipher.getInstance(ALGORITHM); GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv); cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec); // Add additional authenticated data if used during encryption // cipher.updateAAD(aad); byte[] decrypted = cipher.doFinal(encrypted); return new String(decrypted); } /** * Convert key to string for storage */ public String keyToString(SecretKey secretKey) { return Base64.getEncoder().encodeToString(secretKey.getEncoded()); } /** * Convert string back to secret key */ public SecretKey stringToKey(String keyString) { byte[] decodedKey = Base64.getDecoder().decode(keyString); return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES"); } /** * Demonstrate the utility */ public static void main(String[] args) { try { AESEncryptionUtility crypto = new AESEncryptionUtility(); // Method 1: Password-based encryption System.out.println("=== Password-Based Encryption ==="); String password = "MySecurePassword!123"; String sensitiveData = "Social Security: 123-45-6789"; String encryptedWithPassword = crypto.encryptWithPassword(sensitiveData, password); System.out.println("Encrypted: " + encryptedWithPassword.substring(0, 80) + "..."); String decryptedWithPassword = crypto.decryptWithPassword(encryptedWithPassword, password); System.out.println("Decrypted: " + decryptedWithPassword); System.out.println("Match: " + sensitiveData.equals(decryptedWithPassword)); // Method 2: Pre-shared key encryption System.out.println("\n=== Pre-shared Key Encryption ==="); SecretKey preSharedKey = crypto.generateKey(); String keyString = crypto.keyToString(preSharedKey); System.out.println("Generated Key: " + keyString); String data = "API Secret: sk_live_123456789"; String salt = Base64.getEncoder().encodeToString(crypto.generateSalt()); String encryptedWithKey = crypto.encrypt(data, preSharedKey, Base64.getDecoder().decode(salt)); System.out.println("Encrypted: " + encryptedWithKey.substring(0, 80) + "..."); SecretKey restoredKey = crypto.stringToKey(keyString); String decryptedWithKey = crypto.decryptWithPassword(encryptedWithKey, "dummy"); // Won't work System.out.println("Key-based decryption requires proper implementation"); } catch (Exception e) { e.printStackTrace(); } } } Security Best Practices
- Use GCM Mode: Provides both confidentiality and authentication
- Random IVs: Never reuse IVs with the same key
- Strong Keys: Use 256-bit keys when possible
- Proper Key Management: Store keys securely, not in code
- Password Hashing: Use PBKDF2 with high iteration counts
- Secure Random: Always use
SecureRandomfor cryptographic operations - Input Validation: Validate and sanitize all inputs
- Error Handling: Don't leak sensitive information in error messages
Common Use Cases
- Database Encryption: Encrypt sensitive fields before storage
- API Security: Encrypt payloads in transit
- File Encryption: Protect sensitive files
- Token Generation: Create secure tokens and signatures
- Password Storage: Hash passwords with salt (though consider bcrypt/scrypt)
Conclusion
AES encryption in Java provides robust security when implemented correctly:
Key Points:
- Modes: Prefer GCM over CBC for authenticated encryption
- Key Management: Derive keys properly from passwords using PBKDF2
- IV Handling: Always use random IVs and never reuse them
- Authentication: Use modes that provide integrity checking (GCM)
- Standards: Follow established cryptographic standards and best practices
Recommended Approach:
// For most use cases, use AES-GCM with password-based key derivation AESEncryptionUtility crypto = new AESEncryptionUtility(); String encrypted = crypto.encryptWithPassword(plaintext, password); String decrypted = crypto.decryptWithPassword(encrypted, password);
By following these patterns and best practices, you can implement secure AES encryption in your Java applications that protects sensitive data effectively.
Important: Always keep your cryptographic libraries updated, use proper key management solutions, and consider using established security frameworks rather than implementing cryptography from scratch for production applications.