Security Practices

Understanding Java Security

Security is not a feature - it’s a property of the entire system. Java applications face threats from injection attacks, broken authentication, sensitive data exposure, and more. Defense-in-depth requires layered security controls at every tier.

Why security must be systematic:

  • Attackers need one vulnerability: Defenders must secure everything
  • Bugs become exploits: Security flaws enable unauthorized access
  • Compliance requirements: GDPR, HIPAA, PCI-DSS mandate security
  • Reputation damage: Breaches destroy trust and business

This guide covers essential security patterns: input validation, cryptography, authentication, password management, and OWASP Top 10 mitigations.

Input Validation - First Line of Defense

Problem: Trusting user input enables injection attacks (SQL injection, XSS, command injection), buffer overflows, and data corruption.

Recognition signals:

  • Concatenating user input into SQL queries
  • Passing user input to system commands
  • Accepting any string length without bounds
  • No type validation before processing
  • Rendering user input in HTML without escaping

Solution: Validate all input at system boundaries with allowlist-based validation.

CharacteristicUnsafe ApproachSecure Approach
Validation strategyBlacklist (block known bad)Allowlist (permit known good)
LocationBusiness logic layerEntry point (boundary)
SQL queriesString concatenationParameterized queries
Command executionDirect string interpolationAvoid or strictly validate
Data bindingTrust framework defaultsExplicit validation rules

SQL Injection Prevention

// => VULNERABLE: SQL Injection via string concatenation
public User findUser(String username) {
    String query = "SELECT * FROM users WHERE username = '" + username + "'";
    // => ATTACK: username = "admin' OR '1'='1"
    // => Concatenation creates: SELECT * FROM users WHERE username = 'admin' OR '1'='1'
    // => RESULT: Returns ALL users (OR '1'='1' is always true)
    // => DANGER: Bypasses authentication completely
    return jdbcTemplate.queryForObject(query, userRowMapper);
    // => Executes malicious SQL directly
}

// => SECURE: Parameterized Queries prevent injection
public User findUser(String username) {
    String query = "SELECT * FROM users WHERE username = ?";
    // => Placeholder: ? marks parameter position
    return jdbcTemplate.queryForObject(query, userRowMapper, username);
    // => username passed as separate parameter (not concatenated)
    // => Database treats username as DATA, not SQL code
    // => SAFE: "admin' OR '1'='1" treated as literal string
    // => Returns: Only user with exact username match
}

XSS Prevention

// VULNERABLE: Stored XSS
public String displayComment(String userComment) {
    return "<div>" + userComment + "</div>";
    // ATTACK: userComment = "<script>alert('XSS')</script>"
}

// SECURE: HTML Escaping
import org.apache.commons.text.StringEscapeUtils;

public String displayComment(String userComment) {
    String escaped = StringEscapeUtils.escapeHtml4(userComment);
    return "<div>" + escaped + "</div>";
    // OUTPUT: <div>&lt;script&gt;alert('XSS')&lt;/script&gt;</div>
}

Command Injection Prevention

// VULNERABLE: Command Injection
public void processFile(String filename) {
    String command = "cat " + filename;
    Runtime.getRuntime().exec(command);  // DANGEROUS
    // ATTACK: filename = "file.txt; rm -rf /"
}

// SECURE: Avoid Shell Execution
public void processFile(Path filepath) {
    // VALIDATE: File exists and is within allowed directory
    if (!filepath.startsWith(ALLOWED_DIRECTORY)) {
        throw new SecurityException("Access denied");
    }
    // READ DIRECTLY: No shell involved
    String content = Files.readString(filepath);
}

Input Validation Pattern

public record UserInput(String username, String email, int age) {
    private static final Pattern USERNAME_PATTERN =
        Pattern.compile("^[a-zA-Z0-9_]{3,20}$");
    private static final Pattern EMAIL_PATTERN =
        Pattern.compile("^[^@]+@[^@]+\\.[^@]+$");

    public UserInput {  // COMPACT CONSTRUCTOR
        // ALLOWLIST: Only alphanumeric and underscore
        if (!USERNAME_PATTERN.matcher(username).matches()) {
            throw new IllegalArgumentException("Invalid username format");
        }
        // EMAIL VALIDATION
        if (!EMAIL_PATTERN.matcher(email).matches()) {
            throw new IllegalArgumentException("Invalid email format");
        }
        // RANGE CHECK
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("Invalid age");
        }
    }
}

