Skip to content

CWE-326: Inadequate Encryption Strength - Java

Overview

Inadequate Encryption Strength in Java applications occurs when developers use weak cryptographic algorithms, insufficient key sizes, or deprecated ciphers that fail to provide adequate protection against modern attacks. Java's extensive cryptography ecosystem through the JCA (Java Cryptography Architecture) and JCE (Java Cryptography Extension) provides both secure and insecure options, making proper selection critical.

Common Java Vulnerability Scenarios:

  • Using DES or 3DES instead of AES-256
  • Implementing RSA with 1024-bit keys instead of 2048+ bits
  • Using MD5 or SHA-1 for security-critical hashing
  • Applying ECB mode which reveals patterns in encrypted data
  • Using weak cipher suites in TLS/SSL connections
  • Implementing password hashing with insufficient iterations

Java Cryptographic Landscape:

  • javax.crypto: Built-in JCE for encryption/decryption
  • java.security: Core security classes including MessageDigest
  • Bouncy Castle: Extended cryptographic provider
  • Spring Security Crypto: High-level encryption utilities
  • Apache Commons Codec: Utility codecs and encoders

Framework-Specific Considerations:

  • Spring Boot: Use Spring Security Crypto module for encryption
  • JAX-RS: Secure API endpoints with proper cryptographic libraries
  • Jakarta EE: Leverage Java EE security APIs with strong algorithms
  • Micronaut: Configure encryption for configuration properties

Primary Defence: Use AES-256 with GCM mode for symmetric encryption, RSA with 2048+ bit keys for asymmetric encryption, and SHA-256 or SHA-3 for hashing.

Common Vulnerable Patterns

Using DES Encryption

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class WeakEncryptionService {
    private static final String DES_ALGORITHM = "DES";
    private SecretKey key;

    public WeakEncryptionService() throws Exception {
        // DES has only 56-bit effective key strength
        KeyGenerator keyGen = KeyGenerator.getInstance(DES_ALGORITHM);
        keyGen.init(56);
        this.key = keyGen.generateKey();
    }

    public String encryptSSN(String ssn) throws Exception {
        // Using ECB mode - also vulnerable
        Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);

        byte[] encrypted = cipher.doFinal(ssn.getBytes());
        return Base64.getEncoder().encodeToString(encrypted);
    }

    public String decryptSSN(String encryptedSSN) throws Exception {
        Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, key);

        byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedSSN));
        return new String(decrypted);
    }
}

// Spring Boot REST Controller using weak encryption
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;

@RestController
@RequestMapping("/api/users")
public class UserController {
    private final WeakEncryptionService encryptionService;

    public UserController() throws Exception {
        this.encryptionService = new WeakEncryptionService();
    }

    @PostMapping
    public ResponseEntity<UserResponse> createUser(@RequestBody UserRequest request) 
            throws Exception {
        String encryptedSSN = encryptionService.encryptSSN(request.getSsn());
        // Store in database
        return ResponseEntity.ok(new UserResponse("created", encryptedSSN));
    }
}

Why this is vulnerable:

  • DES has only 56-bit effective key strength, making brute-force feasible.
  • ECB mode reveals patterns and duplicate blocks in ciphertext.
  • Pattern leakage lets attackers infer plaintext structure.

Weak RSA Key Size

import java.security.*;
import javax.crypto.Cipher;
import java.util.Base64;

public class WeakRSAEncryption {
    private KeyPair keyPair;

    public WeakRSAEncryption() throws NoSuchAlgorithmException {
        // 1024-bit RSA is considered weak
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(1024);  // Insufficient key size
        this.keyPair = keyGen.generateKeyPair();
    }

    public String encryptAPIKey(String apiKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());

        byte[] encrypted = cipher.doFinal(apiKey.getBytes());
        return Base64.getEncoder().encodeToString(encrypted);
    }

    public String decryptAPIKey(String encryptedKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());

        byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedKey));
        return new String(decrypted);
    }
}

