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.

encryption Article's
30 articles in total
Favicon
Encryption in React Native apps enhances data security, protecting user information and ensuring privacy. However, it also presents challenges, such as performance overhead and complex implementation
Favicon
A Deep Dive into WhatsAppโ€™s Encryption: Identity, Keys, and Message Security
Favicon
The Birthday Paradox: A Statistical Breakdown and How it Relates to Online Security
Favicon
Obfuscating โ€œHello world!โ€ obfuscate on Python
Favicon
Introducing Inline Cryptography Toolkit: Simplify Encryption, Decryption, and Hashing in VS Code ๐Ÿš€
Favicon
Advantages of Asymmetric Encryption with Random Public and Symmetric Private Keys
Favicon
Cloud Security Challenges and Encryption, Identity Management, and Compliance
Favicon
Microsoft Certified Azure Administrator Associate Exam (AZ-104) Lab Preparation #5: Azure Disk Encryption
Favicon
Encryption: ciphers, digests, salt, IV
Favicon
The Evolution of Hashing Algorithms: From MD5 to Modern Day
Favicon
Email Security and Data Protection for Startups: Affordable Solutions
Favicon
Understanding SNI (Server Name Indication) and Modern Encryption Solutions
Favicon
What is RSA Asymmetric Encryption? Basics, Principles and Applications
Favicon
Encryption Vs. Decryption: Whatโ€™s the Difference?
Favicon
Laravel Data Encryption and Decryption
Favicon
Unveiling A Groundbreaking Open-Source Encrypted Machine Learning Framework
Favicon
Secure Text Encryption and Decryption with Vanilla JavaScript
Favicon
Understanding SSL/TLS: The Role of Encryption and Security Protocols in Internet Communication
Favicon
Implications for Encryption and Cybersecurity with Quantum Computing
Favicon
How to Generate Your Own Public and Secret Keys for PGP Encryption
Favicon
Does S/MIME Encrypt Emails and Attachments?
Favicon
What is Token Signing in Software Publisher Certificate?
Favicon
Encryption Symmetric
Favicon
Secret management for the layman
Favicon
JavaScript Base-32 Encryption
Favicon
Comprehensive Encryption and Security Service in NestJS: Argon2 Hashing, Token Generation, and AES Encryption
Favicon
Difference Between Encryption and Hashing ๐Ÿ”๐Ÿ”‘
Favicon
Ransomware Explained: How It Works and Best Defense Mechanisms to Protect Your Data
Favicon
Debunking Most Common Cloud Computing Myths & Misconceptions
Favicon
Implementing HTTP Request and Response Encryption in ASP.NET Core with Custom Attributes

Featured ones: