22.4 Digital Signatures, Key Management, and Certificates

This section covers digital signatures for authenticity verification, secure key generation, storage, and X.509 certificate handling.

Digital Signatures: Concept and Purpose

Digital signatures prove the authenticity and non-repudiation of data using asymmetric cryptography:

// Digital Signatures Overview
public class DigitalSignaturesIntroduction {

    // What digital signatures provide:
    public static void printSignatureBenefits() {
        System.out.println("=== DIGITAL SIGNATURE BENEFITS ===");
        System.out.println("\n1. AUTHENTICITY: Verify sender identity");
        System.out.println("   - Only sender with private key could sign");
        System.out.println("   - Anyone can verify with public key");

        System.out.println("\n2. NON-REPUDIATION: Sender cannot deny");
        System.out.println("   - Signature proves sender created data");
        System.out.println("   - Cannot claim someone else signed it");

        System.out.println("\n3. INTEGRITY: Detect any tampering");
        System.out.println("   - Signature fails if data modified");
        System.out.println("   - Different plaintext = different signature");

        System.out.println("\nKey Difference from MAC:");
        System.out.println("- MAC: Both parties share same key (symmetric)");
        System.out.println("- Signature: Sender signs with private key, receiver verifies with public key");
    }

    // Signature workflow
    public static void printSignatureWorkflow() {
        System.out.println("\n=== SIGNATURE WORKFLOW ===");
        System.out.println("\nSIGNING:");
        System.out.println("1. Sender hashes data (SHA-256)");
        System.out.println("2. Sender encrypts hash with private key");
        System.out.println("3. Result is signature");
        System.out.println("4. Send: original data + signature");

        System.out.println("\nVERIFYING:");
        System.out.println("1. Receiver hashes received data (same algorithm)");
        System.out.println("2. Receiver decrypts signature with sender's public key");
        System.out.println("3. Compare computed hash with decrypted hash");
        System.out.println("4. If equal → signature valid → data authentic");
    }
}

RSA Digital Signatures

RSA with SHA-256 is a widely-used signature algorithm:

// RSA Digital Signatures
public class RSADigitalSignature {

    private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";

    // Generate RSA key pair for signing
    public static KeyPair generateSigningKeyPair() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
        keyPairGen.initialize(2048, new SecureRandom());
        return keyPairGen.generateKeyPair();
    }

    // Sign data with private key
    public static byte[] signData(byte[] data, PrivateKey privateKey) 
            throws Exception {
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(privateKey);
        signature.update(data);
        return signature.sign();
    }

    // Sign string with private key
    public static String signString(String data, PrivateKey privateKey) 
            throws Exception {
        byte[] signedBytes = signData(data.getBytes(StandardCharsets.UTF_8), privateKey);
        return Base64.getEncoder().encodeToString(signedBytes);
    }

    // Verify signature with public key
    public static boolean verifySignature(byte[] data, byte[] signature, 
                                         PublicKey publicKey) throws Exception {
        Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
        sig.initVerify(publicKey);
        sig.update(data);
        return sig.verify(signature);
    }

    // Verify base64-encoded signature
    public static boolean verifySignatureString(String data, String signatureStr, 
                                               PublicKey publicKey) throws Exception {
        byte[] decodedSignature = Base64.getDecoder().decode(signatureStr);
        return verifySignature(data.getBytes(StandardCharsets.UTF_8), 
                             decodedSignature, publicKey);
    }

    // Complete example
    public static void demonstrateRSASignature() throws Exception {
        // Generate key pair
        KeyPair keyPair = generateSigningKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        PublicKey publicKey = keyPair.getPublic();

        // Data to sign
        String document = "Important contract document";
        byte[] documentBytes = document.getBytes(StandardCharsets.UTF_8);

        // Create signature
        byte[] signature = signData(documentBytes, privateKey);
        System.out.println("Document: " + document);
        System.out.println("Signature: " + Base64.getEncoder().encodeToString(signature));

        // Verify signature
        boolean isValid = verifySignature(documentBytes, signature, publicKey);
        System.out.println("Signature valid: " + isValid);

        // Tampering detection
        byte[] tamperedDocument = "Altered contract".getBytes(StandardCharsets.UTF_8);
        boolean tamperDetected = !verifySignature(tamperedDocument, signature, publicKey);
        System.out.println("Tampering detected: " + tamperDetected);
    }

    // Signing large files
    public static byte[] signFile(String filePath, PrivateKey privateKey) 
            throws Exception {
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(privateKey);

        try (InputStream is = new FileInputStream(filePath)) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                signature.update(buffer, 0, bytesRead);
            }
        }

        return signature.sign();
    }

    // Verify file signature
    public static boolean verifyFileSignature(String filePath, byte[] signature, 
                                             PublicKey publicKey) throws Exception {
        Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
        sig.initVerify(publicKey);

        try (InputStream is = new FileInputStream(filePath)) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                sig.update(buffer, 0, bytesRead);
            }
        }

        return sig.verify(signature);
    }
}

ECDSA Digital Signatures

ECDSA provides stronger security with smaller key sizes:

// ECDSA (Elliptic Curve Digital Signature Algorithm)
public class ECDSASignature {

    // Generate ECDSA key pair
    public static KeyPair generateECDSAKeyPair(String curve) throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC");
        ECGenParameterSpec ecSpec = new ECGenParameterSpec(curve);
        keyPairGen.initialize(ecSpec, new SecureRandom());
        return keyPairGen.generateKeyPair();
    }

    // Sign with ECDSA-P256
    public static byte[] signWithECDSA(String data, PrivateKey privateKey) 
            throws Exception {
        Signature signature = Signature.getInstance("SHA256withECDSA");
        signature.initSign(privateKey);
        signature.update(data.getBytes(StandardCharsets.UTF_8));
        return signature.sign();
    }

    // Verify ECDSA signature
    public static boolean verifyECDSA(String data, byte[] signature, 
                                     PublicKey publicKey) throws Exception {
        Signature sig = Signature.getInstance("SHA256withECDSA");
        sig.initVerify(publicKey);
        sig.update(data.getBytes(StandardCharsets.UTF_8));
        return sig.verify(signature);
    }

    // ECDSA advantages
    public static void printECDSAAdvantages() {
        System.out.println("=== ECDSA VS RSA ===");
        System.out.println("\nECDSA Advantages:");
        System.out.println("  - P-256: equivalent to RSA 3072-bit");
        System.out.println("  - Smaller key size = faster computation");
        System.out.println("  - Smaller signatures = less data");
        System.out.println("  - Better performance on resource-constrained devices");

        System.out.println("\nKey Size Equivalence:");
        System.out.println("  P-256 ≈ RSA 3072-bit");
        System.out.println("  P-384 ≈ RSA 7680-bit");
        System.out.println("  P-521 ≈ RSA 15360-bit");
    }

    public static void demonstrateECDSA() throws Exception {
        KeyPair keyPair = generateECDSAKeyPair("secp256r1");
        String data = "Message to sign";

        byte[] signature = signWithECDSA(data, keyPair.getPrivate());
        boolean valid = verifyECDSA(data, signature, keyPair.getPublic());

        System.out.println("ECDSA signature valid: " + valid);
    }
}

EdDSA: Modern Signature Algorithm

EdDSA (Edwards-curve Digital Signature Algorithm) is recommended for new systems:

// EdDSA Digital Signatures
public class EdDSASignature {

    // Generate EdDSA key pair
    public static KeyPair generateEdDSAKeyPair() throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("Ed25519");
        return keyPairGen.generateKeyPair();
    }

    // Sign with EdDSA
    public static byte[] signWithEdDSA(String data, PrivateKey privateKey) 
            throws Exception {
        Signature signature = Signature.getInstance("Ed25519");
        signature.initSign(privateKey);
        signature.update(data.getBytes(StandardCharsets.UTF_8));
        return signature.sign();
    }

    // Verify EdDSA signature
    public static boolean verifyEdDSA(String data, byte[] signature, 
                                     PublicKey publicKey) throws Exception {
        Signature sig = Signature.getInstance("Ed25519");
        sig.initVerify(publicKey);
        sig.update(data.getBytes(StandardCharsets.UTF_8));
        return sig.verify(signature);
    }

    // EdDSA advantages
    public static void printEdDSAAdvantages() {
        System.out.println("=== EdDSA ADVANTAGES ===");
        System.out.println("- Modern cryptographic design");
        System.out.println("- Smaller key size: 256 bits for Ed25519");
        System.out.println("- High performance");
        System.out.println("- Deterministic (no random nonce needed)");
        System.out.println("- Resistant to timing attacks");
        System.out.println("- Excellent for TLS 1.3");
        System.out.println("\nRECOMMENDED for new systems!");
    }

    public static void demonstrateEdDSA() throws Exception {
        KeyPair keyPair = generateEdDSAKeyPair();
        String data = "Message to sign";

        byte[] signature = signWithEdDSA(data, keyPair.getPrivate());
        boolean valid = verifyEdDSA(data, signature, keyPair.getPublic());

        System.out.println("EdDSA signature valid: " + valid);
    }
}

Key Generation and Management

// Cryptographic Key Generation
public class KeyGeneration {

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

    // Generate RSA key pair
    public static KeyPair generateRSAKeyPair(int keySize) 
            throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
        keyPairGen.initialize(keySize, new SecureRandom());
        return keyPairGen.generateKeyPair();
    }

    // Generate EC key pair
    public static KeyPair generateECKeyPair(String curve) throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC");
        ECGenParameterSpec ecSpec = new ECGenParameterSpec(curve);
        keyPairGen.initialize(ecSpec, new SecureRandom());
        return keyPairGen.generateKeyPair();
    }

    // Key derivation from password
    public static SecretKey deriveKeyFromPassword(String password, byte[] salt) 
            throws Exception {
        PBEKeySpec spec = new PBEKeySpec(
            password.toCharArray(),
            salt,
            100000, // iterations
            256     // key size in bits
        );

        try {
            SecretKeyFactory factory = SecretKeyFactory.getInstance(
                "PBKDF2WithHmacSHA256");
            return factory.generateSecret(spec);
        } finally {
            spec.clearPassword();
        }
    }

    // Print recommended key sizes
    public static void printRecommendedKeySizes() {
        System.out.println("=== RECOMMENDED KEY SIZES ===");
        System.out.println("\nSymmetric Keys:");
        System.out.println("  AES: 256 bits (recommended)");
        System.out.println("  AES: 192 bits (acceptable)");
        System.out.println("  AES: 128 bits (minimum)");

        System.out.println("\nAsymmetric Keys:");
        System.out.println("  RSA: 4096 bits (high security)");
        System.out.println("  RSA: 2048 bits (minimum)");
        System.out.println("  ECDSA P-256: Strong, equivalent to RSA 3072");
        System.out.println("  EdDSA Ed25519: Recommended");
    }
}

Java KeyStore (JKS) Management

KeyStore securely stores keys and certificates:

// KeyStore Management
public class KeyStoreManagement {

    private static final String KEYSTORE_TYPE = "PKCS12"; // Better than JKS

    // Create and save KeyStore
    public static void createKeyStore(String keystorePath, String keystorePassword, 
                                     String alias, PrivateKey privateKey,
                                     java.security.cert.Certificate[] certChain) 
            throws Exception {
        KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
        keyStore.load(null, keystorePassword.toCharArray());

        keyStore.setKeyEntry(alias, privateKey, 
                           keystorePassword.toCharArray(), certChain);

        try (FileOutputStream fos = new FileOutputStream(keystorePath)) {
            keyStore.store(fos, keystorePassword.toCharArray());
        }
    }

    // Load KeyStore
    public static KeyStore loadKeyStore(String keystorePath, 
                                       String keystorePassword) 
            throws Exception {
        KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);

        try (FileInputStream fis = new FileInputStream(keystorePath)) {
            keyStore.load(fis, keystorePassword.toCharArray());
        }

        return keyStore;
    }

    // Get private key from KeyStore
    public static PrivateKey getPrivateKey(KeyStore keyStore, String alias, 
                                          String keyPassword) throws Exception {
        KeyStore.PrivateKeyEntry entry = 
            (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias,
                new KeyStore.PasswordProtection(keyPassword.toCharArray()));

        return entry.getPrivateKey();
    }

    // Get certificate from KeyStore
    public static java.security.cert.Certificate getCertificate(KeyStore keyStore, 
                                                                String alias) 
            throws KeyStoreException {
        return keyStore.getCertificate(alias);
    }

    // List all aliases in KeyStore
    public static void listKeyStoreEntries(KeyStore keyStore) 
            throws KeyStoreException {
        java.util.Enumeration<String> aliases = keyStore.aliases();
        while (aliases.hasMoreElements()) {
            String alias = aliases.nextElement();
            System.out.println("Alias: " + alias);
            if (keyStore.isKeyEntry(alias)) {
                System.out.println("  Type: Private Key Entry");
            } else if (keyStore.isCertificateEntry(alias)) {
                System.out.println("  Type: Certificate Entry");
            }
        }
    }

    // Complete example
    public static void demonstrateKeyStore() throws Exception {
        String keystorePath = "my-keystore.p12";
        String keystorePassword = "storepass";
        String keyPassword = "keypass";

        // Create new KeyStore with generated key
        KeyPair keyPair = KeyGeneration.generateRSAKeyPair(2048);
        java.security.cert.Certificate[] certChain = new java.security.cert.Certificate[0];

        createKeyStore(keystorePath, keystorePassword, "my-key",
                      keyPair.getPrivate(), certChain);

        // Load KeyStore
        KeyStore keyStore = loadKeyStore(keystorePath, keystorePassword);

        // List entries
        listKeyStoreEntries(keyStore);

        // Get private key
        PrivateKey privateKey = getPrivateKey(keyStore, "my-key", keyPassword);
        System.out.println("Retrieved private key: " + privateKey.getAlgorithm());
    }

    // Best practices
    public static void printKeyStoreBestPractices() {
        System.out.println("=== KEYSTORE BEST PRACTICES ===");
        System.out.println("\n1. Use PKCS12 format");
        System.out.println("   - Better than JKS");
        System.out.println("   - Standard format");

        System.out.println("\n2. Protect with strong password");
        System.out.println("   - Keystore password protects all keys");
        System.out.println("   - Use strong, unique password");

        System.out.println("\n3. Separate key passwords");
        System.out.println("   - Each key can have own password");
        System.out.println("   - Adds extra layer of protection");

        System.out.println("\n4. Restrict file permissions");
        System.out.println("   - chmod 600 for Unix systems");
        System.out.println("   - Only owner can read");

        System.out.println("\n5. Never hardcode passwords");
        System.out.println("   - Load from environment or config");
        System.out.println("   - Use secure vaults for production");

        System.out.println("\n6. Backup securely");
        System.out.println("   - Keep encrypted copies");
        System.out.println("   - Separate location from application");
    }
}

X.509 Certificates

// X.509 Certificate Handling
public class X509CertificateHandling {

    // Parse certificate from PEM file
    public static java.security.cert.X509Certificate loadCertificate(String certPath) 
            throws Exception {
        try (InputStream is = new FileInputStream(certPath)) {
            CertificateFactory factory = CertificateFactory.getInstance("X.509");
            return (java.security.cert.X509Certificate) factory.generateCertificate(is);
        }
    }

    // Get certificate details
    public static void printCertificateDetails(java.security.cert.X509Certificate cert) {
        System.out.println("=== CERTIFICATE DETAILS ===");
        System.out.println("Subject: " + cert.getSubjectDN());
        System.out.println("Issuer: " + cert.getIssuerDN());
        System.out.println("Serial Number: " + cert.getSerialNumber());
        System.out.println("Not Before: " + cert.getNotBefore());
        System.out.println("Not After: " + cert.getNotAfter());
        System.out.println("Signature Algorithm: " + cert.getSigAlgName());
        System.out.println("Public Key Algorithm: " + cert.getPublicKey().getAlgorithm());
    }

    // Verify certificate signature
    public static boolean verifyCertificate(java.security.cert.X509Certificate cert, 
                                           PublicKey issuerPublicKey) throws Exception {
        try {
            cert.verify(issuerPublicKey);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    // Certificate validation
    public static class CertificateValidator {

        public static boolean isValidForDate(java.security.cert.X509Certificate cert,
                                            java.util.Date date) {
            try {
                cert.checkValidity(date);
                return true;
            } catch (java.security.cert.CertificateExpiredException |
                     java.security.cert.CertificateNotYetValidException e) {
                return false;
            }
        }

        public static boolean isCurrentlyValid(java.security.cert.X509Certificate cert) {
            return isValidForDate(cert, new java.util.Date());
        }

        public static long getDaysUntilExpiration(java.security.cert.X509Certificate cert) {
            long expirationTime = cert.getNotAfter().getTime();
            long currentTime = System.currentTimeMillis();
            return (expirationTime - currentTime) / (1000 * 60 * 60 * 24);
        }
    }
}

Signature Verification in Practice

// Practical Signature Usage
public class SignatureVerificationExample {

    // Sign a message and encode for transmission
    public static String createSignedMessage(String message, PrivateKey privateKey) 
            throws Exception {
        byte[] signature = RSADigitalSignature.signData(
            message.getBytes(StandardCharsets.UTF_8), privateKey);

        // Format: message|||signature (base64)
        return message + "|||" + Base64.getEncoder().encodeToString(signature);
    }

    // Verify and extract message
    public static String verifySignedMessage(String signedMessage, PublicKey publicKey) 
            throws Exception {
        String[] parts = signedMessage.split("\\|\\|\\|");
        if (parts.length != 2) {
            throw new IllegalArgumentException("Invalid signed message format");
        }

        String message = parts[0];
        byte[] signature = Base64.getDecoder().decode(parts[1]);

        boolean valid = RSADigitalSignature.verifySignature(
            message.getBytes(StandardCharsets.UTF_8), signature, publicKey);

        if (!valid) {
            throw new SecurityException("Signature verification failed");
        }

        return message;
    }

    // Complete transaction example
    public static class SignedTransaction {
        public String transactionId;
        public String data;
        public byte[] signature;
        public PublicKey signerPublicKey;

        public boolean verify() throws Exception {
            return RSADigitalSignature.verifySignature(
                data.getBytes(StandardCharsets.UTF_8), signature, signerPublicKey);
        }
    }
}

Best Practices for Signatures and Keys

  • Use RSA-2048+ or ECDSA/EdDSA: Modern algorithms for security.
  • Store private keys securely: Use KeyStore (PKCS12), HSM, or KMS.
  • Never hardcode keys: Load from secure storage at runtime.
  • Protect KeyStore files: Restrict file permissions (chmod 600 Unix).
  • Use strong KeyStore passwords: Unique, complex passwords.
  • Verify certificate validity: Check not-before, not-after dates.
  • Verify certificate chain: Ensure issuer is trusted.
  • Use constant-time comparison: For signature verification.
  • Implement proper error handling: Don't leak key information.
  • Regular key rotation: Update keys periodically.
  • Separate concerns: Signing/verification logic isolated from business logic.
  • Log signature events: Track who signed what and when.