22.1 Cryptography Fundamentals and Concepts

Cryptography is the mathematical science of transforming information to protect its confidentiality, integrity, and authenticity. Understanding fundamental concepts is essential before implementing secure systems.

Cryptographic Goals and Properties

Cryptography serves four primary security goals:

// Security goals illustration
public class CryptoGoals {

    // 1. CONFIDENTIALITY: Prevent unauthorized access to data
    // Example: Encrypting a credit card number with AES
    private static byte[] encryptCreditCard(String cardNumber, SecretKey key) 
            throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(cardNumber.getBytes());
    }

    // 2. INTEGRITY: Detect if data has been modified
    // Example: Using MAC to verify message hasn't been tampered with
    private static byte[] computeHMAC(String message, SecretKey key) 
            throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(key);
        return mac.doFinal(message.getBytes());
    }

    // 3. AUTHENTICITY: Verify identity and source of data
    // Example: Using digital signatures to verify sender identity
    private static byte[] signMessage(String message, PrivateKey privateKey) 
            throws Exception {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(message.getBytes());
        return signature.sign();
    }

    // 4. NON-REPUDIATION: Prove someone took specific action
    // Example: Digitally signed transaction log
    private static class SignedTransaction {
        String transactionId;
        String userId;
        double amount;
        byte[] digitalSignature;
        // Signature proves user cannot deny creating transaction
    }
}

Key Concepts in Cryptography

Essential terminology for understanding cryptographic systems:

// Key cryptographic concepts
public class CryptoTerminology {

    // PLAINTEXT: Original, unencrypted data
    String plaintext = "This is sensitive information";

    // CIPHERTEXT: Encrypted, unreadable data
    // byte[] ciphertext = encrypt(plaintext)

    // KEY: Secret value used to encrypt/decrypt
    // - Symmetric key: Same key for encryption and decryption
    // - Asymmetric keys: Different keys (public/private pair)

    // ALGORITHM: Mathematical procedure for encryption/decryption
    // Examples: AES, RSA, SHA-256, ECDSA

    // CIPHER: Implementation of an algorithm using specific modes/padding
    // Example: "AES/GCM/NoPadding" combines algorithm, mode, and padding

    // MODE: How algorithm processes large data blocks
    // Examples: GCM, CBC, CTR, ECB (note: ECB is insecure)

    // PADDING: Adding bytes to match block size
    // Examples: PKCS5, NoPadding (for GCM which handles it internally)

    // IV (INITIALIZATION VECTOR): Random value for encryption
    // - Ensures same plaintext produces different ciphertext
    // - Must be random and unpredictable

    // NONCE (NUMBER USED ONCE): Value used only once for encryption
    // - Similar to IV but with stricter uniqueness guarantee
    // - Critical for authenticated encryption modes like GCM

    // SALT: Random data added to input before hashing
    // - Prevents rainbow table attacks
    // - Each input gets its own unique salt

    // HASH: One-way function mapping input to fixed-size output
    // - Input: any size
    // - Output: fixed size (256 bits for SHA-256)
    // - Cannot reverse to get original input
    // - Deterministic (same input → same output)
}

Symmetric vs. Asymmetric Cryptography

Understanding the differences is crucial for choosing appropriate algorithms:

// Symmetric vs. Asymmetric Cryptography Comparison
public class CryptoSystemComparison {