// JAX-RS endpoint using weak RSA

Why this is vulnerable:

  • 1024-bit RSA can be factored by well-resourced attackers.
  • NIST deprecated 1024-bit keys in 2013.
  • Minimum 2048-bit keys are required for adequate security.

Using MD5 for Password Hashing

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.xml.bind.DatatypeConverter;

public class WeakPasswordService {

    public String hashPassword(String password) throws NoSuchAlgorithmException {
        // MD5 is cryptographically broken
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] hash = md.digest(password.getBytes());
        return DatatypeConverter.printHexBinary(hash);
    }

    public boolean verifyPassword(String password, String hash) 
            throws NoSuchAlgorithmException {
        String computedHash = hashPassword(password);
        return computedHash.equals(hash);
    }
}

// Spring Boot authentication service
import org.springframework.stereotype.Service;
import org.springframework.security.core.userdetails.*;

@Service
public class UserAuthenticationService implements UserDetailsService {
    private final WeakPasswordService passwordService;
    private final UserRepository userRepository;

    public UserAuthenticationService(UserRepository userRepository) 
            throws NoSuchAlgorithmException {
        this.passwordService = new WeakPasswordService();
        this.userRepository = userRepository;
    }

    public void registerUser(String username, String password) throws Exception {
        String hashedPassword = passwordService.hashPassword(password);

        User user = new User();
        user.setUsername(username);
        user.setPasswordHash(hashedPassword);

        userRepository.save(user);
    }

    @Override
    public UserDetails loadUserByUsername(String username) 
            throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("User not found"));

        return org.springframework.security.core.userdetails.User
            .withUsername(user.getUsername())
            .password(user.getPasswordHash())
            .authorities("USER")
            .build();
    }
}

Why this is vulnerable:

  • 1024-bit RSA can be factored with sufficient computing resources
  • NIST requires minimum 2048-bit keys, recommends 3072-bit
  • Vulnerable to future quantum computing attacks
  • No padding specified (defaults may be insecure)

MD5 for Password Hashing

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.xml.bind.DatatypeConverter;

public class WeakPasswordService {

    public String hashPassword(String password) throws NoSuchAlgorithmException {
        // MD5 is cryptographically broken
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] hash = md.digest(password.getBytes());
        return DatatypeConverter.printHexBinary(hash);
    }

    public boolean verifyPassword(String password, String hash) 
            throws NoSuchAlgorithmException {
        String computedHash = hashPassword(password);
        return computedHash.equals(hash);
    }
}

// Spring Boot authentication service

Why this is vulnerable:

  • MD5 has practical collision attacks and is cryptographically broken.
  • GPUs can brute-force MD5 at billions of hashes per second.
  • No built-in salt enables rainbow table attacks.
  • It fails modern password storage standards.

Using SHA-1 for Digital Signatures

// Spring Boot authentication service
import org.springframework.stereotype.Service;
import org.springframework.security.core.userdetails.*;

@Service
public class UserAuthenticationService implements UserDetailsService {
    private final WeakPasswordService passwordService;
    private final UserRepository userRepository;

    public UserAuthenticationService(UserRepository userRepository) 
            throws NoSuchAlgorithmException {
        this.passwordService = new WeakPasswordService();
        this.userRepository = userRepository;
    }

    public void registerUser(String username, String password) throws Exception {
        String hashedPassword = passwordService.hashPassword(password);

        User user = new User();
        user.setUsername(username);
        user.setPasswordHash(hashedPassword);

        userRepository.save(user);
    }

    @Override
    public UserDetails loadUserByUsername(String username) 
            throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("User not found"));

        return org.springframework.security.core.userdetails.User
            .withUsername(user.getUsername())
            .password(user.getPasswordHash())
            .authorities("USER")
            .build();
    }
}

