Have you heard of or used Data Protection API (DPAPI) before?

What is DPAPI?

DPAPI is a cryptographic API in .NET that provides services to secure sensitive data, especially when storing it in an insecure location. It uses a combination of encryption and hashing algorithms and generates a unique encryption key for each application. But the real beauty lies in its automatic key management.

Key Management

Key management can be a painful task in cryptography. Storing, rotating, and using encryption keys securely demands due diligence. DPAPI, however, shines in this area. It generates a new key roughly every 90 days and retains old keys for 90 days for decryption purposes, removing the headache of manual key management. You can also configure the key storage to your needs.

By default, the DPAPI system provides protection at the application level. This means keys are not shared across applications unless you explicitly configure it that way. Furthermore, data protection keys have a finite lifespan, controlled by the SetDefaultKeyLifetime method, with a default of 90 days. This automatic key rotation adds another layer of protection against potential attacks.

If you have multiple instances of your application, you must store your keys in the distributed storage accessible by all instances of your application. Otherwise, you'll have issues if one instance encrypts the data with its own token and the follow-up request hits another instance with a different set of keys and attempts to decrypt it.

You can use the Azure.Extensions.AspNetCore.DataProtection.Blobs package to store your shared data protection keys in the Azure Blog Storage. Read more about it here.

Custom Configuration

There are many different ways you can customize the data protection logic.

Specifying a Custom Key Storage Location

services
    .AddDataProtection()
    .PersistKeysToAzureBlobStorage(
        new BlobClient(new Uri("blob-url")), "key-container-name");

In the above code:

  • new Uri("blob-url") is a System.Uri that points to the blob where the key file will be stored.
  • "key-container-name" is a string specifying the name of the blob container.

This configuration will store the data protection keys in the specified Azure Blob Storage.

Protecting keys to enhance security

services
    .AddDataProtection()
    .PersistKeysToAzureBlobStorage(
        new BlobClient(new Uri("blob-url")), "key-container-name")
    .ProtectKeysWithAzureKeyVault(
        new Uri("key-vault-url"), "client-id", "client-secret");

In the call to ProtectKeysWithAzureKeyVault:

  • new Uri("key-vault-url") is a System.Uri that points to the key vault.
  • "client-id" and "client-secret" are strings specifying the Azure Active Directory app registration (client) id and secret.

This setup will store the keys in Azure Blob Storage and protect them with Azure Key Vault, giving you a solid combination of distributed storage and enhanced security.

Custom Key Ring Repository

You can even create your own key repository by implementing the IXmlRepository interface:

public class CustomKeyRingStore : IXmlRepository
{
    // Implement methods
}

services
    .AddDataProtection()
    .PersistKeysToDbContext<CustomKeyRingStore>();

This interface provides methods for storing and retrieving XML elements, which you can use to create your own key storage system.

These are just some of the ways you can customize the data protection process with DPAPI. Keep in mind all the magic strings we used above, such as blob-url, key-container-name, etc., should not be hardcoded in your application but rather read from something like Azure Key Vault.

Your needs might require a combination of these or even other custom configurations. Feel free to explore the official Microsoft documentation for more details.

Protecting OAuth Tokens

Let's take a closer look at the common use case - protecting OAuth tokens. These tokens grant access to crucial resources, much like a key to a treasure chest. We don't want these keys to fall into the wrong hands, do we?

public class TokenStore 
{
    private readonly IDataProtector _protector;

    public TokenStore(IDataProtectionProvider provider) 
    {
        _protector = provider.CreateProtector("OAuthTokens");
    }

    public string ProtectToken(string plaintextToken) 
    {
        return _protector.Protect(plaintextToken);
    }

    public string UnprotectToken(string protectedToken) 
    {
        return _protector.Unprotect(protectedToken);
    }
}

This code snippet creates a 'protector' tied to a specific 'purpose' string ("OAuthTokens"). This 'purpose' string adds an extra isolation layer for the encryption keys. If a key is compromised, the damage is limited to the data protected by that specific key.

We can use this TokenStore service to shield our tokens before storing them and reveal them when required. Here's how it unfolds:

// Get the plaintext token from the OAuth provider
string plaintextToken = GetTokenFromOAuthProvider();

// Protect the token before storing it
string protectedToken = tokenStore.ProtectToken(plaintextToken);

// Store the protected token
database.SaveTokenForUser(userId, protectedToken);

And when you're ready to use the token...

// Retrieve the protected token
string savedProtectedToken = database.GetTokenForUser(userId);

// Unprotect the token
string savedPlaintextToken = tokenStore.UnprotectToken(savedProtectedToken);

// Use the token
MakeAuthenticatedRequest(savedPlaintextToken);

Conclusion

With the Data Protection API, .NET has given us robust tools to secure our sensitive data. It's built-in key management and flexible configuration make it an excellent choice for any application dealing with sensitive data.

In the .NET universe, you're not just a coder but a guardian of data and a privacy protector.

You're allowed to feel like a superhero now.