Encrypt and Decrypt users' data in ASP.NET Core Identity using the ILookupProtector and ILookupProtectorKeyRing interfaces

Mohammed Ahmed Hussien - Apr 1 '23 - - Dev Community

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)



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


Enter fullscreen mode Exit fullscreen mode

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):



string? Protect(string keyId, string? data);
string? Unprotect(string keyId, string? data);


Enter fullscreen mode Exit fullscreen mode

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:



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;
    }
}


Enter fullscreen mode Exit fullscreen mode

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:



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;
    }
}


Enter fullscreen mode Exit fullscreen mode

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:



.AddPersonalDataProtection<CustomLookupProtector, CustomLookupProtectorKeyRing>();


Enter fullscreen mode Exit fullscreen mode

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)



options.Stores.ProtectPersonalData = true; 


Enter fullscreen mode Exit fullscreen mode

Here is the complete configuration:



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>();


Enter fullscreen mode Exit fullscreen mode

Now let us run our sample and create a new account.

Image description

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

Image description

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

And Here is the Main Page:

Image description

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.

. . . . . . . .
Terabox Video Player