Why this is vulnerable:

  • MD5 has known collision vulnerabilities
  • No salt means identical passwords produce identical hashes
  • Fast hashing allows brute-force attacks
  • Rainbow table attacks can reverse common passwords instantly

AES-128 in ECB Mode

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;

public class WeakAESEncryption {
    private SecretKey key;

    public WeakAESEncryption() {
        // AES-128 is weaker than AES-256
        byte[] keyBytes = new byte[16];  // 128 bits
        new SecureRandom().nextBytes(keyBytes);
        this.key = new SecretKeySpec(keyBytes, "AES");
    }

    public String encryptToken(String token) throws Exception {
        // ECB mode is insecure - reveals patterns
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);

        byte[] encrypted = cipher.doFinal(token.getBytes());
        return Base64.getEncoder().encodeToString(encrypted);
    }

    public String decryptToken(String encryptedToken) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, key);

        byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedToken));
        return new String(decrypted);
    }
}

// Micronaut controller using weak AES
import io.micronaut.http.annotation.*;
import io.micronaut.http.HttpResponse;

@Controller("/api/tokens")
public class TokenController {
    private final WeakAESEncryption encryption = new WeakAESEncryption();

    @Post
    public HttpResponse<TokenResponse> encryptToken(@Body TokenRequest request) 
            throws Exception {
        String encrypted = encryption.encryptToken(request.getToken());
        return HttpResponse.ok(new TokenResponse(encrypted));
    }
}

Why this is vulnerable:

  • ECB mode makes identical plaintext blocks produce identical ciphertext blocks.
  • Pattern leakage exposes repeated values and structured content.
  • AES-128 has a smaller security margin than AES-256 for long-term use.

SHA-1 for HMAC

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.xml.bind.DatatypeConverter;

public class WeakHMACService {
    private static final String HMAC_ALGORITHM = "HmacSHA1";  // Deprecated
    private SecretKeySpec secretKey;

    public WeakHMACService(byte[] key) {
        this.secretKey = new SecretKeySpec(key, HMAC_ALGORITHM);
    }

    public String generateSignature(String data) 
            throws NoSuchAlgorithmException, InvalidKeyException {
        Mac mac = Mac.getInstance(HMAC_ALGORITHM);
        mac.init(secretKey);

        byte[] signature = mac.doFinal(data.getBytes());
        return DatatypeConverter.printHexBinary(signature).toLowerCase();
    }

    public boolean verifySignature(String data, String signature) 
            throws NoSuchAlgorithmException, InvalidKeyException {
        String computed = generateSignature(data);
        return MessageDigest.isEqual(
            computed.getBytes(), 
            signature.getBytes()
        );
    }
}

// JAX-RS webhook endpoint with weak HMAC
import javax.ws.rs.*;
import javax.ws.rs.core.*;

@Path("/api/webhook")
public class WebhookResource {
    private static final byte[] SECRET = "weak_secret_key".getBytes();
    private final WeakHMACService hmacService = new WeakHMACService(SECRET);

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response processWebhook(
            @HeaderParam("X-Signature") String signature,
            String payload) throws Exception {

        if (!hmacService.verifySignature(payload, signature)) {
            return Response.status(Response.Status.FORBIDDEN)
                .entity("Invalid signature")
                .build();
        }

        // Process webhook
        return Response.ok().entity("Processed").build();
    }
}

Why this is vulnerable:

  • SHA-1 has practical collision attacks (publicly demonstrated in 2017).
  • Deprecated by major standards (NIST, PCI-DSS).
  • SHA-256 or SHA-3 provides stronger future-proofing.

Low Iteration PBKDF2

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;

public class WeakPBKDF2Service {
    private static final int ITERATIONS = 1000;  // Too low!
    private static final int KEY_LENGTH = 128;   // Too short!
    private static final String FIXED_SALT = "fixed_salt_value";  // Fixed salt!

