Does Encryption Ensure Data Integrity?

Mar 5, 2024 9:01:01 AM / by Flake Redmond and Ali Esparza

In today’s digital world, we all want to be sure that the data we share with applications is properly safeguarded throughout its life cycle, including while it’s in transit across the internet and at rest on a server. We wouldn’t feel comfortable exposing sensitive data to applications or transferring it between them without assurance that something was being done to protect it. Of course, many applications prioritize data security and implement various measures to protect users’ data from malicious actors. The most common of these is encryption, a cryptographic process that transforms data so that it appears random and can be deciphered only by authorized parties.

While cryptography is just one of the system components that security analysts cover in security assessments, it’s arguably the most crucial to get right. A mistake in a cryptographic implementation can be catastrophic. However, many people don’t realize that getting the cryptography right does not make a system bulletproof. For example, while securing the connection to a site through TLS and encrypting back-end data through AES-256 are important measures, they can lead to a false sense of security. After all, the main goal of encryption is protecting the confidentiality of data—but does it ensure the integrity of that data?

This blog post will explore that question by examining the use, misuse, and limitations of cryptography. But first, we’ll look at the CIA triad, which defines three security principles that are key to the development of a secure system.

CIA Triad

When security professionals analyze an application’s security posture, we examine the many ways that data is used in the application and the security controls that have been implemented to protect that data. To guide that process, we typically break up the larger concept of data security into the three principles defined by the CIA triad:

  • Confidentiality: The principle of confidentiality is all about protecting information from unauthorized access. However, maintaining the confidentiality of data is not necessarily the same as preventing an attacker from obtaining that data. Instead, confidentiality means that if an attacker does obtain data from an application (by capturing it over a network, for example), the data will be formatted such that the attacker cannot read or do anything with it. This is where good encryption comes into play.
  • Integrity: Data integrity involves ensuring that data can be trusted and has not been altered by unauthorized individuals at any point in its life cycle. The principle is enforced through the use of hashing, digital certificates, or digital signatures.

Say that an attacker were able to capture application data in transit over a network. What would happen if the attacker modified that data, perhaps in an attempt to impersonate another user, and then passed it on to its original destination? Well, if the application implemented strong integrity checks, it would be able to determine that the data was not as it should be.

  • Availability: This principle focuses on ensuring that an organization’s systems, networks, and applications are functioning properly so that users with permission to access data can actually access it. What good would strong confidentiality and integrity checks be if the data they protected couldn’t be made available to those who needed it?

Ensuring that a system is properly protecting its data is clearly a complicated process, so it’s no surprise that developers occasionally overlook something or confuse one data protection technique with another. That brings us to one of the most misunderstood data security practices—encryption—and the common misconception that encryption guarantees data integrity.

What is encryption?

First, let’s quickly go over what exactly encryption is. Encryption is a computing process that alters data such that it appears random and can be deciphered only by parties that have the correct cryptographic key. The process can range from very simple to extremely complex, but regardless, the main goal is to render sensitive data unreadable and unusable by unauthorized parties.

Encryption is a powerful method of ensuring the confidentiality of data, but it does not necessarily ensure the integrity of data. Some cryptographic algorithms, such as RSA, ensure integrity in addition to implementing encryption, while others, such as the one-time pad cipher, do not. The one-time pad cipher provides perfect secrecy, which is the strongest form of confidentiality. Perfect secrecy means that an attacker can obtain little to no information about a plaintext message from the encrypted message (the ciphertext). However, the one-time pad cipher does not provide any mechanism for ensuring the integrity of ciphertext. In other words, if an attacker changes even one bit of ciphertext, the recipient will have no way of determining that the message has been altered.

How can an application ensure data integrity?

There are many techniques for guaranteeing integrity in cryptographic operations, including the following:

  • Message authentication codes (MACs): MACs are checksums used to check the authenticity and integrity of cryptographic messages. The MAC for a message is generated by a cryptographic algorithm that takes the message and a key shared by its sender and recipient as input. The value is then appended to the message and sent along with it. The recipient of the message can check its integrity by using the same algorithm to recompute the MAC. If the message has been changed, the newly generated value will not match the original MAC, and the recipient will know that the integrity of the message has been violated.
  • Digital signatures: A digital signature is a cryptographic output created with a private key and later verified with a public key. Only the holder of a private key can create a signature from it, but anyone with the public key can verify that signature.
  • Unkeyed hash: Like a MAC, an unkeyed hash value is appended to a message; however, unlike a MAC, it is generated from only the message, not the message and a key. Although an unkeyed hash value can help prevent accidental changes to a message, an attacker could just change the message and rehash it.

Let’s dive a bit deeper and explore a real-world data integrity technique: the use of JSON Web Tokens (JWTs).

How Integrity Checks Protect JWTs

JWT is an open standard that provides a way to communicate information in the form of a JSON object. JWTs are commonly used in the real world for authorization and information exchange purposes.

Ideally, a JWT should end with a JSON Web Signature, which represents a digital signature. As long as the digital signature on a JWT is strong and the application using the JWT has implemented proper integrity checks, the information in the JWT can be trusted and verified. (JWTs can also be encrypted and turned into JSON Web Encryption tokens, but in this post, we’ll just focus on how integrity checks protect JWTs.)

Picture1-4

How are JWTs structured?

A Base64-encoded JWT comprises three sections: a header, a payload (claims), and a signature. These sections are separated by periods (.), as shown below.

The header contains metadata about the cryptographic algorithm used to secure the JWT.

The payload contains statements such as the user’s identity and the expiration date of the token. These statements, known as claims, can also include other data relevant to the application.

The signature is the final part of the JWT and, for the purpose of this blog post, the most critical. The digital signature on a JWT is used to guarantee the integrity of the message. The signature is generated by the algorithm specified in the header, which takes in the Base64-encoded JWT header and payload, along with a secret key. The recipient of a JWT must check that its signature matches its content.

The pseudocode below demonstrates the use of the HMAC SHA-256 algorithm to produce a signature.

header = base64UrlEncode({HEADER})

payload = base64UrlEncode({PAYLOAD})

message = header + "." + payload

{SIGNATURE} = HMACSHA256(message, secret-key)

Let’s take a closer look at the payload of the above JWT. Here’s the decoded payload:

Picture1-Feb-20-2024-02-48-55-5066-PM

The payload contains the name of the user, an expiration date (as a Unix timestamp), and the role of the user, which determines the permissions granted to the user in the application utilizing the JWT. Of course, the JWT does not protect the confidentiality of the information it contains, and that information would be exposed to anyone able to capture it and decode it from Base64. The only component of the JWT that provides data security is its digital signature.

Let’s imagine that an attacker captured the JWT and that the application using the JWT had not implemented any integrity checks (or had implemented them incorrectly). What could the attacker do? Well, the attacker could reuse the JWT. Specifically, the attacker could decode the JWT, change the value of the role parameter in its payload to admin, and then send the JWT to the application. The new payload would look something like this:

Picture1-Feb-20-2024-02-49-31-9385-PM

The payload would then be Base64 encoded, transforming the JWT into the following:

Picture1-Feb-20-2024-02-50-07-0884-PM

Note that the signature in the JWT has not changed, as the JWT has not been re-signed. Only the second section, the payload, has been altered, which means that the JWT is invalid. However, the application would still accept the JWT and identify the attacker as an admin.

How Integrity Checks Can Go Wrong

Now let’s imagine that our hypothetical web application uses a secure communication channel for the transmission of JWTs. Let’s also say that its server correctly verifies the integrity of a JWT by using the JWT’s secret key to recompute its signature and then comparing the signature to that received from the client. Everything sounds good, right? It is not possible for an attacker to tamper with a JWT, and the communication channel is secure.

Well, let’s look at the code snippet below, which shows a function in our hypothetical web application. The function, list_all_assets(), is an admin-level feature that takes a user’s JWT as input and returns a comprehensive list of the system’s assets. Can you see what’s wrong with the pseudocode?

function list_all_assets( jwt ){

       header = extract_header( jwt )

       payload = extract_payload( jwt )

       client_signature = extract_signature( jwt )

       message = header + "." + payload

server_signature = HMACSHA256(message, “youwillneverguessthissecret”)

       if ( server_signature == client_signature ){

              return asset_list

}

return “Invalid signature.”

}

At first glance, it looks like the most crucial part of the function, verification of the JWT’s signature, is correct. However, there is one glaring flaw: The function does not check the claims in the payload to ensure that the user is indeed an admin. This means that any non-admin user could obtain a list of all system assets. Although the problem seems obvious when laid out this way, you’d be stunned by how often a mistake like that one is made.

Let’s fix the code by adding in the logic necessary for a check of user permissions. That logic can follow the verification of the signature.

if ( server_signature == client_signature ){

isAdmin = base64decode(payload)[“isadmin”]

if ( isAdmin ){

return asset_list

}

return “You are unauthorized.”

}

This is why integrity checks are crucial to the cryptographic operations within a system. If an application does not perform proper integrity checks, it will interpret invalid JWTs as valid and have no way of determining whether JWTs were sent by legitimate users.

Conclusion

The common misconception that encryption ensures integrity is just that—a misconception. Just because an application uses cryptographic operations does not mean that the application checks the integrity of the user input passed to those operations (such as JWTs). Moreover, implementing integrity checks and verifying the correctness of cryptographic operations will do little to ensure security if an application does not also check users’ permissions. Developers must treat encryption and integrity checks as separate processes that work in tandem to guarantee the confidentiality and integrity of application data.

At ISE, we understand the importance of ensuring the confidentiality and integrity of data—and have nearly two decades of experience in helping clients do just that. To learn more about how our team can help you protect your application and its data, get in touch with one of our security experts today.

Subscribe to Our Blog

Stay up-to-date on the latest ISE and cybersecurity news.

We're committed to your privacy. ISE uses the information you provide to us to contact you about our relevant content, products, and services. You may unsubscribe from these communications at any time. For more information, check out our privacy policy.