Introduction

Welcome to the very first lesson of the "Creating a Secure App following OWASP Top 10 Risks (Part 2)" course! In this lesson, we will explore the fundamentals of data integrity, a crucial aspect of secure data handling. Data integrity ensures that information remains accurate, consistent, and reliable throughout its lifecycle. By the end of this lesson, you'll understand the importance of data integrity and how to implement it in your applications. Let's dive in! 🚀

Understanding Data Integrity

As we discussed earlier, if an attacker already has access to some endpoints or the network, it is still important to limit their capabilities. Previously, we focused on securing specific types of data that attackers can target, such as user credentials and tokens. Now, let's consider a more generalized approach, focusing on the integrity of ANY DATA within the application. Data integrity is the assurance that data is accurate, consistent, and reliable. It plays a vital role in maintaining the trustworthiness of information, especially in applications where data is frequently accessed and modified. There are three main types of data integrity:

  • Physical Integrity: Ensures that data is stored and retrieved without corruption due to hardware failures or environmental factors.
  • Logical Integrity: Maintains the correctness and validity of data within a database, ensuring that it adheres to predefined rules and constraints.
  • Referential Integrity: Ensures that relationships between data in different tables remain consistent, preventing orphaned records or invalid references.

Understanding these types helps us appreciate the various ways data integrity can be compromised and the importance of safeguarding it. In this lesson, we primarily focus on logical and referential integrity.

To better understand how logical and referential integrity apply in real-world Java web applications, consider a case where multiple related entities (e.g., users and their posts) exist in a database. Logical integrity ensures that a user's email follows a proper format and is unique, while referential integrity ensures that every post references a valid user ID. If a user is deleted, cascading deletes or foreign key constraints are often used to maintain referential integrity. These constraints are typically enforced at the database level (e.g., PostgreSQL, MySQL).

The Vulnerable Code

Let's examine a scenario where data integrity can be compromised due to a lack of proper security measures. Consider a simple Java web application using Spring Boot that stores and retrieves snippets of code. Here's a basic implementation:

In this code, the application stores the snippet content directly without any integrity checks. This lack of verification can lead to data being altered or tampered with, compromising its integrity.

Where Should Integrity Checks Be Performed?

Before diving into implementation, it's crucial to understand where integrity checks should be enforced. This is a fundamental security principle that cannot be overlooked.

The Trust Boundary Principle
Core Security Principle: Never Trust the Client

All integrity checks MUST be performed server-side. Here's why:

LocationSecurity ValuePurpose
Server-SideREQUIRED - Actual security enforcementThis is your trust boundary. Attackers can bypass anything client-side.
Client-Side⚠️ OPTIONAL - UX only, provides NO securityEarly feedback to users, reduces unnecessary API calls.

Key Takeaway: Server-side validation is the ONLY validation that matters for security. Client-side checks are purely for user experience and should never be relied upon for security decisions.

Implementing Data Hashing

To protect data integrity, we can start by implementing data hashing. Hashing converts data into a fixed-size string of characters, which acts as a unique identifier for the data. In Java, you can use the MessageDigest class to create a hash of the data. Here's how you can implement data hashing in the POST /api/snippets endpoint:

Now, update the controller to use this hashing function:

In this code, we use the MessageDigest class to create a SHA-256 hash of the snippet content. The hash is calculated on the server and stored alongside the original data, allowing us to verify its integrity later.

Retrieving and Verifying the Snippet

When retrieving the snippet, verify its integrity using the stored hash:

In this implementation, when a snippet is stored, both the content and its hash are saved in the database. When the snippet is retrieved, the application recalculates the hash of the content and compares it with the stored hash to ensure the data has not been altered. If the hashes match, the data is considered intact and is returned to the client. If they don't match, we need to handle the integrity failure appropriately (more on this in the next section).

Implementing HMAC Verification

HMAC (Hash-based Message Authentication Code) is a mechanism that combines a cryptographic hash function with a secret key to provide both data integrity and authenticity. Unlike simple hashing, which only ensures data integrity, HMAC also verifies that the data has not been altered by an unauthorized party. This is achieved by using a secret key that is known only to the sender and receiver, making it more secure against tampering.

Important: The secret key must NEVER be exposed to the client. It should only exist on the server and be loaded from secure configuration (environment variables, secrets manager, etc.).

