Encrypt and decrypt strings in ASP.NET Core using Data Protection.

9 minute read

1. Introduction

The ASP.NET Core data protection provides the cryptographic API to encrypt and decrypt the string with a purpose key that includes key management, key rotation, etc. The key can be stored securely by encrypting with Windows DPAPI or in plain text, but it is not intended to be used for web applications because it has a Windows dependency on using DPAPI to encrypt or decrypt it. We will talk more in detail in this blog post, so please stay tuned!

2. What is Data Protection?

Data protection provides the cryptographic API mentioned in our introduction to encrypt and decrypt user data using the secure purpose key. There are two types of keys there:

A. Master key
B. Purpose

2.1. Master key

The master key exists in the key ring, which is used to protect and unprotect the payload. The key is generated by the data protection provider and stored in the configured location, i.e., inside your project directory, a shared location, Azure, etc. You can choose to encrypt the master key with the Windows DPAPI, but it can run only on the Windows machine, whereas it won’t work in non-Windows environments like Linux, etc.

2.2 Purpose

The purpose is to encrypt the data and use the same to decrypt it. Without purpose, the decrypted data cannot be reversed to the original string.

3. Two ways to use data protection in the ASP.NET Core app

3.1 With DI (Dependency Injection)

Register the DataProtection service in the startup class that injects the DataProtectionProvider interface in the constructor of the controller class or any class, and make use of it to create the protector to protect or unprotect the string.

3.2 Without DI (Dependency Injection)

Create the data protection using the DataProtectionProvider concrete class and make use of it in your controller to create the protector to protect or unprotect the string.

4. Data Protection with DI

4.1 Startup class

Register the Data Protection service with the directory info to store the key in the key ring in the given location. Also, there is a builder service option to set the application name to distinguish the key between your apps so that each app has individual keys; otherwise, the other apps will share the same key.

public void ConfigureServices(IServiceCollection services)
{
  // Data protection service for crptography
  services.AddDataProtection()
          .SetApplicationName("MyAppName")
          .PersistKeysToFileSystem(new DirectoryInfo(@"wwwroot/DataProtectionKeys"));
}

4.2 Data Protection custom Class

public class DataProtection {

  private IDataProtectionProvider _dataProtectionProvider;
  private IDataProtector _protector;

  // Data Protection class constructor 
  public DataProtection(IConfiguration configuration, IDataProtectionProvider dataProtectionProvider)
  {
      // Create the data protector from data protection provider with the purpose key
      _protector = dataProtectionProvider.CreateProtector("MyPurpose");
  }

  // Encrypt the user data
  public string Encrypt(string data)
  {
      return _protector.Protect(data);
  }

  // Decrypt the protected data
  public string? Decrypt(string data)
  {
      try
      {
          return _protector.Unprotect(data);
      }
      catch (CryptographicException ex)
      {
          return null;
      }
  }
}

5. Data Protection without DI

5.1 Startup class

There is no need to register the Data Protection service in the startup class since some of the projects have different requirements and don’t want to use dependency injection. There is another approach to registering the data protection in the startup class and creating the data protector in the custom class that makes you create the data protection provider once in the project initial load. Comment below to share Method 3 if you are curious to know about it.

5.2 Data Protection custom Class

public class DataProtection {

  private IDataProtectionProvider _dataProtectionProvider;
  private IDataProtector _protector;

  // Data Protection class constructor 
  public DataProtection(IConfiguration configuration)
  {
    // Creates the data protection provider and stores the generated key & other optional configurations
    _dataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(@configuration.GetSection("DataProtection:KeyPath").Value), (builder) => { builder.SetApplicationName("MyApplicationName"); });

    _protector = _dataProtectionProvider.CreateProtector("MyPurpose");
  }

  public string Encrypt(string data)
  {
      return _protector.Protect(data);
  }

  public string? Decrypt(string data)
  {
      try
      {
          return _protector.Unprotect(data);
      }
      catch (CryptographicException ex)
      {
          return null;
      }
  }
}

6. Register Data Protection with Windows DPAPI to encrypt the master key

6.1 Startup class

Protect your key in the key ring with DPAPI

public void ConfigureServices(IServiceCollection services)
{
  // Data protection service for crptography
  services.AddDataProtection()
          .SetApplicationName("MyAppName")
          .PersistKeysToFileSystem(new DirectoryInfo(@"wwwroot/DataProtectionKeys"))
          .ProtectKeysWithDpapi();
}

7. Encrypt and decrypt string

7.1 Controller method using custom data protection class

public class UserController : ControllerBase
{
    IConfiguration _configuration;
    private IDataProtectionProvider _dataProtectionProvider;

    public PermissionController(IConfiguration configuration, IDataProtectionProvider dataProtectionProvider) {
        _configuration = configuration;
        _dataProtectionProvider = dataProtectionProvider;
    }

    /// <summary>
    /// Encrypt the string using the data protection class
    /// </summary>
    /// <param name="data"></param>
    /// <param name="purpose"></param>
    /// <returns></returns>
    [HttpGet]
    public ActionResult<string> Encrypt(string data, string purpose)
    {
        if(purpose != Constants.DataProtection.Purpose)
        {
            return BadRequest();
        }

        DataProtection dataProtection = new DataProtection(_configuration, _dataProtectionProvider);
        return dataProtection.Encrypt(data);
    }

    /// <summary>
    /// Decrypt the encrypted string using the data protection class
    /// </summary>
    /// <param name="data"></param>
    /// <param name="purpose"></param>
    /// <returns></returns>
    [HttpGet]
    public ActionResult<string> Decrypt(string data, string purpose)
    {
        if (purpose != Constants.DataProtection.Purpose)
        {
            return BadRequest();
        }

        DataProtection dataProtection = new DataProtection(_configuration, _dataProtectionProvider);
        var decryptedString = dataProtection.Decrypt(data);
        if(decryptedString == null)
        {
            return BadRequest();
        }
        return decryptedString;
    }
}

7.2 Output

7.2.1 Postman results of encryption

 
Fig 1. Encryption using ASP.NET Core Data Protection

7.2.2 Postman results of decryption

 
Fig 2. Decryption using ASP.NET Core Data Protection

Note: Instances of the DataProtectionProvider concrete type are expensive to create. If an app maintains multiple instances of this type and if they’re all using the same key storage directory, app performance might degrade.

Thanks for reading the article on Data Protection to Protect User Data in the ASP.NET Core. Please feel free to drop your comments below, and you can follow us on Twitter (@Codetoliveblog) to receive the latest updates from this blog.

Leave a comment