    public String deriveKey(String password) 
            throws NoSuchAlgorithmException, InvalidKeySpecException {

        PBEKeySpec spec = new PBEKeySpec(
            password.toCharArray(),
            FIXED_SALT.getBytes(),
            ITERATIONS,
            KEY_LENGTH
        );

        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        byte[] hash = factory.generateSecret(spec).getEncoded();

        return Base64.getEncoder().encodeToString(hash);
    }
}

// Spring Boot configuration encryption
import org.springframework.stereotype.Service;

@Service
public class ConfigEncryptionService {
    private final WeakPBKDF2Service pbkdf2Service = new WeakPBKDF2Service();

    public String encryptConfig(String password, String configValue) throws Exception {
        String key = pbkdf2Service.deriveKey(password);
        // Use derived key for encryption
        return encryptWithKey(key, configValue);
    }

    private String encryptWithKey(String key, String value) {
        // Implementation
        return value;
    }
}

Why this is vulnerable:

  • 1,000 iterations are far below OWASP's 600,000+ PBKDF2-SHA256 guidance.
  • Fixed salts make identical passwords produce identical hashes.
  • SHA-1 provides weaker security margins than SHA-256/SHA-512.

3DES Instead of AES

import javax.crypto.*;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.security.spec.KeySpec;
import java.util.Base64;

public class Legacy3DESEncryption {
    private SecretKey key;

    public Legacy3DESEncryption(byte[] keyBytes) throws Exception {
        // 3DES is deprecated
        KeySpec keySpec = new DESedeKeySpec(keyBytes);
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
        this.key = keyFactory.generateSecret(keySpec);
    }

    public String encryptCreditCard(String creditCard) throws Exception {
        Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");

        byte[] iv = new byte[8];  // 64-bit IV for 3DES
        cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));

        byte[] encrypted = cipher.doFinal(creditCard.getBytes());
        return Base64.getEncoder().encodeToString(encrypted);
    }
}

// Jakarta EE REST service
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

@Path("/api/payments")
@Produces(MediaType.APPLICATION_JSON)
public class PaymentResource {
    private Legacy3DESEncryption encryption;

    public PaymentResource() throws Exception {
        byte[] key = "FIXED_24_BYTE_KEY_123456".getBytes();
        this.encryption = new Legacy3DESEncryption(key);
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response processPayment(PaymentRequest request) throws Exception {
        String encryptedCC = encryption.encryptCreditCard(request.getCreditCard());
        // Process payment
        return Response.ok()
            .entity(new PaymentResponse("processed", encryptedCC))
            .build();
    }
}

Why this is vulnerable:

  • 3DES deprecated by NIST (retired in 2023)
  • 64-bit block size vulnerable to birthday attacks
  • Slower than AES with no security benefit
  • Not compliant with modern security standards

Weak TLS Cipher Suite Configuration

import javax.net.ssl.*;
import java.security.KeyStore;

public class WeakSSLConfiguration {

    public SSLContext createWeakSSLContext() throws Exception {
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, null, null);

        // Default configuration may include weak ciphers
        return context;
    }

    public HttpsURLConnection createConnection(String url) throws Exception {
        HttpsURLConnection connection = 
            (HttpsURLConnection) new URL(url).openConnection();

        SSLContext sslContext = createWeakSSLContext();
        connection.setSSLSocketFactory(sslContext.getSocketFactory());

        // No cipher suite restrictions - may use RC4, 3DES, etc.
        return connection;
    }
}

// Spring Boot with weak SSL
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;

@Configuration
public class WeakHttpClientConfig {

    @Bean
    public HttpClient httpClient() throws Exception {
        WeakSSLConfiguration sslConfig = new WeakSSLConfiguration();
        SSLConnectionSocketFactory socketFactory = 
            new SSLConnectionSocketFactory(sslConfig.createWeakSSLContext());

        return HttpClients.custom()
            .setSSLSocketFactory(socketFactory)
            .build();
    }
}

Why this is vulnerable:

  • May negotiate weak cipher suites (RC4, 3DES, MD5)
  • No explicit cipher suite allowlist
  • Vulnerable to downgrade attacks
  • May not enforce TLS 1.2+ minimum version

Secure Patterns

AES-256-GCM Encryption

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.security.SecureRandom;
import java.util.Base64;

public class SecureAESGCMEncryption {
    private static final String TRANSFORMATION = "AES/GCM/NoPadding";
    private static final int GCM_TAG_LENGTH = 128;  // bits
    private static final int GCM_IV_LENGTH = 12;    // bytes (96 bits recommended)
    private static final int AES_KEY_SIZE = 256;    // bits

    private SecretKey key;
    private SecureRandom secureRandom;

    public SecureAESGCMEncryption() throws Exception {
        // Generate strong AES-256 key
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(AES_KEY_SIZE);
        this.key = keyGenerator.generateKey();
        this.secureRandom = new SecureRandom();
    }

    public String encrypt(String plaintext) throws Exception {
        // Generate random IV for each encryption
        byte[] iv = new byte[GCM_IV_LENGTH];
        secureRandom.nextBytes(iv);

        // Configure GCM parameters
        GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);

        // Initialize cipher
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec);

        // Optional: Add authenticated associated data (AAD) for context binding
        cipher.updateAAD("CONTEXT_V1".getBytes());

        // Encrypt
        byte[] ciphertext = cipher.doFinal(plaintext.getBytes());

        // Combine IV + ciphertext for storage
        byte[] encrypted = new byte[iv.length + ciphertext.length];
        System.arraycopy(iv, 0, encrypted, 0, iv.length);
        System.arraycopy(ciphertext, 0, encrypted, iv.length, ciphertext.length);

        return Base64.getEncoder().encodeToString(encrypted);
    }

    public String decrypt(String encryptedData) throws Exception {
        byte[] encrypted = Base64.getDecoder().decode(encryptedData);

        // Extract IV and ciphertext
        byte[] iv = new byte[GCM_IV_LENGTH];
        byte[] ciphertext = new byte[encrypted.length - GCM_IV_LENGTH];
        System.arraycopy(encrypted, 0, iv, 0, iv.length);
        System.arraycopy(encrypted, iv.length, ciphertext, 0, ciphertext.length);

        // Configure GCM parameters
        GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);

        // Initialize cipher
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, key, gcmSpec);

        // Add same AAD used during encryption
        cipher.updateAAD("CONTEXT_V1".getBytes());

        // Decrypt and verify authentication tag
        byte[] plaintext = cipher.doFinal(ciphertext);

        return new String(plaintext);
    }
}

Why this works:

  • AES-256 provides a large key space that makes brute-force infeasible.
  • GCM delivers authenticated encryption (confidentiality + integrity).
  • A random 96-bit IV per encryption prevents deterministic ciphertexts.
  • The 128-bit tag detects tampering before decryption succeeds.
  • AAD binds ciphertext to context to prevent substitution attacks.

RSA-3072 with OAEP Padding

import java.security.*;
import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import java.security.spec.MGF1ParameterSpec;
import java.util.Base64;

public class SecureRSAEncryption {
    private static final int KEY_SIZE = 3072;  // 3072-bit RSA
    private static final String TRANSFORMATION = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";

    private KeyPair keyPair;

    public SecureRSAEncryption() throws NoSuchAlgorithmException {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(KEY_SIZE, new SecureRandom());
        this.keyPair = keyGen.generateKeyPair();
    }

    public String encrypt(String plaintext) throws Exception {
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);

        // Configure OAEP with SHA-256
        OAEPParameterSpec oaepParams = new OAEPParameterSpec(
            "SHA-256",
            "MGF1",
            MGF1ParameterSpec.SHA256,
            PSource.PSpecified.DEFAULT
        );

        cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic(), oaepParams);

        byte[] encrypted = cipher.doFinal(plaintext.getBytes());
        return Base64.getEncoder().encodeToString(encrypted);
    }

    public String decrypt(String encryptedData) throws Exception {
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);

        OAEPParameterSpec oaepParams = new OAEPParameterSpec(
            "SHA-256",
            "MGF1",
            MGF1ParameterSpec.SHA256,
            PSource.PSpecified.DEFAULT
        );

        cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate(), oaepParams);

        byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
        return new String(decrypted);
    }
}

Why this works:

  • RSA-3072 provides ~128-bit security for long-term protection.
  • OAEP randomizes padding, blocking PKCS#1 v1.5 padding oracles.
  • SHA-256 for both OAEP and MGF1 avoids weak hash defaults.
  • Explicit OAEP parameters prevent provider default regressions.
  • Suitable for small secrets; use hybrid encryption for bulk data.

BCrypt for Password Hashing

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

public class SecurePasswordService {
    private final PasswordEncoder passwordEncoder;

    public SecurePasswordService() {
        // BCrypt with strength 12 (2^12 = 4096 rounds)
        this.passwordEncoder = new BCryptPasswordEncoder(12);
    }

    public String hashPassword(String password) {
        // BCrypt automatically generates per-password random salt
        return passwordEncoder.encode(password);
    }

    public boolean verifyPassword(String password, String storedHash) {
        // Timing-safe password comparison
        return passwordEncoder.matches(password, storedHash);
    }

    // Prevent timing attacks when user doesn't exist
    public boolean authenticateUser(String username, String password, String storedHash) {
        if (storedHash == null) {
            // Perform dummy check with same timing
            passwordEncoder.matches(password, "$2a$12$dummyhashtopreventtiming");
            return false;
        }
        return passwordEncoder.matches(password, storedHash);
    }
}

Why this works:

  • BCrypt is intentionally slow and memory-hard, raising brute-force cost.
  • Cost factor 12 (4096 rounds) is adjustable as hardware improves.
  • Per-password random salts prevent rainbow table attacks.
  • Encoded output stores salt + hash together for safe storage.
  • Constant-time matches() and dummy checks reduce timing leaks.

PBKDF2 with High Iteration Count

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;

public class SecurePBKDF2Service {
    private static final int ITERATIONS = 600_000;  // OWASP 2023 recommendation
    private static final int KEY_LENGTH = 256;      // bits
    private static final int SALT_LENGTH = 32;      // bytes
    private static final String ALGORITHM = "PBKDF2WithHmacSHA256";

    private final SecureRandom secureRandom = new SecureRandom();

    public String hashPassword(String password) throws Exception {
        // Generate random salt
        byte[] salt = new byte[SALT_LENGTH];
        secureRandom.nextBytes(salt);

        // Derive key
        byte[] hash = deriveKey(password, salt);

        // Return hash:salt format for storage
        return Base64.getEncoder().encodeToString(hash) + ":" +
               Base64.getEncoder().encodeToString(salt);
    }

    public boolean verifyPassword(String password, String storedHash) throws Exception {
        // Extract hash and salt
        String[] parts = storedHash.split(":");
        byte[] hash = Base64.getDecoder().decode(parts[0]);
        byte[] salt = Base64.getDecoder().decode(parts[1]);

        // Recompute hash
        byte[] computedHash = deriveKey(password, salt);

        // Timing-safe comparison
        return MessageDigest.isEqual(computedHash, hash);
    }

    private byte[] deriveKey(String password, byte[] salt) throws Exception {
        PBEKeySpec spec = new PBEKeySpec(
            password.toCharArray(),
            salt,
            ITERATIONS,
            KEY_LENGTH
        );

        SecretKeyFactory factory = SecretKeyFactory.getInstance(ALGORITHM);
        return factory.generateSecret(spec).getEncoded();
    }
}

