22.2 Encryption Algorithms and Implementation
This section covers symmetric and asymmetric encryption algorithms with practical implementations, focusing on secure and modern approaches.
Symmetric Encryption: AES (Advanced Encryption Standard)
AES is the recommended symmetric encryption algorithm for most applications. It supports key sizes of 128, 192, and 256 bits.
// AES Encryption with GCM (Galois/Counter Mode)
public class AESGCMEncryption {
private static final int GCM_IV_LENGTH = 12; // 96 bits
private static final int GCM_TAG_LENGTH = 128; // 128 bits
// Generate encryption key
public static SecretKey generateKey(int keySize) throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(keySize, new SecureRandom());
return keyGen.generateKey();
}
// Encrypt plaintext with AES-GCM
public static byte[] encrypt(String plaintext, SecretKey key)
throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// Generate random IV
byte[] iv = new byte[GCM_IV_LENGTH];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
// Initialize cipher in encryption mode
GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec);
// Encrypt plaintext
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// Combine IV + ciphertext (IV needed for decryption)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(iv);
baos.write(ciphertext);
return baos.toByteArray();
}
// Decrypt ciphertext with AES-GCM
public static String decrypt(byte[] encryptedData, SecretKey key)
throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// Extract IV from beginning of encrypted data
ByteArrayInputStream bais = new ByteArrayInputStream(encryptedData);
byte[] iv = new byte[GCM_IV_LENGTH];
bais.read(iv);
byte[] ciphertext = bais.readAllBytes();
// Initialize cipher in decryption mode with extracted IV
GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
cipher.init(Cipher.DECRYPT_MODE, key, gcmSpec);
// Decrypt and verify authentication tag
byte[] plaintext = cipher.doFinal(ciphertext);
return new String(plaintext, StandardCharsets.UTF_8);
}
// Complete example
public static void demonstrateAESGCM() throws Exception {
SecretKey key = generateKey(256); // 256-bit key
String plaintext = "This is sensitive data that must be encrypted";
byte[] encrypted = encrypt(plaintext, key);
System.out.println("Encrypted: " + HexFormat.of().formatHex(encrypted));
String decrypted = decrypt(encrypted, key);
System.out.println("Decrypted: " + decrypted);
assert plaintext.equals(decrypted);
}
}
AES with CBC Mode (Alternative)
CBC mode is less preferred than GCM but may be necessary in some contexts. Always use with separate authentication:
// AES with CBC mode (less secure than GCM, needs separate MAC)
public class AESCBCEncryption {
private static final int IV_LENGTH = 16; // 128 bits for AES
public static byte[] encryptCBC(String plaintext, SecretKey key)
throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// Generate random IV
byte[] iv = new byte[IV_LENGTH];
new SecureRandom().nextBytes(iv);
// Initialize with IV
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
// Encrypt plaintext
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// Return IV + ciphertext
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(iv);
baos.write(ciphertext);
return baos.toByteArray();
}
public static String decryptCBC(byte[] encryptedData, SecretKey key)
throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// Extract IV
ByteArrayInputStream bais = new ByteArrayInputStream(encryptedData);
byte[] iv = new byte[IV_LENGTH];
bais.read(iv);
byte[] ciphertext = bais.readAllBytes();
// Initialize with IV
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] plaintext = cipher.doFinal(ciphertext);
return new String(plaintext, StandardCharsets.UTF_8);
}
// IMPORTANT: For CBC, must use separate MAC for authentication
public static byte[] encryptAndAuthenticate(String plaintext, SecretKey encKey,
SecretKey macKey) throws Exception {
// 1. Encrypt with CBC
byte[] encrypted = encryptCBC(plaintext, encKey);
// 2. Compute MAC over ciphertext
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(macKey);
byte[] tag = mac.doFinal(encrypted);
// 3. Return ciphertext + tag
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(encrypted);
baos.write(tag);
return baos.toByteArray();
}
}
Asymmetric Encryption: RSA (Rivest-Shamir-Adleman)
RSA enables encryption with public keys and decryption with private keys. Always use OAEP padding.
// RSA Asymmetric Encryption
public class RSAEncryption {
// Generate RSA key pair
public static KeyPair generateKeyPair(int keySize)
throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(keySize, new SecureRandom());
return keyPairGen.generateKeyPair();
}
// Encrypt with public key
public static byte[] encryptWithPublicKey(String plaintext, PublicKey publicKey)
throws Exception {
// Use OAEP padding for security
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
}
// Decrypt with private key
public static String decryptWithPrivateKey(byte[] ciphertext, PrivateKey privateKey)
throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] plaintext = cipher.doFinal(ciphertext);
return new String(plaintext, StandardCharsets.UTF_8);
}
// Complete encryption flow
public static void demonstrateRSA() throws Exception {
// Generate key pair
KeyPair keyPair = generateKeyPair(2048);
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// Encrypt with public key
String plaintext = "Secret message";
byte[] encrypted = encryptWithPublicKey(plaintext, publicKey);
System.out.println("Encrypted: " + HexFormat.of().formatHex(encrypted));
// Decrypt with private key
String decrypted = decryptWithPrivateKey(encrypted, privateKey);
System.out.println("Decrypted: " + decrypted);
assert plaintext.equals(decrypted);
}
// RSA limitations
public static void demonstrateRSALimitations() throws Exception {
KeyPair keyPair = generateKeyPair(2048);
PublicKey publicKey = keyPair.getPublic();
// RSA can only encrypt data smaller than key size - padding
// 2048-bit RSA can encrypt ~190 bytes (key size / 8 - padding overhead)
String longMessage = "a".repeat(500);
try {
encryptWithPublicKey(longMessage, publicKey);
} catch (IllegalBlockSizeException e) {
System.out.println("Cannot encrypt data longer than: key_size/8 - padding");
}
}
}
Hybrid Encryption: Combining RSA and AES
For large messages, use hybrid encryption: asymmetric for key exchange, symmetric for data:
// Hybrid Encryption (RSA for key exchange + AES for data)
public class HybridEncryption {
// Structure to hold encrypted data
public static class EncryptedMessage {
public byte[] encryptedSymmetricKey; // Encrypted with RSA public key
public byte[] encryptedData; // Encrypted with AES
public byte[] iv; // IV for AES-CBC
public byte[] authenticationTag; // MAC tag for authenticity
}
// Encrypt large message with hybrid approach
public static EncryptedMessage encryptLargeMessage(String plaintext,
PublicKey publicKey)
throws Exception {
EncryptedMessage result = new EncryptedMessage();
// Step 1: Generate random symmetric key
SecretKey symmetricKey = AESGCMEncryption.generateKey(256);
// Step 2: Encrypt symmetric key with RSA public key
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
result.encryptedSymmetricKey = rsaCipher.doFinal(symmetricKey.getEncoded());
// Step 3: Encrypt large message with symmetric key
result.encryptedData = AESGCMEncryption.encrypt(plaintext, symmetricKey);
return result;
}
// Decrypt with hybrid approach
public static String decryptLargeMessage(EncryptedMessage encryptedMessage,
PrivateKey privateKey)
throws Exception {
// Step 1: Decrypt symmetric key with RSA private key
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] symmetricKeyBytes = rsaCipher.doFinal(encryptedMessage.encryptedSymmetricKey);
// Reconstruct SecretKey
SecretKey symmetricKey = new javax.crypto.spec.SecretKeySpec(
symmetricKeyBytes, 0, symmetricKeyBytes.length, "AES");
// Step 2: Decrypt large message with symmetric key
return AESGCMEncryption.decrypt(encryptedMessage.encryptedData, symmetricKey);
}
// Complete example
public static void demonstrateHybridEncryption() throws Exception {
KeyPair keyPair = RSAEncryption.generateKeyPair(2048);
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// Large message that wouldn't fit in RSA encryption
String largeMessage = "a".repeat(1000);
// Encrypt using hybrid approach
EncryptedMessage encrypted = encryptLargeMessage(largeMessage, publicKey);
System.out.println("Encrypted symmetric key size: " +
encrypted.encryptedSymmetricKey.length);
System.out.println("Encrypted data size: " + encrypted.encryptedData.length);
// Decrypt
String decrypted = decryptLargeMessage(encrypted, privateKey);
assert largeMessage.equals(decrypted);
}
}
Elliptic Curve Cryptography (ECC)
ECC provides stronger security with smaller key sizes compared to RSA:
// Elliptic Curve Cryptography
public class ECCEncryption {
// Generate ECC key pair
public static KeyPair generateECCKeyPair(String curve)
throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecSpec = new ECGenParameterSpec(curve);
keyPairGen.initialize(ecSpec, new SecureRandom());
return keyPairGen.generateKeyPair();
}
// ECC key sizes and equivalent RSA strength
public static void demonstrateECCEfficiency() {
System.out.println("=== ECC Key Size Equivalence ===");
System.out.println("P-256 ECC ≈ RSA 3072-bit (128-bit security)");
System.out.println("P-384 ECC ≈ RSA 7680-bit (192-bit security)");
System.out.println("P-521 ECC ≈ RSA 15360-bit (256-bit security)");
System.out.println("\nECC provides stronger security with smaller keys!");
}
// Recommended ECC curves
public static void demonstrateECCCurves() throws Exception {
// P-256: RECOMMENDED, good balance
KeyPair p256 = generateECCKeyPair("secp256r1");
// P-384: High security
KeyPair p384 = generateECCKeyPair("secp384r1");
// P-521: Very high security
KeyPair p521 = generateECCKeyPair("secp521r1");
System.out.println("Generated ECC key pairs with different curves");
}
}
Encryption Modes Explained
Different modes of operation for block ciphers:
// Block Cipher Modes Comparison
public class BlockCipherModes {
public static void printModesComparison() {
System.out.println("=== BLOCK CIPHER MODES ===");
System.out.println("\nECB (Electronic Codebook):");
System.out.println(" - INSECURE: Same plaintext → same ciphertext");
System.out.println(" - Reveals patterns in data");
System.out.println(" - Use: NEVER");
System.out.println("\nCBC (Cipher Block Chaining):");
System.out.println(" - Chained blocks for randomness");
System.out.println(" - Requires random IV for each encryption");
System.out.println(" - MUST use with MAC for authentication");
System.out.println(" - Use: When GCM not available");
System.out.println("\nGCM (Galois/Counter Mode):");
System.out.println(" - Authenticated encryption");
System.out.println(" - Detects tampering automatically");
System.out.println(" - Counter mode: parallelizable");
System.out.println(" - Use: RECOMMENDED");
System.out.println("\nCTR (Counter Mode):");
System.out.println(" - Parallelizable encryption");
System.out.println(" - Turns block cipher into stream cipher");
System.out.println(" - MUST use with MAC");
System.out.println(" - Use: When GCM not available and parallel needed");
System.out.println("\nOFB/CFB (Output/Cipher Feedback):");
System.out.println(" - Stream cipher modes");
System.out.println(" - Less common in modern systems");
System.out.println(" - Use: Legacy systems only");
}
// Mode selection guide
public static String selectMode(String requirement) {
return switch(requirement) {
case "authenticated" -> "AES/GCM/NoPadding";
case "parallel" -> "AES/CBC/PKCS5Padding + HMAC";
case "legacy" -> "AES/CBC/PKCS5Padding";
default -> "AES/GCM/NoPadding"; // Default recommendation
};
}
}
Initialization Vectors (IVs) and Nonces
Proper IV/nonce handling is critical for security:
// Initialization Vector and Nonce Management
public class IVAndNonceManagement {
// IV requirements for different modes
public static void demonstrateIVRequirements() {
System.out.println("=== IV/NONCE REQUIREMENTS ===");
System.out.println("\nAES-GCM:");
System.out.println(" - 96 bits (12 bytes) recommended");
System.out.println(" - Must be unique for same key");
System.out.println(" - Should be random");
System.out.println("\nAES-CBC:");
System.out.println(" - 128 bits (16 bytes)");
System.out.println(" - Must be random and unpredictable");
System.out.println(" - Transmit with ciphertext");
System.out.println("\nGeneral Rules:");
System.out.println(" - NEVER reuse with same key");
System.out.println(" - Use SecureRandom to generate");
System.out.println(" - Store/transmit with ciphertext");
System.out.println(" - Don't encrypt IV");
}
// Correct IV generation pattern
public static byte[] generateSecureIV(int lengthInBytes) {
byte[] iv = new byte[lengthInBytes];
new SecureRandom().nextBytes(iv);
return iv;
}
// Tracking IV usage to prevent reuse
public static class IVTracker {
private final Set<String> usedIVs = new CopyOnWriteArraySet<>();
public synchronized boolean isIVUsed(byte[] iv) {
String ivHex = HexFormat.of().formatHex(iv);
return usedIVs.contains(ivHex);
}
public synchronized void markIVUsed(byte[] iv) {
String ivHex = HexFormat.of().formatHex(iv);
if (!usedIVs.add(ivHex)) {
throw new SecurityException("IV reuse detected!");
}
}
}
}
Key Size Recommendations
// Recommended Key Sizes
public class KeySizeRecommendations {
public static void printRecommendations() {
System.out.println("=== RECOMMENDED KEY SIZES (2024) ===");
System.out.println("\nSymmetric Encryption:");
System.out.println(" AES: 256-bit (RECOMMENDED)");
System.out.println(" AES: 192-bit (acceptable)");
System.out.println(" AES: 128-bit (minimum, adequate for most uses)");
System.out.println("\nAsymmetric Encryption (RSA):");
System.out.println(" RSA: 4096-bit (RECOMMENDED for long-term security)");
System.out.println(" RSA: 2048-bit (minimum acceptable)");
System.out.println(" RSA: 1024-bit or less (DEPRECATED, insecure)");
System.out.println("\nElliptic Curve:");
System.out.println(" P-256 (secp256r1): RECOMMENDED (128-bit security)");
System.out.println(" P-384 (secp384r1): High security (192-bit security)");
System.out.println(" P-521 (secp521r1): Very high security");
System.out.println("\nHash Functions:");
System.out.println(" SHA-256: RECOMMENDED");
System.out.println(" SHA-512: For higher security");
System.out.println(" SHA-3: Modern alternative");
System.out.println(" MD5, SHA-1: DEPRECATED");
}
// Verify key size
public static void validateKeySize(SecretKey key) {
int bitLength = key.getEncoded().length * 8;
if (bitLength < 128) {
throw new SecurityException("Key too small: " + bitLength + " bits");
}
System.out.println("Key size valid: " + bitLength + " bits");
}
}
Best Practices for Encryption
- Always use authenticated encryption: GCM mode combines encryption and authentication.
- Generate unique IVs: Never reuse IV with same key.
- Use strong key sizes: AES-256, RSA-2048+, or P-256 for ECC.
- Prefer GCM over CBC: GCM is more secure and easier to use correctly.
- Use OAEP padding: For RSA encryption, always use OAEP.
- Secure key storage: Never hardcode keys; use KeyStore or secure configuration.
- Validate key source: Ensure keys come from cryptographically secure generation.
- Handle exceptions properly: Cipher failures indicate potential attacks.
- Update deprecated algorithms: Regularly audit code for weak algorithms.