Logo

dev-resources.site

for different kinds of informations.

Implementing HTTP Request and Response Encryption in ASP.NET Core with Custom Attributes

Published at
11/7/2024
Categories
aes
encryption
aspnetcore
Author
imzihad21
Categories
3 categories in total
aes
open
encryption
open
aspnetcore
open
Author
9 person written this
imzihad21
open
Implementing HTTP Request and Response Encryption in ASP.NET Core with Custom Attributes

In today's world of web development, securing the transmission of sensitive data over the internet is of paramount importance. A critical aspect of this security is ensuring that both HTTP requests and responses are encrypted. This article demonstrates how to implement a custom HTTP encryption mechanism in an ASP.NET Core Web API using a combination of attributes, filters, and AES encryption.

We'll walk through the process of creating an HttpEncryptionAttribute that encrypts and decrypts HTTP request and response bodies. This solution ensures that any sensitive data in transit is securely encrypted before being sent over the wire and decrypted once it reaches the server or client.

Prerequisites

To follow along with this guide, you'll need:

  • A basic understanding of ASP.NET Core and its middleware pipeline.
  • Familiarity with encryption concepts, particularly AES encryption.
  • .NET 6 or later for compatibility with the latest features and libraries.

Overview of the Approach

The approach leverages an attribute-based filter system to automatically encrypt and decrypt data as part of the request and response lifecycle. This is achieved through the use of custom attributes (HttpEncryptionAttribute) and filters (HttpEncryptionAttributeFilter), which interact with the HTTP context to encrypt or decrypt streams using AES encryption.

Let's break down the code step by step:

Step 1: The HttpEncryptionAttribute

The HttpEncryptionAttribute is the core of this solution. This custom attribute is applied to controllers or actions that require encryption. It implements the IFilterFactory interface, allowing it to produce a filter when applied.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class HttpEncryptionAttribute : Attribute, IFilterFactory
{
    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
        var config = serviceProvider.GetRequiredService<IOptions<ApplicationConfiguration>>();
        return new HttpEncryptionAttributeFilter(config.Value);
    }
}

Enter fullscreen mode Exit fullscreen mode
  • AttributeUsage: Specifies that this attribute can be applied to both classes and methods.
  • CreateInstance: When the attribute is applied, this method is invoked to create an instance of HttpEncryptionAttributeFilter, passing in the application configuration (such as the encryption key) from the DI container.

Step 2: The HttpEncryptionAttributeFilter

The HttpEncryptionAttributeFilter is responsible for intercepting the request and response flows. It implements the IAsyncResourceFilter interface, which provides hooks to run code before and after an action executes. The encryption and decryption logic are handled here.

public class HttpEncryptionAttributeFilter : IAsyncResourceFilter
{
    private readonly Aes _aes;

    public HttpEncryptionAttributeFilter(ApplicationConfiguration appConfig)
    {
        _aes = GenerateAes(appConfig.HttpEncryptionKey);
    }

    public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
    {
        context.HttpContext.Request.Body = DecryptStream(context.HttpContext.Request.Body);
        context.HttpContext.Response.Body = EncryptStream(context.HttpContext.Response.Body);

        if (context.HttpContext.Request.QueryString.HasValue)
        {
            var decryptedQueryString = DecryptString(context.HttpContext.Request.QueryString.Value[1..]);
            context.HttpContext.Request.QueryString = new QueryString($"?{decryptedQueryString}");
        }

        await next();
        await context.HttpContext.Request.Body.DisposeAsync();
        await context.HttpContext.Response.Body.DisposeAsync();
    }

    private CryptoStream EncryptStream(Stream responseStream)
    {
        var encryptor = _aes.CreateEncryptor();
        var base64Transform = new ToBase64Transform();
        var base64EncodedStream = new CryptoStream(responseStream, base64Transform, CryptoStreamMode.Write);
        return new CryptoStream(base64EncodedStream, encryptor, CryptoStreamMode.Write);
    }

    private CryptoStream DecryptStream(Stream cipherStream)
    {
        var decryptor = _aes.CreateDecryptor();
        var base64Transform = new FromBase64Transform(FromBase64TransformMode.IgnoreWhiteSpaces);
        var base64DecodedStream = new CryptoStream(cipherStream, base64Transform, CryptoStreamMode.Read);
        return new CryptoStream(base64DecodedStream, decryptor, CryptoStreamMode.Read);
    }

    private string DecryptString(string cipherText)
    {
        using var memoryStream = new MemoryStream(Convert.FromBase64String(cipherText));
        using var cryptoStream = new CryptoStream(memoryStream, _aes.CreateDecryptor(), CryptoStreamMode.Read);
        using var reader = new StreamReader(cryptoStream);
        return reader.ReadToEnd();
    }

    private static Aes GenerateAes(string encryptionKey)
    {
        var key = encryptionKey.PadRight(32, '0');
        var aes = Aes.Create();
        aes.Key = Encoding.UTF8.GetBytes(key[..32]);
        aes.IV = Encoding.UTF8.GetBytes(key[..16]);
        aes.Mode = CipherMode.CBC;
        aes.Padding = PaddingMode.PKCS7;
        return aes;
    }
}
Enter fullscreen mode Exit fullscreen mode
  • OnResourceExecutionAsync: This is the main method where the request and response streams are encrypted and decrypted. The request body is decrypted before the action executes, and the response body is encrypted before being sent back to the client.
  • DecryptStream and EncryptStream: These methods wrap the original request/response streams with CryptoStream objects, applying AES decryption and encryption, respectively.
  • DecryptString: This method is used to decrypt query strings if they are present in the request.

Step 3: AES Encryption Logic

The encryption mechanism uses the AES algorithm, a symmetric encryption standard, to handle the encryption and decryption of data. AES is configured with a key and initialization vector (IV) derived from the HttpEncryptionKey provided in the application configuration.

private static Aes GenerateAes(string encryptionKey)
{
    var key = encryptionKey.PadRight(32, '0');
    var aes = Aes.Create();
    aes.Key = Encoding.UTF8.GetBytes(key[..32]);
    aes.IV = Encoding.UTF8.GetBytes(key[..16]);
    aes.Mode = CipherMode.CBC;
    aes.Padding = PaddingMode.PKCS7;
    return aes;
}
Enter fullscreen mode Exit fullscreen mode
  • Key & IV: The AES key is derived from the provided encryption key. If the key is shorter than required, it is padded. The IV is extracted from the first 16 bytes of the encryption key.
  • Padding: PKCS7 padding is used to ensure that the data length is a multiple of the block size.

Step 4: Applying the Attribute

To apply the encryption to your API endpoints, simply decorate your controllers or actions with the HttpEncryptionAttribute.

[HttpEncryption]
[Route("api/[controller]")]
public class SensitiveDataController : ControllerBase
{
    [HttpPost]
    public IActionResult PostSensitiveData([FromBody] SensitiveData data)
    {
        // Sensitive data processing logic
        return Ok();
    }
}
Enter fullscreen mode Exit fullscreen mode

This ensures that both the request and response data are automatically encrypted and decrypted by the HttpEncryptionAttributeFilter.

Optional: Frontend Implementation using Axios and crypto-js

API Wrapper (Axios Interceptor)

This file creates a custom Axios instance to handle API requests and responses. It encrypts query parameters and request data before sending them and decrypts the response data upon receiving it.

import axios from 'axios';
import { BASE_URL } from '../../constants/ServerEndpoints';
import { decryptData, encryptData } from '../../utilities/encryptionUtils';

const secureAPI = axios.create({ baseURL: BASE_URL });

secureAPI.interceptors.request.use((config) => {
  const [endpoint, queryParams] = config.url ? config.url.split('?') : [];

  if (queryParams) {
    config.url = `${endpoint}?${encryptData(queryParams)}`;
  }

  if (config.data) {
    config.headers['Content-Type'] = 'application/json';
    config.transformRequest = encryptData;
  }

  config.transformResponse = decryptData;

  return config;
});

export default secureAPI;
Enter fullscreen mode Exit fullscreen mode

Encryption Utilities

This file provides two utility functions, encryptData and decryptData, using AES encryption with a fixed key and IV. It securely encrypts input data (e.g., API requests) and decrypts ciphertext while handling JSON parsing.

import CryptoJS from 'crypto-js';
import { SECRET_KEY } from '../constants/envVariables';

const keyString = SECRET_KEY.padEnd(32, '0');

const encryptionKey = CryptoJS.enc.Utf8.parse(keyString.substring(0, 32));
const initializationVector = CryptoJS.enc.Utf8.parse(keyString.substring(0, 16));

const encryptionOptions = {
  iv: initializationVector,
  mode: CryptoJS.mode.CBC,
  padding: CryptoJS.pad.Pkcs7
};

export const encryptData = (input) => {
  if (input === null || input === undefined) return input;

  const data = CryptoJS.enc.Utf8.parse(
    typeof input === 'string' ? input : JSON.stringify(input)
  );

  const encrypted = CryptoJS.AES.encrypt(data, encryptionKey, encryptionOptions);

  return CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
};

export const decryptData = (encryptedInput) => {
  if (!encryptedInput) return encryptedInput;

  try {
    const encryptedBytes = CryptoJS.enc.Base64.parse(encryptedInput);

    const decrypted = CryptoJS.AES.decrypt(
      { ciphertext: encryptedBytes },
      encryptionKey,
      encryptionOptions
    ).toString(CryptoJS.enc.Utf8);

    try {
      return JSON.parse(decrypted);
    } catch (_) {
      return decrypted;
    }
  } catch (_) {
    return encryptedInput;
  }
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

This solution leverages ASP.NET Core's powerful middleware and filter mechanisms to seamlessly encrypt and decrypt HTTP request and response bodies. By using custom attributes and filters, you can secure sensitive data in your API without modifying the individual actions or controllers. This is particularly useful when you need to enforce encryption across an entire set of endpoints with minimal overhead.

Incorporating encryption at this level ensures that your API meets security standards for data-in-transit encryption, mitigating risks associated with man-in-the-middle attacks and ensuring the privacy and integrity of sensitive data.

aspnetcore Article's
30 articles in total
Favicon
[ASP.NET Core] Try reading a word processing file by OpenXML 2
Favicon
Deploy Your Bot to Azure in 5 Minutes with Azure Developer CLI (azd)
Favicon
How to Implement Passkey in ASP.NET Core with Fido2-net-lib?
Favicon
Building Async APIs in ASP.NET Core - The Right Way
Favicon
How To Improve Performance Of My ASP.NET Core Web API In 18x Times Using HybridCache In .NET 9
Favicon
Using React for the client-side of an ASP.NET Core application
Favicon
.NET 9 Improvements for ASP.NET Core: Open API, Performance, and Tooling
Favicon
🚀Build, Implement, and Test gRPC Services with .NET9
Favicon
[ASP.NET Core] Try reading a word processing file by OpenXML 1
Favicon
[Vite + React] Running an ASP.NET Core application behind a reverse proxy
Favicon
Implementing Idempotent REST APIs in ASP.NET Core
Favicon
✅ASP.NET Core API Gateway with Ocelot Part 4 (Rate Limiting)
Favicon
Problem Details for ASP.NET Core APIs
Favicon
[ASP.NET Core][EntityFramework Core] Update from .NET 6 to .NET 8
Favicon
HybridCache in ASP.NET Core - New Caching Library
Favicon
Link Many To Many entities with shadow join-table using Entity Framework Core
Favicon
🚀 𝐁𝐨𝐨𝐬𝐭 𝐘𝐨𝐮𝐫 𝐀𝐏𝐈 𝐒𝐤𝐢𝐥𝐥𝐬 𝐰𝐢𝐭𝐡 𝐌𝐲 𝐎𝐜𝐞𝐥𝐨𝐭 𝐆𝐚𝐭𝐞𝐰𝐚𝐲 𝐄𝐬𝐬𝐞𝐧𝐭𝐢𝐚𝐥𝐬!
Favicon
Implementing HTTP Request and Response Encryption in ASP.NET Core with Custom Attributes
Favicon
Reducing Database Load Without Relying on MARS
Favicon
Task Cancellation Pattern
Favicon
Custom Role-Based Authorization with JWT in ASP.NET Core
Favicon
Flexibilidad y Escalabilidad: Usando Strategy y Factory Patterns
Favicon
Building a Custom Logging Provider in ASP.NET Core
Favicon
ASP.NET Core Middleware
Favicon
How to Use .NET Aspire (Part2)
Favicon
How To Test Integrations with APIs Using WireMock in .NET
Favicon
Connecting NestJS and ASP.NET Core with gRPC: A Step-by-Step Guide
Favicon
Implementing ASP.NET Identity for a Multi-Tenant Application: Best Practices
Favicon
Welcome to .NET 9 Preview
Favicon
How use a Blazor QuickGrid with GraphQL

Featured ones: