.NET Core  

Encrypting User Passwords [GamesCatalog] 20

Previous part: Validations with DataAnnotations and User Creation [GamesCatalog] 19

Step 1. Let's encrypt the password that will be saved in the database. To do this, we will create a class called EncryptionService in the Services project.

 EncryptionService

Code

using System.Security.Cryptography;
using System.Text;

namespace Services.Functions;

public interface IEncryptionService
{
    string Decrypt(string cipherText);
    string Encrypt(string plainText);
}

public class EncryptionService(string Key32, string IV16) : IEncryptionService
{
    private readonly byte[] Key = Encoding.UTF8.GetBytes(Key32);
    private readonly byte[] IV = Encoding.UTF8.GetBytes(IV16);

    public string Encrypt(string plainText)
    {
        string encrypted = "";

        using (Aes aesAlg = Aes.Create())
        {
            aesAlg.Key = Key;
            aesAlg.IV = IV;

            ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

            using MemoryStream msEncrypt = new();
            using (CryptoStream csEncrypt = new(msEncrypt, encryptor, CryptoStreamMode.Write))
            using (StreamWriter swEncrypt = new(csEncrypt))
            {
                swEncrypt.Write(plainText);
            }
            encrypted = Convert.ToBase64String(msEncrypt.ToArray());
        }

        return encrypted;
    }

    public string Decrypt(string cipherText)
    {
        try
        {
            byte[] cipherBytes = Convert.FromBase64String(cipherText);

            using Aes aesAlg = Aes.Create();
            aesAlg.Key = Key;
            aesAlg.IV = IV;

            ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

            using MemoryStream msDecrypt = new(cipherBytes);
            using CryptoStream csDecrypt = new(msDecrypt, decryptor, CryptoStreamMode.Read);
            using StreamReader srDecrypt = new(csDecrypt);
            string plainText = srDecrypt.ReadToEnd();
            return plainText;
        }
        catch (Exception /*ex*/) { throw; }
    }
}

Step 2. Right-click on the EncryptionService function and create a test class.

Solution explorer

Code

using Services.Functions;

namespace ServicesTests.Functions;

[TestClass()]
public class EncryptionServiceTests
{
    public static EncryptionService BuildEncryptionService()
    {
        string key32 = "12345678901234567890123456789012";
        string IV16 = "1234567890123456";

        return new EncryptionService(key32, IV16);
    }

    [TestMethod()]
    public void CompareEncryptionTest()
    {
        string password = "131313";

        var encryptionService = BuildEncryptionService();

        string encryptedString = encryptionService.Encrypt(password);

        string decryptedString = encryptionService.Decrypt(encryptedString);

        Assert.AreEqual(password, decryptedString);
    }

    [TestMethod()]
    public void Compare_Diferent_EncryptionTest()
    {
        string passworda = "123456";

        var encryptionService = BuildEncryptionService();

        string encryptedaString = encryptionService.Encrypt(passworda);

        string passwordb = "654321";

        string encryptedbString = encryptionService.Encrypt(passwordb);

        Assert.AreNotEqual(encryptedaString, encryptedbString);
    }

    [TestMethod()]
    public void DecrypTest()
    {
        string encrypted = "CwY0Vg1K6wNlHSMLMDy2Fw==";
        string decrypted = "131313";
        var encryptionService = BuildEncryptionService();
        string DecryptedString = encryptionService.Decrypt(encrypted);

        Assert.AreEqual(DecryptedString, decrypted);
    }

    [TestMethod()]
    public void EncrypTest()
    {
        string decrypted = "131313";
        string encrypted = "CwY0Vg1K6wNlHSMLMDy2Fw==";
        var encryptionService = BuildEncryptionService();
        string EncryptedString = encryptionService.Encrypt(decrypted);

        Assert.AreEqual(EncryptedString, encrypted);
    }
}    

Step 3. This way, we have an efficient method to confirm that our encryption function works, and we also have a tool to run focused tests on it.

Methd

Step 4. Add your keys to the appsettings.json file.

Appsetting

Step 5. Configure the DI (Dependency Injection) of EncryptionService, passing the two keys.

DI

Step 6. This way, we can use our password encryption function when creating the user.

User

Code for CreateAsync() with password encryption

 public async Task<BaseResp> CreateAsync(ReqUser reqUser)
 {
     string? validateError = reqUser.Validate();
     if (!string.IsNullOrEmpty(validateError)) return new BaseResp(null, validateError);

     UserDTO user = new()
     {
         Name = reqUser.Name,
         Email = reqUser.Email,
         Password = reqUser.Password,
         CreatedAt = DateTime.Now
     };

     string? existingUserMessage = await ValidateExistingUserAsync(user);
     if (existingUserMessage != null) { return new BaseResp(null, existingUserMessage); }

     if (user.Password != null)
         user.Password = encryptionService.Encrypt(user.Password);
     else throw new NullReferenceException(nameof(user.Password));

     await userRepo.CreateAsync(user);

     ResUser? resUser = new()
     {
         Id = user.Id,
         Name = user.Name,
         Email = user.Email,
         CreatedAt = user.CreatedAt
     };

     return new BaseResp(resUser);
 }

Step 7. So when we create a user, their password will be stored encrypted in the database.

Stored encrypted

Output

In the next step, we will generate a token as a way to authenticate the API user.

Code on git: GamesCatalogBackEnd