Why this works:

  • 600,000 iterations aligns with OWASP 2023 guidance and raises brute-force cost.
  • Per-user 32-byte random salts prevent rainbow table attacks.
  • SHA-256 avoids deprecated SHA-1 and provides strong hash security.
  • MessageDigest.isEqual() prevents timing leaks in comparisons.
  • The encoded hash:salt format keeps all components together.

HMAC-SHA256 for Message Authentication

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.HexFormat;

public class SecureHMACService {
    private static final String ALGORITHM = "HmacSHA256";
    private static final int KEY_SIZE = 32;  // 256 bits

    private final SecretKeySpec secretKey;

    public SecureHMACService(byte[] key) {
        if (key.length < KEY_SIZE) {
            throw new IllegalArgumentException("Key must be at least 256 bits");
        }
        this.secretKey = new SecretKeySpec(key, ALGORITHM);
    }

    public static SecureHMACService withRandomKey() {
        byte[] key = new byte[KEY_SIZE];
        new SecureRandom().nextBytes(key);
        return new SecureHMACService(key);
    }

    public String generateSignature(String data) throws Exception {
        Mac mac = Mac.getInstance(ALGORITHM);
        mac.init(secretKey);

        byte[] signature = mac.doFinal(data.getBytes());
        return HexFormat.of().formatHex(signature);
    }

    public boolean verifySignature(String data, String signature) throws Exception {
        String expected = generateSignature(data);

        // Timing-safe comparison
        return MessageDigest.isEqual(
            expected.getBytes(), 
            signature.getBytes()
        );
    }
}

Why this works:

  • HMAC-SHA256 provides integrity and authenticity with modern security strength.
  • A 256-bit key matches SHA-256's security level.
  • HMAC construction prevents length-extension attacks.
  • withRandomKey() generates a fresh 256-bit secret using SecureRandom.
  • Constant-time comparison reduces timing side channels.

Spring Security Crypto Module

import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.security.crypto.keygen.KeyGenerators;

public class SpringCryptoService {
    private final TextEncryptor encryptor;

    public SpringCryptoService(String password, String hexEncodedSalt) {
        // Creates AES-256-CBC with HMAC-SHA256 for Encrypt-then-MAC
        this.encryptor = Encryptors.delux(password, hexEncodedSalt);
    }

    public String encrypt(String plaintext) {
        return encryptor.encrypt(plaintext);
    }

    public String decrypt(String ciphertext) {
        return encryptor.decrypt(ciphertext);
    }

    public static String generateSalt() {
        // Generate hex-encoded salt for secure initialization
        return KeyGenerators.string().generateKey();
    }
}

Why this works:

  • Encrypt-then-MAC (AES-256-CBC + HMAC-SHA256) provides confidentiality and integrity.
  • MAC verification happens before decryption, preventing padding oracles.
  • Random IVs prevent identical plaintexts from producing identical ciphertexts.
  • PBKDF2-derived keys and salts avoid weak password-based keys.
  • TextEncryptor reduces implementation errors with a vetted API.

Verification

After implementing the recommended secure patterns, verify the fix through multiple approaches:

  • Manual testing: Submit malicious payloads relevant to this vulnerability and confirm they're handled safely without executing unintended operations
  • Code review: Confirm all instances use the secure pattern (parameterized queries, safe APIs, proper encoding) with no string concatenation or unsafe operations
  • Static analysis: Use security scanners to verify no new vulnerabilities exist and the original finding is resolved
  • Regression testing: Ensure legitimate user inputs and application workflows continue to function correctly
  • Edge case validation: Test with special characters, boundary conditions, and unusual inputs to verify proper handling
  • Framework verification: If using a framework or library, confirm the recommended APIs are used correctly according to documentation
  • Authentication/session testing: Verify security controls remain effective and cannot be bypassed (if applicable to the vulnerability type)
  • Rescan: Run the security scanner again to confirm the finding is resolved and no new issues were introduced

Additional Resources