Validation principles:

  • Validate at boundaries: Entry points, not business logic
  • Allowlist over blacklist: Define what’s permitted
  • Type safety: Convert strings to typed objects early
  • Length limits: Prevent buffer-related issues
  • Encode outputs: Escape based on context (HTML, SQL, JavaScript)

Cryptography - Protecting Data at Rest and in Transit

Problem: Weak cryptography (DES, MD5, SHA-1, ECB mode) provides false sense of security. Custom crypto implementations have subtle flaws.

Recognition signals:

  • Using DES, 3DES, or RC4 ciphers
  • MD5 or SHA-1 for hashing
  • ECB mode for block ciphers
  • Hardcoded encryption keys
  • Custom cryptographic algorithms
  • Ignoring certificate validation in SSL/TLS

Solution: Use strong, modern algorithms with recommended configurations. Never implement custom crypto.

Encryption Standards

PurposeWeak/DeprecatedStrong/Modern
Symmetric encryptionDES, 3DES, RC4AES-256-GCM
Asymmetric encryptionRSA < 2048 bitsRSA 2048+ bits, ECC
HashingMD5, SHA-1SHA-256, SHA-3
Password hashingPlain hashingbcrypt, Argon2, PBKDF2
Block cipher modeECBGCM, CBC with HMAC

AES-GCM Encryption

import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.SecureRandom;

public class SecureEncryption {
    private static final String ALGORITHM = "AES/GCM/NoPadding";
    // => AES-256 with Galois/Counter Mode (authenticated encryption)
    private static final int GCM_IV_LENGTH = 12;  // 96 bits
    // => Initialization Vector: 12 bytes recommended for GCM
    private static final int GCM_TAG_LENGTH = 128;  // 128 bits
    // => Authentication tag: 128 bits for integrity verification

    public record EncryptedData(byte[] ciphertext, byte[] iv) {}
    // => VALUE OBJECT: Holds encrypted data + IV (both needed for decryption)

    public static EncryptedData encrypt(byte[] plaintext, SecretKey key)
            throws GeneralSecurityException {
        // => CRITICAL: Generate random IV for each encryption
        byte[] iv = new byte[GCM_IV_LENGTH];
        SecureRandom random = new SecureRandom();
        // => SecureRandom: Cryptographically strong random number generator
        random.nextBytes(iv);
        // => iv is now 12 random bytes (never reuse with same key!)

        Cipher cipher = Cipher.getInstance(ALGORITHM);
        // => Get AES/GCM/NoPadding cipher instance
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
        // => spec contains: tag length (128 bits) + IV (12 bytes)
        cipher.init(Cipher.ENCRYPT_MODE, key, spec);
        // => Initialize cipher: ENCRYPT mode with key and GCM parameters

        byte[] ciphertext = cipher.doFinal(plaintext);
        // => Encrypt plaintext and append authentication tag
        // => Output: encrypted data + 16-byte authentication tag
        return new EncryptedData(ciphertext, iv);
        // => RETURN: Both ciphertext and IV (IV not secret, needed for decryption)
    }

    public static byte[] decrypt(EncryptedData data, SecretKey key)
            throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, data.iv());
        // => Use SAME IV that was used for encryption
        cipher.init(Cipher.DECRYPT_MODE, key, spec);
        // => Initialize cipher: DECRYPT mode with key and GCM parameters

        return cipher.doFinal(data.ciphertext());
        // => Decrypt and verify authentication tag
        // => THROWS: AEADBadTagException if data tampered or wrong key
        // => GCM provides: Confidentiality (encryption) + Integrity (authentication)
    }

    public static SecretKey generateKey() throws NoSuchAlgorithmException {
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        // => Get AES key generator
        keyGen.init(256);
        // => Generate 256-bit key (AES-256)
        return keyGen.generateKey();
        // => Returns: SecretKey suitable for AES-256 encryption
    }
}

Critical requirements:

  • Random IVs: Never reuse initialization vectors
  • Authenticated encryption: GCM provides confidentiality + integrity
  • Key management: Store keys securely (key management service, HSM)
  • Key rotation: Periodically rotate encryption keys

SSL/TLS Best Practices

// VULNERABLE: Disabling certificate validation
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new SecureRandom());  // DANGEROUS

// SECURE: Use default trust store
SSLContext sc = SSLContext.getInstance("TLSv1.3");
sc.init(null, null, null);  // Uses system's trusted CAs

HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(sc.getSocketFactory());
// VALIDATES: Certificate chain, hostname, expiration