    // SYMMETRIC ENCRYPTION
    // - Same key encrypts and decrypts
    // - Fast (suitable for large data)
    // - Key distribution challenge (must securely share key)
    // - Use when: Encrypting large files, database encryption
    public static class SymmetricExample {
        public static void demo() throws Exception {
            KeyGenerator keyGen = KeyGenerator.getInstance("AES");
            keyGen.init(256);
            SecretKey key = keyGen.generateKey(); // Single key

            // Encrypt
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] iv = new byte[12];
            new SecureRandom().nextBytes(iv);
            cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv));
            byte[] ciphertext = cipher.doFinal("secret".getBytes());

            // Decrypt with SAME key
            cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
            byte[] plaintext = cipher.doFinal(ciphertext);
        }
    }

    // ASYMMETRIC ENCRYPTION (Public Key Cryptography)
    // - Different keys for encryption (public) and decryption (private)
    // - Slower (not suitable for large data)
    // - No key distribution problem (public key is public)
    // - Use when: Key distribution, digital signatures, agreements
    public static class AsymmetricExample {
        public static void demo() throws Exception {
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
            keyPairGen.initialize(2048);
            KeyPair keyPair = keyPairGen.generateKeyPair();

            PublicKey publicKey = keyPair.getPublic(); // Can share publicly
            PrivateKey privateKey = keyPair.getPrivate(); // Keep secret

            // Encrypt with public key
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] ciphertext = cipher.doFinal("secret".getBytes());

            // Decrypt with DIFFERENT key
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] plaintext = cipher.doFinal(ciphertext);
        }
    }

    // COMPARISON TABLE
    public static void printComparison() {
        System.out.println("SYMMETRIC vs ASYMMETRIC");
        System.out.println("Symmetric: Fast, Same key, Hard to distribute");
        System.out.println("Asymmetric: Slow, Different keys, Easy to distribute");
        System.out.println("Hybrid: Use asymmetric to exchange symmetric key");
    }
}

Java Cryptography Architecture (JCA)

Java's cryptographic framework is provider-based and pluggable:

// Java Cryptography Architecture (JCA) Overview
public class JCAArchitecture {

    // JCA has three main components:
    // 1. Engine classes: Cipher, MessageDigest, Signature, KeyGenerator
    // 2. Key classes: Key, PublicKey, PrivateKey, SecretKey
    // 3. Providers: Implementations of algorithms (SunJCE, BC, BoringSSL)

    public static void demonstrateJCA() throws Exception {
        // Getting an algorithm implementation
        // getInstance uses default provider chain
        MessageDigest md1 = MessageDigest.getInstance("SHA-256");

        // Explicitly specifying provider
        MessageDigest md2 = MessageDigest.getInstance("SHA-256", "SUN");

        // Listing available providers
        java.security.Provider[] providers = java.security.Security.getProviders();
        for (java.security.Provider provider : providers) {
            System.out.println("Provider: " + provider.getName());
        }

        // Listing available algorithms for a service
        java.util.Set<String> algorithms = new java.util.HashSet<>();
        for (java.security.Provider provider : providers) {
            for (Object key : provider.keySet()) {
                String keyStr = (String) key;
                if (keyStr.startsWith("Cipher.")) {
                    algorithms.add(keyStr.substring(7));
                }
            }
        }
        System.out.println("Available cipher algorithms: " + algorithms);
    }
}

Secure Random Number Generation

Cryptographic randomness is essential for keys, IVs, and salts:

// Secure Random Number Generation
public class SecureRandomDemo {

    public static void demonstrateSecureRandom() throws Exception {
        // SecureRandom: Cryptographically strong random number generator
        SecureRandom secureRandom = new SecureRandom();

        // Generate random bytes (for keys, IVs, salts)
        byte[] randomBytes = new byte[32];
        secureRandom.nextBytes(randomBytes);
        System.out.println("Random bytes: " + HexFormat.of().formatHex(randomBytes));

        // Generate random integers
        int randomInt = secureRandom.nextInt();
        System.out.println("Random int: " + randomInt);

        // Generate random long
        long randomLong = secureRandom.nextLong();
        System.out.println("Random long: " + randomLong);

        // Generate random double (0.0 to 1.0)
        double randomDouble = secureRandom.nextDouble();
        System.out.println("Random double: " + randomDouble);
    }

    public static class SecureRandomBestPractices {

        // Generate IV for AES-GCM
        public static byte[] generateIV() {
            byte[] iv = new byte[12]; // 96 bits for GCM
            new SecureRandom().nextBytes(iv);
            return iv;
        }

        // Generate salt for password hashing
        public static byte[] generateSalt() {
            byte[] salt = new byte[16];
            new SecureRandom().nextBytes(salt);
            return salt;
        }

        // Generate cryptographic key
        public static SecretKey generateKey() throws NoSuchAlgorithmException {
            KeyGenerator keyGen = KeyGenerator.getInstance("AES");
            keyGen.init(256, new SecureRandom());
            return keyGen.generateKey();
        }

        // IMPORTANT: Share single SecureRandom instance for performance
        private static final SecureRandom RANDOM = new SecureRandom();

        public static void useSharedInstance() {
            byte[] bytes = new byte[32];
            RANDOM.nextBytes(bytes); // Reuse instance
        }
    }
}

Common Cryptographic Algorithms and Use Cases

Overview of recommended algorithms for different security needs:

// Algorithm Selection Guide
public class AlgorithmSelectionGuide {

    public static void printAlgorithmRecommendations() {
        System.out.println("=== HASHING (One-way) ===");
        System.out.println("SHA-256: General purpose, 256-bit, RECOMMENDED");
        System.out.println("SHA-512: Higher security, 512-bit, good for password hashing");
        System.out.println("MD5: DEPRECATED, use only for non-security purposes");
        System.out.println("SHA-1: DEPRECATED for cryptographic uses");

        System.out.println("\n=== SYMMETRIC ENCRYPTION (Same key) ===");
        System.out.println("AES-256-GCM: RECOMMENDED, authenticated encryption");
        System.out.println("AES-256-CBC: Alternative, needs separate MAC");
        System.out.println("DES: DEPRECATED, only 56-bit, easily broken");
        System.out.println("3DES: Acceptable but slow, prefer AES");

        System.out.println("\n=== ASYMMETRIC ENCRYPTION (Public/Private key) ===");
        System.out.println("RSA-2048+OAEP: RECOMMENDED for key exchange");
        System.out.println("ECDH: RECOMMENDED, more efficient than RSA");
        System.out.println("RSA-1024: DEPRECATED, insufficient key size");

        System.out.println("\n=== DIGITAL SIGNATURES ===");
        System.out.println("SHA-256 with RSA-2048: RECOMMENDED");
        System.out.println("SHA-256 with ECDSA: RECOMMENDED, more efficient");
        System.out.println("EdDSA: RECOMMENDED for new systems");
        System.out.println("MD5withRSA: DEPRECATED");

        System.out.println("\n=== MESSAGE AUTHENTICATION ===");
        System.out.println("HMAC-SHA256: RECOMMENDED");
        System.out.println("HMAC-SHA512: For higher security");
        System.out.println("CMAC: Alternative authenticated encryption");

        System.out.println("\n=== KEY DERIVATION ===");
        System.out.println("PBKDF2: RECOMMENDED for password-based keys");
        System.out.println("Scrypt: Better for password hashing, resistant to GPU attacks");
        System.out.println("bcrypt: Good for password storage");
    }
}

Cryptographic Pitfalls and How to Avoid Them

Common mistakes and their solutions:

// Common Cryptographic Mistakes
public class CryptoPitfalls {

    // PITFALL 1: Using weak randomness
    static class WeakRandomExample {
        // WRONG: Random is not cryptographically secure
        Random weakRandom = new Random();
        byte[] badKey = new byte[32];
        // weakRandom.nextBytes(badKey); // DON'T DO THIS

        // CORRECT: Use SecureRandom
        SecureRandom secureRandom = new SecureRandom();
        byte[] goodKey = new byte[32];
        // secureRandom.nextBytes(goodKey); // OK
    }

    // PITFALL 2: Reusing IVs or nonces
    static class IVReuseExample {
        SecretKey key = null;

        // WRONG: Reusing same IV
        byte[] fixedIV = new byte[12];
        // Both encryptions use same IV - breaks security!
        // encryptMessage1(key, fixedIV);
        // encryptMessage2(key, fixedIV);

        // CORRECT: Generate new IV for each encryption
        private byte[] generateNewIV() {
            byte[] iv = new byte[12];
            new SecureRandom().nextBytes(iv);
            return iv;
        }
    }

    // PITFALL 3: Ignoring authentication
    static class NoAuthenticationExample {
        // WRONG: Encryption without authentication (CBC mode)
        // Attacker can modify ciphertext without being detected

        // CORRECT: Use authenticated encryption (GCM mode)
        // GCM detects any modifications to ciphertext
    }

    // PITFALL 4: Hardcoded keys
    static class HardcodedKeyExample {
        // WRONG: Key hardcoded in source
        // private static final String KEY = "mysecretkey";

        // CORRECT: Load from secure storage
        private static String loadKeyFromSecureStorage() {
            // Load from: environment variable, secure key store, config file
            return System.getenv("ENCRYPTION_KEY");
        }
    }

    // PITFALL 5: Using deterministic encryption for sensitive data
    static class DeterministicExample {
        // WRONG: ECB mode produces same ciphertext for same plaintext
        // Cipher.getInstance("AES/ECB/PKCS5Padding");

        // CORRECT: Use mode with randomization (GCM, CBC)
        // Cipher.getInstance("AES/GCM/NoPadding");
    }

    // PITFALL 6: Ignoring key sizes
    static class KeySizeExample {
        // WRONG: Too small key
        // KeyPairGenerator.getInstance("RSA").initialize(512);

        // CORRECT: Use recommended key size
        // RSA: minimum 2048 bits
        // AES: 256 bits
        // ECDSA: P-256 curve (equivalent to 128-bit security)
    }
}

Cryptographic Libraries and Providers

Java's default and alternative cryptographic providers:

// Available Cryptographic Providers
public class CryptoProviders {

    // Built-in providers (always available)
    public static void demonstrateBuiltInProviders() {
        System.out.println("=== Built-in Providers ===");
        System.out.println("SUN: Main cryptographic provider");
        System.out.println("SunJCE: Java Cryptography Extension provider");
        System.out.println("SunRsaSign: RSA signature algorithms");
        System.out.println("SunEC: Elliptic Curve cryptography");
    }

    // Third-party providers (require dependency)
    public static void demonstrateThirdPartyProviders() {
        System.out.println("\n=== Third-Party Providers ===");
        System.out.println("Bouncy Castle: Comprehensive cryptography library");
        System.out.println("  - More algorithms than built-in");
        System.out.println("  - Better support for newer algorithms");
        System.out.println("  - Open source");

        System.out.println("Conscrypt: Modern cryptographic library");
        System.out.println("  - Provides BoringSSL via JNI");
        System.out.println("  - Better performance");
        System.out.println("  - Recommended for Android");
    }

    // Adding Bouncy Castle provider
    public static void addBouncyCastleProvider() {
        // java.security.Security.addProvider(
        //     new org.bouncycastle.jce.provider.BouncyCastleProvider());

        // Then use: MessageDigest.getInstance("SHA-256", "BC");
    }
}

Best Practices

  • Use SecureRandom: Never use Random for cryptographic purposes.
  • Generate unique IVs/nonces: New random value for each encryption.
  • Use authenticated encryption: AES-GCM is recommended; always combine encryption with MAC if using CBC.
  • Secure key storage: Never hardcode keys; use key stores or secure configuration.
  • Recommend strong algorithms: AES-256 for symmetric, RSA-2048+ or ECDSA for asymmetric.
  • Validate key sizes: Ensure keys meet minimum security requirements.
  • Update deprecated algorithms: MD5, SHA-1, DES, 3DES should not be used for security.
  • Keep libraries updated: Regularly update Java and third-party crypto libraries.
  • Test cryptographic code: Use known test vectors to verify implementations.
  • Document algorithm choices: Explain why specific algorithms were selected.