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.