Phase 5 · Professional DevelopmentModule 36~36 min read

Security Fundamentals for Java Developers

Write secure code: validate input, store passwords safely, and avoid common vulnerabilities.

What you'll learn

Security isn't a feature you bolt on at the end — it's a mindset you code with from the start. This module covers the defensive fundamentals every Java developer must know to avoid the mistakes that lead to breaches. (This is defensive security: how to protect the software you build.)

By the end you'll be able to:

  • Apply core secure-coding principles
  • Validate input and prevent injection attacks
  • Store passwords the right way (and know why)
  • Understand hashing vs encryption and use secure randomness
  • Handle secrets, dependencies, and deserialization safely

Secure-coding principles

A few mindsets prevent the majority of vulnerabilities:

  • Never trust input — treat all external data (users, APIs, files) as potentially hostile
  • Least privilege — give code, users, and services only the access they truly need
  • Defense in depth — layer protections; don't rely on a single check
  • Fail securely — on error, deny access rather than accidentally granting it
  • Don't roll your own crypto — use vetted, standard libraries

Input validation & injection

The number-one source of breaches is trusting input. Injection attacks trick your program into treating attacker data as code. You saw SQL injection in Module 23 — the fix was parameterised PreparedStatements. The same principle applies everywhere: validate and separate data from commands.

The injection rule

Never build a command, query, or path by concatenating raw input. Validate against an allow-list (whatis permitted, not what isn't), and use parameterised APIs. This covers SQL injection, command injection, and path traversal (where ../../ in a filename escapes your intended folder).

Password storage

Passwords must never be stored in plaintext — if your database leaks, every account is compromised. Instead, store a one-way hash. But not just any hash: use a slow, salted algorithm built for passwords, so attackers can't brute-force them:

Passwords.java
// ✗ NEVER store or compare plaintext passwords
// ✗ NEVER use a plain fast hash (MD5, or SHA-256 alone) for passwords

// ✓ Use a slow, salted, adaptive algorithm via a trusted library
//   (BCrypt, Argon2, scrypt) — do NOT roll your own crypto.

String hash = BCrypt.hashpw(password, BCrypt.gensalt());  // at sign-up

boolean ok = BCrypt.checkpw(entered, hash);               // at login

Key idea

A salt is random data added to each password before hashing, so identical passwords get different hashes and precomputed "rainbow tables" are useless. Libraries like BCrypt handle salting for you — always use one.

Hashing vs encryption

These are often confused but serve different goals. Hashing is one-way (for passwords and integrity checks); encryption is two-way (for data you need to read back). Pick based on whether you ever need the original value:

One-way vs two-way

Hashing — one-way

Turns data into a fixed digest you can't reverse. Verify by re-hashing and comparing. Use for passwords.

password → (salt + slow hash) → digest

Encryption — two-way

Scrambles data that a key can unscramble. Use to protect data you need to read back later.

data + key → cipher → data

For any randomness that protects something — session tokens, reset codes, keys — use SecureRandom, never java.util.Random:

SecureRandom.java
import java.security.SecureRandom;

SecureRandom random = new SecureRandom();   // cryptographically strong
byte[] token = new byte[16];
random.nextBytes(token);                    // e.g. a session/reset token

// ✗ NEVER use java.util.Random for anything security-related —
//   its output is predictable.

Secrets & configuration

API keys, passwords, and tokens are secrets — they must never be hardcoded or committed to Git (Module 32). Keep them in environment variables, a config file that's git-ignored, or a dedicated secrets manager, and inject them at runtime:

Secrets.java
// ✗ Hardcoded secret — ends up in Git history forever
String apiKey = "sk_live_9f8a7b6c5d";

// ✓ Read from the environment or a secrets manager
String apiKey2 = System.getenv("API_KEY");

Other risks to know

  • Deserialization — deserializing untrusted data (Module 19) can execute malicious code. Avoid Java serialization for external data; prefer JSON with strict parsing.
  • Dependency vulnerabilities — libraries you use can have known flaws. Keep them updated and scan them (e.g. with OWASP Dependency-Check or GitHub Dependabot).
  • Sensitive data in logs — never log passwords, tokens, or personal data (Module 30).
  • Error messages — don't leak stack traces or internal details to end users.

Note

A great free resource is the OWASP Top 10 — the industry's list of the most critical web-app security risks. Reading it once will change how you look at every feature you build.

Recap & quick check

Key takeaways

  • Never trust input; apply least privilege, defense in depth, and fail securely.
  • Prevent injection by validating input and separating data from commands (parameterised APIs).
  • Store passwords as slow, salted hashes (BCrypt/Argon2) — never plaintext, never MD5.
  • Hashing is one-way (passwords); encryption is two-way (readable data). Use SecureRandom, not Random.
  • Keep secrets out of code/Git, patch dependencies, avoid untrusted deserialization, and never log secrets.

Quick check

1. How should passwords be stored?

2. What is the best defense against SQL injection?

3. What's the difference between hashing and encryption?

4. Which class should generate security tokens?

5. Where should an API key be stored?

Excellent — you now build with security in mind. Next up: Module 37 — Advanced Architecture & Enterprise Concepts.