TLS configuration:

  • Minimum TLS 1.2: Disable SSL, TLS 1.0, TLS 1.1
  • Strong cipher suites: Prefer AEAD ciphers (GCM)
  • Certificate validation: Never disable hostname/certificate verification
  • Certificate pinning: For mobile apps, pin leaf or CA certificates

Authentication - Verifying Identity

Problem: Weak authentication allows unauthorized access. Broken session management enables session hijacking.

Recognition signals:

  • Accepting weak passwords
  • Storing passwords in plaintext or weakly hashed
  • No multi-factor authentication
  • Sessions never expire
  • Session IDs in URLs
  • No CSRF protection

Solution: Strong authentication mechanisms with defense-in-depth.

Password Security

Never store plaintext passwords. Use adaptive hashing with salt.

import at.favre.lib.crypto.bcrypt.BCrypt;

public class PasswordManager {
    private static final int BCRYPT_COST = 12;
    // => COST FACTOR: 2^12 = 4096 iterations
    // => Higher cost = slower hashing = more secure against brute force
    // => Cost 12: ~150-300ms per hash (balance security vs UX)

    public static String hashPassword(String plaintext) {
        return BCrypt.withDefaults()
            .hashToString(BCRYPT_COST, plaintext.toCharArray());
        // => Hashes password with 2^12 iterations
        // => SALT: Automatically generated (random, unique per password)
        // => Salt stored IN hash string (no separate storage needed)
        // => OUTPUT FORMAT: $2a$12$[22-char salt][31-char hash]
        // => Example: $2a$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
    }

    public static boolean verifyPassword(String plaintext, String hash) {
        BCrypt.Result result = BCrypt.verifyer()
            .verify(plaintext.toCharArray(), hash);
        // => Extracts salt from hash string
        // => Re-hashes plaintext with extracted salt
        // => Compares hashes in constant time
        return result.verified;
        // => Returns: true if plaintext matches, false otherwise
        // => CONSTANT-TIME: Takes same time regardless of match
        // => Prevents: Timing attacks (attacker can't guess by timing)
    }
}

Password requirements:

CharacteristicWeakStrong
Minimum length< 8 characters12+ characters
ComplexitySingle typeMixed (upper, lower, digit, symbol)
StoragePlaintext/MD5/SHA-1bcrypt/Argon2/PBKDF2
Reuse preventionNot checkedCheck against breached passwords
RotationForced monthlyOnly on compromise

Session Management

// VULNERABLE: Predictable session IDs
String sessionId = username + "_" + System.currentTimeMillis();

// SECURE: Cryptographically random session IDs
import java.security.SecureRandom;

public class SessionManager {
    private static final SecureRandom random = new SecureRandom();
    private static final int TOKEN_LENGTH = 32;  // 256 bits

    public static String generateSessionToken() {
        byte[] token = new byte[TOKEN_LENGTH];
        random.nextBytes(token);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(token);
        // OUTPUT: URL-safe, 256-bit random token
    }

    public static void setSessionCookie(HttpServletResponse response, String token) {
        Cookie cookie = new Cookie("SESSIONID", token);
        cookie.setHttpOnly(true);  // PREVENT: JavaScript access (XSS protection)
        cookie.setSecure(true);  // REQUIRE: HTTPS only
        cookie.setPath("/");
        cookie.setMaxAge(1800);  // 30 minutes
        cookie.setSameSite("Strict");  // CSRF protection
        response.addCookie(cookie);
    }
}

Session security checklist:

  • ✓ Cryptographically random session IDs (256+ bits)
  • ✓ HttpOnly cookie flag (prevents XSS theft)
  • ✓ Secure cookie flag (HTTPS only)
  • ✓ SameSite=Strict/Lax (CSRF protection)
  • ✓ Session expiration (idle timeout + absolute timeout)
  • ✓ Session fixation protection (regenerate ID after login)

Multi-Factor Authentication

import com.warrenstrange.googleauth.*;

public class TwoFactorAuth {
    private final GoogleAuthenticator gAuth = new GoogleAuthenticator();

    public String generateSecret() {
        GoogleAuthenticatorKey key = gAuth.createCredentials();
        return key.getKey();  // Share with user (QR code)
    }

    public boolean verifyCode(String secret, int code) {
        return gAuth.authorize(secret, code);
        // TOTP: Time-based One-Time Password
        // WINDOW: Accepts codes within ±30 seconds
    }
}

OWASP Top 10 Mitigations

1. Injection Attacks

Mitigation:

  • Parameterized queries (PreparedStatement)
  • Input validation (allowlist)
  • Least privilege database accounts
  • ORM frameworks (with safe usage)

2. Broken Authentication

