Back to Blog
critical SEVERITY8 min read

Decrypted Secrets in Plain Sight: Fixing AES Log Exposure in Java

A critical vulnerability was discovered in AESEncryption.java where decrypted plaintext was being printed directly to standard output, exposing sensitive data to anyone with access to application logs. This fix eliminates the dangerous logging pattern that completely undermined the purpose of AES encryption. Understanding this vulnerability is essential for any developer building applications that handle sensitive encrypted data.

O
By orbisai0security
April 23, 2026
#java#aes#encryption#logging#security#cryptography#owasp

Decrypted Secrets in Plain Sight: Fixing AES Log Exposure in Java

Vulnerability Severity: Critical
Affected File: src/main/java/com/thealgorithms/ciphers/AESEncryption.java
Fix Type: Remove plaintext logging of decrypted data


Introduction

Imagine spending significant engineering effort implementing AES encryption — one of the strongest symmetric encryption algorithms available — to protect your users' sensitive data. You choose the right key size, implement proper padding, and feel confident your data is secure. Then, quietly, a single line of code hands that protected data to anyone who can read a log file.

This is exactly the scenario uncovered in AESEncryption.java. At line 41, the result of AES decryption was being printed directly to standard output via System.out.println. On the surface, this looks like harmless debugging code. In practice, it is a security catastrophe waiting to happen.

This blog post breaks down the vulnerability, explains why it matters in real-world deployments, and walks through the fix and the broader lessons every developer should take away.


The Vulnerability Explained

What Happened?

AES (Advanced Encryption Standard) is a symmetric encryption algorithm widely used to protect sensitive information — passwords, tokens, personal data, financial records, and more. The entire security model of AES depends on one guarantee: only parties who possess the encryption key can read the plaintext.

The vulnerable code broke this guarantee not by attacking the cryptography itself, but by casually printing the decrypted result to standard output:

// VULNERABLE CODE (before fix)
String decryptedText = decrypt(encryptedData, secretKey);
System.out.println(decryptedText); // <-- This line destroys your encryption guarantees

This pattern is extremely common in development. Developers add System.out.println statements to verify their code is working, and sometimes those debug lines make it into production. The result is that the decrypted plaintext travels directly into your logging infrastructure.

Why Is This So Dangerous?

In modern deployment environments, standard output is almost never truly "standard." It gets captured, aggregated, stored, and monitored. Consider where your System.out output actually ends up:

Environment Where stdout goes
Docker / Kubernetes Container log streams, accessible via kubectl logs or Docker daemon
AWS Lambda / Cloud Functions Automatically shipped to CloudWatch Logs
Heroku / PaaS platforms Log drains, accessible to all team members
ELK Stack / Splunk Centralized log aggregation, often with broad read access
CI/CD Pipelines Build logs, sometimes stored for months
Traditional servers /var/log/ files, often with weak access controls

In every one of these environments, the decrypted plaintext is now accessible to anyone with read access to the logs — without ever needing the AES encryption key.

The Real-World Impact

The consequences of this vulnerability scale with the sensitivity of the data being decrypted:

  • OAuth tokens and API keys decrypted for validation are now visible in logs, allowing attackers to impersonate users or services
  • Personally Identifiable Information (PII) exposed in logs creates GDPR, HIPAA, and PCI-DSS compliance violations
  • Passwords or session tokens logged in plaintext become trivially harvestable
  • Business-critical secrets (database credentials, private keys) become accessible to anyone who can query your log system

The attack scenario is chillingly simple:

1. Attacker gains read access to log aggregation system
   (compromised credentials, misconfigured S3 bucket, insider threat)

2. Attacker searches logs for decrypted output patterns

3. Attacker extracts plaintext secrets  no cryptography required

4. Encryption provided zero protection

Why This Is Classified as Critical

