Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
186: Cryptography with the javax.crypto Package
Cryptography in Java can feel like walking through a minefield. Between the checked exceptions and the cryptic naming conventions in the javax.crypto package, it's easy to get overwhelmed. But at its core, most of what we do boils down to three things: a Cipher, a Key, and sometimes an Initialization Vector (IV). To show you how this works, we're going to build a simple SecretVault class that encrypts and decrypts sensitive configuration strings—think of it like a way to store an API key in a file without leaving it in plain text.
Picking the right algorithm
First, we need a Cipher instance. I've always preferred AES (Advanced Encryption Standard) because it's the industry standard for symmetric encryption. In symmetric encryption, the same key is used for both locking and unlocking the data. If you lose the key, the data is gone. Period.
Cipher cipher = Cipher.getInstance("AES");
Now, here is where I usually see people trip up. If you just use "AES", Java often defaults to ECB (Electronic Codebook) mode. I'll be honest: ECB is dangerous. It encrypts identical blocks of data into identical ciphertext, which means patterns in your data stay visible. We want AES/CBC/PKCS5Padding. CBC (Cipher Block Chaining) ensures that even if you encrypt the same word twice, the result looks completely different both times.
The "Invalid Key Length" Trap
Let's try to set up our key. I remember the first time I did this; I tried to be "clever" by just converting a password string into bytes. I wrote something like this:
String myKey = "my-secret-password"; // 18 characters
SecretKeySpec keySpec = new SecretKeySpec(myKey.getBytes(), "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
And immediately, Java threw a java.security.InvalidKeyException: Invalid AES key length. I stared at it for ten minutes before realizing that AES is incredibly picky. It doesn't just take "a string"; it requires a key that is exactly 16, 24, or 32 bytes long. My 18-character string was just wrong.
To fix this and make it professional, I'm going to use a KeyGenerator. This ensures we get a cryptographically strong key of the correct length every time.
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256); // Use AES-256 for maximum security
SecretKey secretKey = keyGen.generateKey();
Handling the Initialization Vector (IV)
Since we're using CBC mode, we can't just use a key. We need an IV—a random block of data that "seeds" the first block of encryption. If you reuse the same IV with the same key, you're back to square one with security vulnerabilities. The IV doesn't need to be secret (you can store it right next to the encrypted text), but it must be random.
Here is the full implementation of our vault. Notice how I handle the IV by prepending it to the encrypted data. This is a common pattern: the decryption method reads the first 16 bytes to figure out what the IV was, then decrypts the rest.
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
import java.util.Base64;
public class SecretVault {
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
public static String encrypt(String plainText, SecretKey key) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
// Generate a random IV
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted = cipher.doFinal(plainText.getBytes());
// Combine IV and encrypted text so we have the IV for decryption later
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);
}
public static String decrypt(String cipherText, SecretKey key) throws Exception {
byte[] combined = Base64.getDecoder().decode(cipherText);
// Extract the IV from the start of the array
byte[] iv = new byte[16];
System.arraycopy(combined, 0, iv, 0, iv.length);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// Extract the actual encrypted data
int encryptedSize = combined.length - 16;
byte[] encrypted = new byte[encryptedSize];
System.arraycopy(combined, 16, encrypted, 0, encryptedSize);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
return new String(cipher.doFinal(encrypted));
}
}
I used Base64 encoding at the end because encrypted bytes are often "unprintable" characters that break when you try to save them in a text file or a database. Base64 turns those messy bytes into a clean string of alphanumeric characters.
📋 Practical Task
Exercise: Building a Secure Configuration Manager
Your task is to expand the SecretVault concept into a ConfigManager class. This class should simulate a system that loads an API key from a "protected" source.
Requirements:
- Create a
ConfigManagerclass that contains aSecretKeygenerated once during the class instantiation. - Implement a method
String protectKey(String rawKey)that uses the AES/CBC/PKCS5Padding logic from the lesson to encrypt the key. - Implement a method
String revealKey(String encryptedKey)that decrypts it. - In your
mainmethod, encrypt the string"AI_SECRET_TOKEN_2024_XYZ", print the Base64 result, and then immediately decrypt it to verify the original string is recovered. - Constraint: You must handle the
GeneralSecurityException(or its subclasses) using a try-catch block rather than just throwingExceptionfrom the method signatures.
There are no comments for now.