Encrypt and Decrypt users' data in ASP.NET Core Identity using the ILookupProtector and ILookupProtectorKeyRing interfaces
Tags: dotnetcore, aspnetcore, identity, csharp
Published: 2023-04-01 09:47:33

Decrypting and Encrypting users' data in ASP.NET Core Identity are straightforward task; all you have to do is to implement a two interface to accomplish this task.
In this new post I'm going to show you how to encrypt and decrypt the users' data in ASP.NET Core Identity targeting .NET 7
You can buy my full course on Udemy that discuss more advanced topics in ASP.NET Core Identity from here: https://www.udemy.com/course/advanced-aspnet-core-identity-in-net-7/

So, Encrypting and decrypting data is high level way to secure your content, and there is more than one algorithm help us to do that. Here I'm going to user AES algorithm (Advanced Encryption Standard) to complete my task, so let's do that.
I suppose you have a ready ASP.NET Core Application with configured Identity Core. All I have to do here is to configure the relate options that I need to let ASP.NET Core Identity know how to encrypt and decrypt users' data.

To decrypt and encrypt the user data in ASP.NET Core Identity you have to implement two interfaces the first one is **ILookupProtectorKeyRing** and the second one is the **ILookupProtector**.

**The ILookupProtectorKeyRing:**
This interface has three members and all these members related to the key that we are going to use when we decrypt and encrypt users' data and as you can see below these members: (more details later)

```C#
string CurrentKeyId { get; }
string this[string keyId] { get; }
IEnumerable<string> GetAllKeyIds();
```

**The ILookupProtector:**
This interface has two members the first one to encrypt the users' data and the second one to decrypt the data that you encrypted (more details later):

```C#
string? Protect(string keyId, string? data);
string? Unprotect(string keyId, string? data);
```

## The Implementation inside our Application

So, I have a folder in my application root named Infrastructure, inside this folder I have create a new class named **CustomLookupProtectorKeyRing** in this class I'm going to implement the _ILookupProtectorKeyRing_ interface:

```C#
public class CustomLookupProtectorKeyRing : ILookupProtectorKeyRing
{
    public string this[string keyId]
    {
        get
        {
            return GetAllKeyIds().Where(x => x == keyId).FirstOrDefault();
        }
    }

    public string CurrentKeyId
    {
        get
        {
            byte[] key = { 200, 15, 147, 5, 155, 78, 118, 57, 180, 179, 60, 150, 188, 18, 165, 134 };
            var currentKey = Convert.ToBase64String(key);
            return currentKey;
        }
    }

    public IEnumerable<string> GetAllKeyIds()
    {
        var list = new List<string>();
        // This is 24 bytes length
        byte[] key = { 200, 15, 147, 5, 155, 78, 118, 57, 180, 179, 60, 150, 188, 18, 165, 134 };
        byte[] key2 = { 242, 207, 146, 81, 121, 231, 168, 93, 89, 130, 4, 68, 18, 185, 98, 154 };
        byte[] key3 = { 101, 104, 174, 233, 88, 29, 20, 16, 21, 216, 249, 45, 148, 18, 102, 150 };

        list.Add(Convert.ToBase64String(key));
        list.Add(Convert.ToBase64String(key2));
        list.Add(Convert.ToBase64String(key3));


        return list;
    }
}
```
_First_ the **GetAllKeyIds()** method holds all available keys that you have to use it with the encryption algorithm. All the key here is 24 bytes length.
_Second_ the **CurrentKeyId** property returns the current that are going to use in the encryption and decryption process.
_The last thing_ is an indexer (this) to search for any key by its position (indexing).

So, our first class is done, we need another class and the mission of this class is to encrypt and decrypt the users' data, so in the Infrastructure folder create a new class named **CustomLookupProtector** in this class I'm going to implement the _ILookupProtector_ interface:

```C#
public class CustomLookupProtector : ILookupProtector
{

    byte[] iv = { 208, 148, 29, 187, 168, 51, 181, 178, 137, 83, 40, 13, 28, 177, 131, 248 };
    public string Protect(string keyId, string data)
    {
        byte[] plainTextBytes = Encoding.UTF8.GetBytes(data);

        string cipherText;
        using (SymmetricAlgorithm algorithm = Aes.Create())
        {
            using (ICryptoTransform encryptor = algorithm.CreateEncryptor(Encoding.UTF8.GetBytes(keyId), iv))
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
                        cryptoStream.Close();
                        byte[] chiperTextByte = ms.ToArray();
                        cipherText = Convert.ToBase64String(chiperTextByte);
                    }
                }
            }
        }

        return cipherText;
    }


    public string Unprotect(string keyId, string data)
    {
        byte[] cipherTextBytes = Convert.FromBase64String(data);
        string plainText;
        using (SymmetricAlgorithm algorithm = Aes.Create())
        {
            using (ICryptoTransform decrypter = algorithm.CreateDecryptor(Encoding.UTF8.GetBytes(keyId), iv))
            {
                using (MemoryStream ms = new MemoryStream(cipherTextBytes))
                {
                    using (CryptoStream cryptoStream = new CryptoStream(ms, decrypter, CryptoStreamMode.Read))
                    {
                        using (StreamReader streamReader = new StreamReader(cryptoStream))
                        {
                            plainText = streamReader.ReadToEnd();
                        }
                    }
                }
            }
        }

        return plainText;
    }
}
```
_First_, we have a **Protect()** method this method accept two arguments the first one is the **keyId** by default the value of this parameter is coming from the **CurrentKeyId** property that is defined in the **CustomLookupProtectorKeyRing** class that we created in the previous step. The second parameter is the data, and ASP.NET Core Identity by default will encrypt five columns in the AspNetUsers table:

1. UserName
2. NormalizedUserName
3. Email
4. NormalizedEmail
5. PhoneNumber

So the **Protect()** will be call five times during the creation process of the user account. The remaining or the implementation of the method is very clear and it a pure C# code that will encrypt a plain text (data) and then return the encrypted data as Base64.
_Second_, we have an **Unprotect()** method which is do the opposite job of **Protect()** method and also it accept two arguments the first on is the keyId and this is the same key that you used when you encrypt the data and the second parameter is the encrypted data (UserName, NormalizedUserName, Email, NormalizedEmail, PhoneNumber) and after that will return the original data for user.

The last things we need to do is to inform the ASP.NET Core Identity we are going to encrypt the users' data by using the in our application and to accomplish this task we need to use the **AddPersonalDataProtection** extension method and this method accept a two type parameters the first one a class that should implement **ILookupProtector** interface and the second type parameter should be a class that implement **ILookupProtectorKeyRing** so, open the Program.cs class
 and in the registration service of the ASP.NET Core Identity add this line right after **AddDefaultTokenProviders** method:

```C#
.AddPersonalDataProtection<CustomLookupProtector, CustomLookupProtectorKeyRing>();
```
One more thing in the configuration option of ASP.NET Core Identity add also the below line. 
`(Be sure to add this option because without it aspnet core identity will not going to encrypt and decrypt users' data) `

```C#
options.Stores.ProtectPersonalData = true; 
```

Here is the complete configuration:

```C#
builder.Services.AddIdentity<AppUser, IdentityRole>(options =>
{
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Password.RequiredLength = 5;
    options.Password.RequireUppercase = false;
    options.Password.RequireNonAlphanumeric = false;

    options.User.RequireUniqueEmail = true;

    options.SignIn.RequireConfirmedAccount = true;
    options.SignIn.RequireConfirmedEmail = true;

    options.Lockout.AllowedForNewUsers = false;
    options.Lockout.DefaultLockoutTimeSpan = System.TimeSpan.FromMinutes(3); // The Default Lock time is 5 minutes
    options.Lockout.MaxFailedAccessAttempts = 5;

    // be sure to add this option because without it aspnet core
    // identity will not going to encrypt and decrypt users data

    options.Stores.ProtectPersonalData = true; 

}).AddEntityFrameworkStores<BaseDBContext>()
.AddDefaultTokenProviders()
.AddPersonalDataProtection<CustomLookupProtector, CustomLookupProtectorKeyRing>();
```
Now let us run our sample and create a new account.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1szlh3ngh66mdj54enha.png)

And if you see the below pic you will catch that there five columns are encrypted

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lc6z27dvku6fhuab6v0g.png)

Then let me try to login and see if the ASP.NET Core Identity can decrypt my data without any issue or not! ( belive me ASP.NET Core Identity can decrypt the data, unless you change the **rgbkey** that you used in the **CreateEncryptor** method which is defined in the **SymmetricAlgorithm** object 😉).


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lvcuyjkt8euzdom2cl9j.png)

And Here is the Main Page:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wdy3y1r5bzl406c9l7xd.png)

So, now we see how we can encrypt the users' data by using interfaces that are provided by ASP.NET Core Identity, but at any time you should encrypt your users' data?! this is a big question; and to answer this question you have to know that ASP.NET Core Identity can't work in two ways, by another words ASP.NET Core Identity works with plain text data for your users or Encrypted data, but you can't combine between them, and in my opinion this is a problem or let me say from my site this is a problem, imagine that, you have a third party provider let us say Active Directory, and you want to encrypt any data for all users coming from this provider, but in the same time you won't do that for the users using your local provider.
Thanks for reading.