Here's how to implement HMAC verification in Java using the Mac and SecretKeySpec classes:

Now, update the controller to verify HMAC when retrieving a snippet:

Implementing Digital Signature Verification

A digital signature is a cryptographic technique that provides data integrity, authenticity, and non-repudiation. It involves using a pair of keys: a private key for signing the data and a public key for verifying the signature. Unlike hashing and HMAC, which primarily ensure data integrity and authenticity, digital signatures also provide non-repudiation, meaning the signer cannot deny having signed the data.

Digital signatures are ideal in distributed systems where different services or clients interact. For instance, a client signs a request payload with a private key, and the server verifies it using the public key — ensuring the request truly came from the client and hasn't been altered. This technique is common in financial APIs and cross-service authentication.

Here's how to implement digital signature verification in Java using the Signature class:

Example usage in a controller endpoint:

Choosing the Right Integrity Mechanism

Now that we've covered hashing, HMAC, and digital signatures, you might wonder: "When should I use each approach?" Here's a decision guide to help you choose:

MechanismUse CaseProvidesWhen to Use
Hash (SHA-256)Data integrity verificationIntegrity onlyWhen you need to verify data hasn't been tampered with, but authenticity isn't critical. Example: Verifying file downloads haven't been corrupted.
HMACAuthenticated data integrityIntegrity + AuthenticityWhen you need to verify both that data is intact AND that it came from a trusted source. Both parties share a secret key. Example: API request verification between services.
Digital SignatureNon-repudiable authenticationIntegrity + Authenticity + Non-repudiationWhen you need the highest level of security, including proof that a specific party signed the data. Uses public/private key pairs. Example: Admin actions, financial transactions, legal documents.

Quick Decision Guide:

  • Use Hash if you only need to detect accidental corruption or tampering.
  • Use HMAC if you need to verify the data came from a trusted source and both parties can share a secret key.
  • Use Digital Signature if you need legal proof of who signed the data, or when parties cannot share a secret key securely.
Handling Integrity Check Failures

Detecting an integrity violation is only the first step. What happens after a check fails is equally critical. An integrity failure could indicate:

  • Malicious tampering (security incident)
  • Data corruption (technical issue)
  • Software bug (development issue)
  • Network issues (infrastructure problem)
The Complete Incident Response Workflow

Here's a comprehensive approach to handling integrity failures:

Response Decision Matrix

Different scenarios require different responses:

ScenarioSeverityImmediate ActionFollow-up
Single failure, legitimate user🟡 LowLog + Return errorMonitor for patterns
Multiple failures, same user🟠 MediumLog + Temporary account lockManual review
Multiple failures, multiple users, same resource🔴 HighLog + Disable resource + Alert adminsData corruption investigation
Multiple failures, multiple resources, same IP🔴 CriticalLog + Block IP + Alert security teamPotential attack investigation
Monitoring and Alerting

Implement automated monitoring to detect attack patterns:

Data Recovery Procedures

When corruption is detected, have a recovery plan:

Key Principles for Incident Response
  1. Log Everything: Detailed forensic data is crucial for investigation
  2. Don't Expose Security Details: Return generic errors to users
  3. Monitor Patterns: A single failure might be normal; patterns indicate attacks
  4. Automate Responses: Threshold-based actions protect the system faster
  5. Have a Recovery Plan: Know how to restore data integrity when it's compromised
  6. Alert the Right People: Security team for attacks, ops team for corruption
Conclusion and Next Steps

In this lesson, we explored the fundamentals of data integrity, including its importance, common threats, and methods to ensure it. We learned:

  • What data integrity is and why it matters
  • Where integrity checks must be performed (always server-side!)
  • How to implement integrity checks using hashing, HMAC, and digital signatures
  • When to use each mechanism based on your security requirements
  • What to do when integrity checks fail (logging, alerting, recovery)

We also implemented data integrity measures in a Java web application, using techniques like hashing, HMAC verification, and digital signature verification. As you move on to the practice exercises, you'll have the opportunity to apply these concepts and reinforce your understanding. In the upcoming lessons, we'll continue to build on this foundation, exploring more advanced security topics. Keep up the great work, and let's continue to secure our applications! 🎉

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal