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

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

low

SQL Injection via String Formatting: How Parameterized Queries Save the Day

A database query in DBeaver's Altibase extension was constructing SQL statements using `String.format()` with user-controlled input, creating a classic SQL injection vulnerability. The fix replaces the unsafe string interpolation with parameterized queries using `PreparedStatement`, ensuring user input is always treated as data rather than executable SQL. This type of vulnerability is deceptively simple to introduce but equally simple to fix once you know what to look for.

critical

Heap Buffer Overflow in Audio Ring Buffer: How a Missing Bounds Check Could Crash Your App

A critical heap buffer overflow vulnerability was discovered in `audio_backend.c`, where the audio ring buffer's `memcpy` operations lacked bounds validation before writing PCM data. Without checking that incoming data sizes fell within the allocated buffer's capacity, a maliciously crafted audio file could corrupt adjacent heap memory, potentially enabling arbitrary code execution. The fix adds a concise pre-flight validation guard that rejects out-of-range write requests before any memory oper

critical

Critical Memory Safety Bug: Free of Uninitialized Memory in Rust Telemetry (CVE-2021-29937)

CVE-2021-29937 is a critical memory safety vulnerability in the Rust `telemetry` crate (versions prior to 0.1.3) that allows freeing uninitialized memory, leading to undefined behavior, potential crashes, and possible code execution. The fix involves upgrading the crate from version 0.1.0 to 0.1.3, which patches the unsafe memory handling at the root cause. Despite Rust's reputation for memory safety, this vulnerability demonstrates that `unsafe` code blocks can still introduce serious bugs that

critical

Critical Heap Buffer Overflow in SSDP Control Point: How Unbounded String Operations Put Networks at Risk

A critical heap buffer overflow vulnerability was discovered and patched in the SSDP control point implementation (`ssdp_ctrlpt.c`), where multiple unbounded `strcpy` and `strcat` operations constructed HTTP request buffers without any length validation. Network-received SSDP response fields — including service type strings and location URLs — could be crafted by an attacker to exceed buffer boundaries, potentially enabling arbitrary code execution or denial of service. The fix replaces the unsa

critical

Heap Buffer Overflow in OPDS Parser: How a Misplaced Variable Nearly Opened the Door to Remote Code Execution

A critical heap buffer overflow vulnerability was discovered in `lib/OpdsParser/OpdsParser.cpp`, where the buffer allocation size was calculated *after* a fixed chunk size was used to allocate memory, meaning the actual bytes read could exceed the allocated buffer. On embedded devices parsing untrusted OPDS catalog data from the network, this flaw could allow a remote attacker to corrupt heap memory and potentially achieve arbitrary code execution. The fix was elegantly simple: move the `toRead`

critical

Heap Buffer Overflow in BLE MIDI: How a Missing Bounds Check Opens the Door to Remote Exploitation

A critical heap buffer overflow vulnerability was discovered in the BLE MIDI packet assembly code of `blemidi.c`, where attacker-controlled packet length values could trigger writes beyond allocated heap memory. The fix adds an integer overflow guard before the `malloc` call, ensuring that maliciously crafted BLE MIDI packets can no longer corrupt heap memory. This vulnerability is particularly dangerous because it is remotely exploitable by any nearby Bluetooth device — no physical access requi