Java Security
Secure Java coding practices — input validation, OWASP Top 10 for Java, KeyStore, TLS/HTTPS setup, and protecting against common vulnerabilities.
Input Validation
All data from external sources — HTTP parameters, headers, database results, file contents, environment variables — is untrusted:
import java.util.regex.*;
public class InputValidator {
// Whitelist validation — only allow known-good characters
private static final Pattern ALPHANUMERIC = Pattern.compile("^[a-zA-Z0-9_\\-]{1,50}$");
private static final Pattern EMAIL = Pattern.compile(
"^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"
);
public static String requireValidUsername(String input) {
if (input == null || !ALPHANUMERIC.matcher(input).matches()) {
throw new IllegalArgumentException("Invalid username format");
}
return input;
}
public static String requireValidEmail(String input) {
if (input == null || !EMAIL.matcher(input.trim()).matches()) {
throw new IllegalArgumentException("Invalid email format");
}
return input.trim().toLowerCase();
}
// Length limits — prevent resource exhaustion
public static String requireLength(String input, int minLen, int maxLen, String fieldName) {
Objects.requireNonNull(input, fieldName + " must not be null");
if (input.length() < minLen || input.length() > maxLen) {
throw new IllegalArgumentException(
fieldName + " must be between " + minLen + " and " + maxLen + " characters"
);
}
return input;
}
// Numeric range validation
public static int requireInRange(int value, int min, int max, String fieldName) {
if (value < min || value > max) {
throw new IllegalArgumentException(
fieldName + " must be between " + min + " and " + max
);
}
return value;
}
}
SQL Injection Prevention
import java.sql.*;
public class UserRepository {
// NEVER do this — SQL injection vulnerability
public User findByUsernameInsecure(Connection conn, String username) throws SQLException {
// If username is: ' OR '1'='1 → returns all users
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql); // DANGEROUS
// ...
}
// ALWAYS use PreparedStatement with parameter binding
public Optional<User> findByUsername(Connection conn, String username) throws SQLException {
String sql = "SELECT id, username, email FROM users WHERE username = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, username); // safely bound — no injection possible
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return Optional.of(new User(rs.getLong("id"),
rs.getString("username"),
rs.getString("email")));
}
return Optional.empty();
}
}
}
// Dynamic queries — still use PreparedStatement
public List<User> findUsers(Connection conn, String role, boolean activeOnly)
throws SQLException {
StringBuilder sql = new StringBuilder(
"SELECT id, username, email FROM users WHERE role = ?");
if (activeOnly) sql.append(" AND active = TRUE");
sql.append(" ORDER BY username");
try (PreparedStatement ps = conn.prepareStatement(sql.toString())) {
ps.setString(1, role);
// ...
}
}
}
Password Hashing
// Using Spring Security's BCryptPasswordEncoder
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
public class PasswordService {
// BCrypt with strength 12 (default is 10; each +1 doubles the work)
private final PasswordEncoder encoder = new BCryptPasswordEncoder(12);
public String hashPassword(String rawPassword) {
// BCrypt auto-generates a unique salt
return encoder.encode(rawPassword);
// Result: $2a$12$... (60-char string containing salt + hash)
}
public boolean verifyPassword(String rawPassword, String storedHash) {
return encoder.matches(rawPassword, storedHash);
}
}
// Using Java's built-in PBKDF2 (no external dependency)
import javax.crypto.spec.*;
import javax.crypto.*;
import java.security.*;
public class Pbkdf2PasswordService {
private static final int ITERATIONS = 310_000; // OWASP 2023 recommendation
private static final int KEY_LENGTH = 256;
private static final String ALGORITHM = "PBKDF2WithHmacSHA256";
public byte[] hash(char[] password, byte[] salt) throws Exception {
var spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
var factory = SecretKeyFactory.getInstance(ALGORITHM);
return factory.generateSecret(spec).getEncoded();
}
public byte[] generateSalt() {
byte[] salt = new byte[16];
new SecureRandom().nextBytes(salt);
return salt;
}
}
Secure Random and Token Generation
import java.security.SecureRandom;
import java.util.Base64;
import java.util.HexFormat;
public class TokenGenerator {
// NEVER use java.util.Random for security-sensitive values
// Math.random() and new Random() are NOT cryptographically secure
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
// Generate a URL-safe token (e.g., password reset, session ID)
public static String generateToken(int byteLength) {
byte[] bytes = new byte[byteLength];
SECURE_RANDOM.nextBytes(bytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
// 32 bytes = 256 bits = 43 base64 characters
public static String generateSessionToken() {
return generateToken(32);
}
// Generate a numeric OTP
public static String generateOtp(int digits) {
long max = (long) Math.pow(10, digits);
long otp = Math.abs(SECURE_RANDOM.nextLong()) % max;
return String.format("%0" + digits + "d", otp);
}
}
// Usage
String resetToken = TokenGenerator.generateToken(32);
// "Xk2p_Rz8vQ3mNj7LwEi1Oa5Yh6Fc9Ub4Td0Sv2" (example — unpredictable)
Encryption with JCA
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
import java.util.Base64;
public class AesEncryption {
private static final String ALGORITHM = "AES/GCM/NoPadding";
private static final int KEY_BITS = 256;
private static final int IV_BYTES = 12; // 96-bit IV for GCM
private static final int TAG_BITS = 128; // GCM authentication tag
public static SecretKey generateKey() throws Exception {
KeyGenerator gen = KeyGenerator.getInstance("AES");
gen.init(KEY_BITS, new SecureRandom());
return gen.generateKey();
}
// Returns IV + ciphertext (IV must be stored alongside the ciphertext)
public static byte[] encrypt(byte[] plaintext, SecretKey key) throws Exception {
byte[] iv = new byte[IV_BYTES];
new SecureRandom().nextBytes(iv);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(TAG_BITS, iv));
byte[] ciphertext = cipher.doFinal(plaintext);
// Prepend IV to ciphertext
byte[] result = new byte[IV_BYTES + ciphertext.length];
System.arraycopy(iv, 0, result, 0, IV_BYTES);
System.arraycopy(ciphertext, 0, result, IV_BYTES, ciphertext.length);
return result;
}
public static byte[] decrypt(byte[] ivAndCiphertext, SecretKey key) throws Exception {
byte[] iv = new byte[IV_BYTES];
byte[] ciphertext = new byte[ivAndCiphertext.length - IV_BYTES];
System.arraycopy(ivAndCiphertext, 0, iv, 0, IV_BYTES);
System.arraycopy(ivAndCiphertext, IV_BYTES, ciphertext, 0, ciphertext.length);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(TAG_BITS, iv));
return cipher.doFinal(ciphertext); // throws AEADBadTagException if tampered
}
}
KeyStore — Managing Keys and Certificates
import java.security.*;
import java.security.cert.*;
import javax.net.ssl.*;
import java.io.*;
import java.nio.file.*;
public class KeyStoreDemo {
// Load a KeyStore from a PKCS12 file (.p12 / .pfx)
public static KeyStore loadKeyStore(Path path, char[] password) throws Exception {
KeyStore ks = KeyStore.getInstance("PKCS12");
try (InputStream is = Files.newInputStream(path)) {
ks.load(is, password);
}
return ks;
}
// Create an SSLContext that uses a custom keystore (e.g., mutual TLS)
public static SSLContext createSslContext(KeyStore keyStore, char[] keyPassword,
KeyStore trustStore) throws Exception {
KeyManagerFactory kmf = KeyManagerFactory.getInstance(
KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keyPassword);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return ctx;
}
}
HTTPS with HttpClient
import java.net.http.*;
import java.net.URI;
public class SecureHttpClient {
// Default HttpClient already validates TLS certificates
private static final HttpClient CLIENT = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
public static String get(String url) throws Exception {
var request = HttpRequest.newBuilder(URI.create(url))
.GET()
.header("Accept", "application/json")
.build();
var response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("Unexpected status: " + response.statusCode());
}
return response.body();
}
// Custom trust store (e.g., internal CA)
public static HttpClient buildWithCustomTrustStore(SSLContext sslContext) {
return HttpClient.newBuilder()
.sslContext(sslContext)
.build();
}
}
Security Checklist
OWASP Top 10 for Java
| # | Vulnerability | Java mitigation |
|---|---|---|
| A01 | Broken Access Control | Spring Security @PreAuthorize, method-level security |
| A02 | Cryptographic Failures | Use AES-256-GCM, BCrypt; no MD5/SHA-1 for passwords |
| A03 | Injection (SQL, LDAP, OS) | PreparedStatement, parameterised queries, no string concat |
| A04 | Insecure Design | Threat modelling, defence in depth |
| A05 | Security Misconfiguration | Disable debug endpoints, no default credentials |
| A06 | Vulnerable Components | mvn versions:display-dependency-updates, Dependabot |
| A07 | Auth & Session Failures | SecureRandom tokens, short session expiry, rate limiting |
| A08 | Software Integrity Failures | Verify dependency checksums, sign JARs |
| A09 | Security Logging & Monitoring | Log auth events, anomaly detection |
| A10 | SSRF | Validate and allowlist outbound URLs |
// Path traversal prevention
public byte[] readUserFile(String filename) throws IOException {
// Bad — attacker passes "../../etc/passwd"
// return Files.readAllBytes(Path.of("/user-files/" + filename));
// Good — resolve and verify the path stays inside the allowed directory
Path base = Path.of("/user-files").toRealPath();
Path resolved = base.resolve(filename).normalize();
if (!resolved.startsWith(base)) {
throw new SecurityException("Path traversal attempt detected: " + filename);
}
return Files.readAllBytes(resolved);
}
// Deserialization — never deserialize untrusted data with ObjectInputStream
// Use JSON (Jackson) or Protocol Buffers instead
// If you must use Java serialization, implement a whitelist filter:
ObjectInputStream ois = new ObjectInputStream(inputStream) {
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
Set<String> allowed = Set.of("com.example.MyClass", "java.util.ArrayList");
if (!allowed.contains(desc.getName())) {
throw new InvalidClassException("Unauthorized deserialization: " + desc.getName());
}
return super.resolveClass(desc);
}
}; Frequently Asked Questions
What is the most common security vulnerability in Java web apps?
SQL injection remains the most common and most dangerous. It occurs when user input is concatenated directly into SQL strings instead of using parameterized queries (PreparedStatement). A close second is broken authentication — weak tokens, no rate limiting, or storing passwords in plain text.
How should I store passwords in Java?
Never store plain text passwords or use MD5/SHA-1. Use a password hashing function designed for this purpose: BCrypt (via Spring Security's BCryptPasswordEncoder), Argon2 (recommended by OWASP), or PBKDF2. These are intentionally slow and include a salt automatically.
What is the difference between authentication and authorisation?
Authentication answers 'who are you?' — verifying identity (username + password, JWT, certificate). Authorisation answers 'what can you do?' — checking that the authenticated identity has permission to perform the requested action. Always authenticate before authorising.
Should I write my own cryptography code?
No. Cryptography is extremely easy to get wrong. Use well-audited libraries: Java's built-in JCA (Java Cryptography Architecture) for symmetric encryption and hashing, Bouncy Castle for advanced algorithms, and Spring Security or JJWT for JWT. Never roll your own crypto algorithms.