Secure Password Storage
Storing passwords safely remains a top priority. Plaintext storage invites disaster. I’ve seen breaches where leaked credentials led to cascading system compromises. Password hashing with salt provides essential protection. Java’s PBKDF2WithHmacSHA256
algorithm offers robust defense against brute-force attacks.
Consider this implementation:
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
public class PasswordManager {
private static final int ITERATIONS = 210000;
private static final int KEY_LENGTH = 256;
public static byte[] createSecureHash(char[] password, byte[] salt) throws Exception {
KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
return factory.generateSecret(spec).getEncoded();
}
public static byte[] generateSalt() {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[32]; // Increased salt size
random.nextBytes(salt);
return salt;
}
}
I recommend increasing iterations beyond 100,000 to counter modern GPUs. Always store salt alongside hashes. During login, rehash the provided password with the original salt for comparison.
Input Validation with Whitelisting
Input validation acts as our first security perimeter. I’ve encountered systems where a single unvalidated field caused SQL injection and XSS chain attacks. Whitelisting outperforms blacklisting by defining allowed patterns rather than blocking known bad characters.
For example:
public class InputSanitizer {
private static final String SAFE_TEXT_PATTERN = "^[\\p{L}0-9 ._@-]{1,100}$";
private static final String EMAIL_REGEX = "^[\\w-]+(\\.[\\w-]+)*@([\\w-]+\\.)+[a-zA-Z]{2,7}$";
public static String cleanTextInput(String input) {
if (input == null) return "";
return input.replaceAll("[^\\p{L}0-9 ._@-]", "");
}
public static boolean isValidEmail(String email) {
return email != null && email.matches(EMAIL_REGEX)
&& email.length() <= 254;
}
}
Validate both client-side for UX and server-side for security. Length constraints prevent buffer overflow exploits. For web forms, combine this with output encoding.
TLS Configuration for Secure Communication
Transport Layer Security prevents eavesdropping. I’ve configured TLS for financial systems where weak ciphers could expose sensitive transactions. Modern best practices demand TLS 1.3 with perfect forward secrecy.
Java implementation:
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
public class SecureTransport {
public static SSLContext createTLSContext() throws Exception {
SSLContext context = SSLContext.getInstance("TLSv1.3");
context.init(null, null, new SecureRandom());
SSLParameters params = context.getSupportedSSLParameters();
params.setProtocols(new String[]{"TLSv1.3"});
params.setCipherSuites(new String[]{
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256"
});
params.setUseCipherSuitesOrder(true); // Enforce server cipher preference
return context;
}
}
Disable legacy protocols like SSLv3. Use tools like SSLabs to test configurations. Certificate pinning adds extra protection against compromised CAs.
SQL Injection Prevention
SQL injection remains alarmingly common. I once traced a data breach to a single unparameterized query. Prepared statements separate SQL logic from data, neutralizing injection vectors.
Here’s proper usage:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class UserDAO {
public User getUserById(Connection conn, String userId) throws Exception {
String query = "SELECT email, name FROM users WHERE user_id = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, userId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return new User(
rs.getString("email"),
rs.getString("name")
);
}
}
}
return null;
}
}
Use parameterized queries for all dynamic data. Stored procedures with validation provide additional layers. ORM frameworks like Hibernate require careful mapping to avoid HQL injections.
XSS Prevention in Web Output
Cross-site scripting compromises user sessions. I’ve witnessed attackers steal cookies through unescaped comment fields. Context-aware output encoding is crucial.
OWASP Encoder implementation:
import org.owasp.encoder.Encode;
public class OutputSanitizer {
public String forHTML(String untrusted) {
return Encode.forHtmlContent(untrusted);
}
public String forHTMLAttribute(String value) {
return Encode.forHtmlAttribute(value);
}
public String forJavaScript(String input) {
return Encode.forJavaScript(input);
}
public String forCSS(String style) {
return Encode.forCssString(style);
}
}
Apply encoding immediately before output. Different contexts (HTML, JS, CSS) require specific encoders. Content Security Policy headers provide fallback protection.
Secure Random Number Generation
Predictable random numbers undermine security. I audited a system where Math.random()
generated weak session IDs. SecureRandom
uses cryptographically strong sources.
Proper usage:
import java.security.SecureRandom;
public class CryptographicOperations {
private static final SecureRandom SECURE_RAND = new SecureRandom();
public static byte[] generateIV(int size) {
byte[] iv = new byte[size];
SECURE_RAND.nextBytes(iv);
return iv;
}
public static String generateSessionToken() {
byte[] token = new byte[24];
SECURE_RAND.nextBytes(token);
return Base64.getUrlEncoder().withoutPadding().encodeToString(token);
}
}
Avoid seeding SecureRandom
manually. Prefer NativePRNG
on Linux systems via SecureRandom.getInstanceStrong()
. Never reuse initialization vectors.
File Permission Restrictions
Overprivileged file access enables ransomware. I’ve recovered systems where attackers modified configuration files due to lax permissions. Java’s security manager provides granular control.
Implementation:
import java.security.Policy;
import java.security.Permission;
import java.security.ProtectionDomain;
import java.io.FilePermission;
public class FileSecurityPolicy {
public static void enforceRestrictions() {
Policy.setPolicy(new Policy() {
@Override
public PermissionCollection getPermissions(ProtectionDomain domain) {
Permissions perms = new Permissions();
// Allow read access to specific directories only
perms.add(new FilePermission("/var/app/config/*", "read"));
perms.add(new FilePermission("/var/app/logs/*", "read,write"));
return perms;
}
});
System.setSecurityManager(new SecurityManager());
}
}
Follow least-privilege principles. Audit file operations during development. Consider filesystem monitoring for critical paths.
CSRF Token Validation
Cross-site request forgery tricks users into unwanted actions. I mitigated an attack where forged requests changed admin passwords. Synchronizer tokens validate request legitimacy.
Server-side implementation:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public class CSRFHandler {
private static final String CSRF_TOKEN_NAME = "csrfToken";
public static String generateToken(HttpSession session) {
String token = UUID.randomUUID().toString();
session.setAttribute(CSRF_TOKEN_NAME, token);
return token;
}
public static boolean isValidRequest(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) return false;
String sessionToken = (String) session.getAttribute(CSRF_TOKEN_NAME);
String requestToken = request.getParameter(CSRF_TOKEN_NAME);
return sessionToken != null && sessionToken.equals(requestToken);
}
}
Include tokens in forms and AJAX headers. Regenerate tokens after login. Combine with same-site cookies for defense-in-depth.
Security Headers for Web Applications
HTTP headers provide passive protection. I’ve patched vulnerabilities simply by adding X-Content-Type-Options
. Modern browsers enforce these directives client-side.
Filter implementation:
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
public class SecurityHeaderFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Content-Security-Policy",
"default-src 'self'; script-src 'self' 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'");
response.setHeader("X-Content-Type-Options", "nosniff");
response.setHeader("X-Frame-Options", "DENY");
response.setHeader("Strict-Transport-Security", "max-age=63072000; includeSubDomains");
response.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
chain.doFilter(req, res);
}
}
Use CSP nonces for inline scripts. Test policies with report-only mode. HSTS preloading prevents SSL-stripping attacks.
Security Logging with Audit Trails
Effective logging enables breach analysis. I reconstructed an attack timeline using properly logged events. Distinguish security logs from application logs.
Structured logging example:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.logstash.logback.argument.StructuredArguments;
public class SecurityLogger {
private static final Logger AUDIT_LOG = LoggerFactory.getLogger("SECURITY_AUDIT");
public static void logAuthEvent(String userId, boolean success, String sourceIP) {
String outcome = success ? "SUCCESS" : "FAILURE";
AUDIT_LOG.warn("Authentication attempt - User: {}, Outcome: {}, IP: {}",
userId,
outcome,
StructuredArguments.keyValue("client_ip", sourceIP));
}
public static void logPrivilegeChange(String adminId, String targetUser, String action) {
AUDIT_LOG.info("Privilege modification - Admin: {}, Action: '{}' on user: {}",
adminId,
action,
targetUser);
}
}
Log key details: timestamps, user IDs, IPs, and outcomes. Protect logs from tampering using write-once storage. Ship logs to secured SIEM systems.
These techniques form a defense-in-depth strategy. I’ve implemented these in production environments handling sensitive data. Password hashing and input validation prevent common injection attacks. TLS and security headers harden communication channels. File permissions and logging limit damage from breaches. Each layer addresses specific threat models while complementing others.
Regular security testing remains essential. I recommend combining SAST tools like SpotBugs with DAST scanners and manual penetration testing. Update dependencies quarterly—I’ve seen more breaches from outdated libraries than zero-days.
Security evolves continuously. New vulnerabilities like Log4Shell remind us to stay vigilant. These Java practices provide a robust foundation adaptable to emerging threats. What matters most is consistent implementation and thorough validation at each development phase.