Mitigation:

  • Strong password hashing (bcrypt, Argon2)
  • Multi-factor authentication
  • Secure session management
  • Account lockout after failed attempts

3. Sensitive Data Exposure

Mitigation:

  • Encrypt data at rest (AES-256-GCM)
  • Encrypt data in transit (TLS 1.2+)
  • Never log sensitive data (passwords, tokens)
  • Secure key management

4. XML External Entities (XXE)

// VULNERABLE: XXE Attack
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(untrustedXML);  // DANGEROUS

// SECURE: Disable external entities
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);

5. Broken Access Control

Mitigation:

  • Deny by default
  • Enforce access control on every request
  • Disable directory listing
  • Check permissions server-side (never client-side)
public void deleteUser(String userId, User currentUser) {
    // AUTHORIZATION CHECK
    if (!currentUser.hasRole("ADMIN")) {
        throw new AccessDeniedException("Insufficient privileges");
    }
    // OWNERSHIP CHECK
    if (userId.equals(currentUser.getId()) && !currentUser.isSuperAdmin()) {
        throw new AccessDeniedException("Cannot delete own account");
    }
    userRepository.delete(userId);
}

6. Security Misconfiguration

Mitigation:

  • Remove default accounts
  • Disable unnecessary features
  • Keep frameworks updated
  • Use security headers (CSP, HSTS, X-Frame-Options)
// Security Headers
response.setHeader("Content-Security-Policy", "default-src 'self'");
response.setHeader("X-Frame-Options", "DENY");
response.setHeader("X-Content-Type-Options", "nosniff");
response.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");

7. Cross-Site Scripting (XSS)

Mitigation:

  • Escape all user-generated content
  • Content Security Policy headers
  • HttpOnly cookies
  • Modern frameworks with auto-escaping

8. Insecure Deserialization

// VULNERABLE: Arbitrary code execution
ObjectInputStream ois = new ObjectInputStream(untrustedInput);
Object obj = ois.readObject();  // DANGEROUS: Can execute malicious code

// SECURE: Avoid Java serialization with untrusted data
// Alternative: Use JSON/XML with schema validation
ObjectMapper mapper = new ObjectMapper();
MyClass obj = mapper.readValue(jsonInput, MyClass.class);  // SAFE

9. Using Components with Known Vulnerabilities

Mitigation:

  • Dependency scanning (OWASP Dependency-Check, Snyk)
  • Keep dependencies updated
  • Monitor security advisories
  • Remove unused dependencies
<!-- Maven: Check for vulnerabilities -->
<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <executions>
        <execution>
            <goals><goal>check</goal></goals>
        </execution>
    </executions>
</plugin>

10. Insufficient Logging & Monitoring

Mitigation:

  • Log authentication failures
  • Log access control failures
  • Log input validation failures
  • Centralized logging
  • Alert on suspicious patterns
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SecurityLogger {
    private static final Logger securityLog = LoggerFactory.getLogger("SECURITY");

    public static void logFailedLogin(String username, String ipAddress) {
        securityLog.warn("Failed login attempt: username={}, ip={}",
            username, ipAddress);
        // NEVER LOG: Passwords, tokens, sensitive data
    }

    public static void logAccessDenied(String userId, String resource) {
        securityLog.warn("Access denied: user={}, resource={}",
            userId, resource);
    }
}

Security Checklist

Input Validation

  • All user input validated at entry points
  • Parameterized queries for SQL
  • HTML escaping for output
  • Length limits enforced
  • Type validation before processing

Cryptography

  • AES-256-GCM for symmetric encryption
  • Random IVs for each encryption
  • bcrypt/Argon2 for password hashing
  • TLS 1.2+ for network communication
  • Secure key management (not hardcoded)

Authentication & Authorization

  • Strong password requirements
  • Multi-factor authentication for sensitive operations
  • Secure session management
  • Authorization checks on every request
  • Account lockout after failed attempts

Configuration

  • Security headers configured
  • Default accounts removed
  • Error messages don’t leak information
  • Dependency vulnerabilities monitored
  • Logging configured (excluding sensitive data)

Conclusion

Java security requires defense-in-depth:

  • Input validation: Never trust user input
  • Cryptography: Use strong, standard algorithms
  • Authentication: Verify identity with multiple factors
  • Authorization: Check permissions on every operation
  • OWASP Top 10: Understand and mitigate common vulnerabilities

Security is not optional. Integrate security practices throughout the development lifecycle: design review, secure coding, testing, and monitoring. Regular security audits and penetration testing identify weaknesses before attackers do.

Last updated