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.