This vulnerability doesn't require a sophisticated exploit. There's no buffer overflow, no injection attack, no race condition to win. The application itself is doing the attacker's work, broadcasting decrypted secrets to anyone watching the logs. The cryptographic protection — regardless of key strength — is rendered completely ineffective.


The Fix

What Changed

The fix removes the System.out.println call that was printing decrypted plaintext to standard output. The decryption logic itself is sound; the problem was solely in how the result was being handled after decryption.

// BEFORE (vulnerable)
public static String decrypt(String encryptedText, SecretKey secretKey)
        throws Exception {
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.DECRYPT_MODE, secretKey);
    byte[] decryptedBytes = cipher.doFinal(
        Base64.getDecoder().decode(encryptedText)
    );
    String decryptedText = new String(decryptedBytes);
    System.out.println(decryptedText); // VULNERABILITY: logs plaintext
    return decryptedText;
}
// AFTER (fixed)
public static String decrypt(String encryptedText, SecretKey secretKey)
        throws Exception {
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.DECRYPT_MODE, secretKey);
    byte[] decryptedBytes = cipher.doFinal(
        Base64.getDecoder().decode(encryptedText)
    );
    return new String(decryptedBytes); // Plaintext returned, never logged
}

How This Solves the Problem

By removing the System.out.println call, the decrypted plaintext is only ever held in memory and returned directly to the calling code. It never touches the logging infrastructure. The AES encryption guarantee is restored: only code that explicitly handles the return value can access the plaintext.

The Broader Context: Credential Storage

It's worth noting that this vulnerability exists within a broader pattern of credential handling issues. OAuth tokens and API keys stored on the filesystem without encryption compound the risk — even if the logging issue is fixed, credentials at rest without cryptographic protection remain a concern. Libraries like PBKDF2 (available in the dependency tree) should be used to derive encryption keys for protecting credentials stored on disk. Defense in depth means fixing both the logging exposure and the storage exposure.


Prevention & Best Practices

1. Never Log Sensitive Data

This is the cardinal rule of secure logging. Establish a clear policy in your team:

// ❌ Never do this with sensitive data
System.out.println("Decrypted value: " + sensitiveData);
logger.debug("Token: " + authToken);
logger.info("Password hash: " + hash);

// ✅ Log metadata, not content
logger.info("Decryption completed successfully for record ID: " + recordId);
logger.debug("Authentication token validated for user: " + userId);

2. Use a Structured Logging Framework with Masking

Replace raw System.out.println with a proper logging framework (SLF4J + Logback, Log4j2) and configure sensitive field masking:

// Use SLF4J instead of System.out
private static final Logger logger = LoggerFactory.getLogger(AESEncryption.class);

// Log operational events, never data content
logger.info("Decryption operation completed in {}ms", duration);

Many enterprise logging frameworks support automatic masking of fields matching patterns like password, token, secret, key.

3. Code Review Checklist for Cryptographic Code

When reviewing any code that handles encryption or decryption, check for:

  • [ ] Are decrypted values logged anywhere (stdout, stderr, log files)?
  • [ ] Are encryption keys or IVs logged?
  • [ ] Are intermediate cryptographic values printed during debugging?
  • [ ] Do error messages include plaintext values?
  • [ ] Are there any toString() implementations on sensitive objects that might leak data?

4. Static Analysis Tools

Integrate static analysis into your CI/CD pipeline to catch these issues automatically:

Tool What it catches
SpotBugs + FindSecBugs Security-specific Java bugs including logging issues
SonarQube Broad code quality and security rules
Semgrep Custom rules for detecting sensitive data in logs
Checkmarx / Veracode Enterprise SAST with taint analysis

A Semgrep rule to catch this pattern:

rules:
  - id: sensitive-data-in-logs
    patterns:
      - pattern: System.out.println($DECRYPTED)
      - pattern-inside: |
          $TYPE $METHOD(...) {
            ...
            $CIPHER.doFinal(...);
            ...
          }
    message: Possible logging of decrypted data
    severity: ERROR

5. Encrypt Credentials at Rest

For any credentials (OAuth tokens, API keys, passwords) stored on the filesystem, always encrypt them before writing to disk:

// Use PBKDF2 to derive an encryption key from a master secret
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(masterPassword, salt, 310000, 256);
SecretKey derivedKey = factory.generateSecret(spec);

// Use the derived key to encrypt credentials before storage
// Never store the plaintext credential

6. Relevant Security Standards

This vulnerability maps to several well-known security standards:

  • OWASP Top 10 A02:2021 — Cryptographic Failures (previously "Sensitive Data Exposure")
  • CWE-312 — Cleartext Storage of Sensitive Information
  • CWE-532 — Insertion of Sensitive Information into Log File
  • NIST SP 800-57 — Key Management Guidelines
  • PCI DSS Requirement 3 — Protect stored cardholder data

Conclusion

The vulnerability fixed here is a powerful reminder that cryptographic security is not just about the algorithm — it's about the entire lifecycle of sensitive data. You can implement AES-256 perfectly and still expose every secret you decrypt if you log the results carelessly.

The fix is simple: remove the debug logging of plaintext. But the lesson is deeper:

  1. Treat decrypted data as sensitive at every point in your code, not just at storage
  2. Log events and metadata, never data content
  3. Use static analysis tools to catch these patterns before they reach production
  4. Encrypt credentials at rest using proper key derivation functions
  5. Review cryptographic code with extra scrutiny — the stakes are higher than ordinary logic bugs

Security vulnerabilities like this one often aren't the result of malicious intent or ignorance of cryptography. They're the result of a debug println that never got cleaned up. The best defense is a culture of security awareness, automated tooling, and thorough code review.

Every line of code that touches sensitive data deserves a moment of pause: "If this line ended up in a log file, what would an attacker learn?"


This vulnerability was identified and fixed as part of an automated security scanning process. Security fixes like this one are most effective when paired with developer education — understanding the "why" behind a vulnerability helps prevent the entire class of issues, not just the single instance.


Further Reading:
- OWASP Cryptographic Storage Cheat Sheet
- OWASP Logging Cheat Sheet
- CWE-532: Insertion of Sensitive Information into Log File
- NIST Guidelines for Cryptographic Key Management

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #7392

Related Articles

critical

Stack Buffer Overflow in MapScale: How Five Unsafe sprintf Calls Created a Critical Vulnerability

A critical stack-based buffer overflow vulnerability was discovered and patched in `src/mapscale.c`, where five unbounded `sprintf` calls wrote formatted output into fixed-size stack buffers without any bounds checking. An attacker controlling unit text strings could overflow the stack buffer, potentially overwriting the function return address and achieving arbitrary code execution. The fix replaces dangerous `sprintf` calls with their bounds-checked counterparts, eliminating the overflow risk

critical

Heap Buffer Overflows in YAML Parser: How Unchecked memcpy Calls Create Critical Attack Vectors

A critical heap buffer overflow vulnerability was discovered and patched in the YAML parser embedded within an Android VPN application, where five unvalidated `memcpy` calls could allow an attacker to corrupt heap memory by supplying a crafted YAML configuration file. This class of vulnerability is particularly dangerous because it can lead to arbitrary code execution or application crashes in security-sensitive contexts. The fix adds proper bounds validation before each copy operation, eliminat

critical

Critical Buffer Overflow Fixed: When "Safe" Functions Aren't Safe

A critical vulnerability in DeepSkyStackerKernel's StackWalker.cpp was silently replacing bounds-checking string functions with their unsafe counterparts via preprocessor macros, exposing the entire codebase to buffer overflow attacks. This fix removes the dangerous macro definitions that discarded buffer size arguments, restoring the intended memory safety protections across all call sites. Understanding how this subtle macro trick works is essential for any C/C++ developer working with string