This document provides an overview of the key classes in the system, focusing on those that play a fundamental role in the application's functionality. Minor implementations are excluded to maintain clarity.
The Windows version can be downloaded from GitHub, while the Android version is available on the Play Store.
The MainPage.xaml.cs file is part of a .NET MAUI application for managing passwords. Here's a breakdown of how the code works:
- MainPage: Inherits from
ContentPageand represents the main page of the application. - Properties:
_storageFile: Stores the path to the file where passwords are saved.Passwords: An observable collection ofPasswordModelobjects, used for data binding._allPasswords: A private collection of all passwords, used for filtering._currentSearchText: Stores the current search text for filtering passwords.
- MainPage(): Initializes the component and sets the
BindingContextto the current instance.
- OnAppearing(): Called when the page appears. It initializes encryption, checks user authentication, and loads passwords from storage.
- IsUserAuthenticatedAsync(): Checks if the user is authenticated by retrieving the current user from secure storage.
- OnLogoutClicked(): Logs out the user by removing the current user from secure storage and navigates to the login page.
- OnAboutClicked(): Navigates to the About page.
- OnAddPasswordClicked(): Navigates to the AddPasswordPage and updates the password list when returning.
- OnPasswordSelected(): Displays details of the selected password.
- OnDeletePasswordClicked(): Deletes the selected password after confirmation.
- OnCopyPasswordClicked(): Copies the selected password to the clipboard.
- OnSaveBackupClicked(): Saves an encrypted backup of passwords to a file.
- OnLoadBackupClicked(): Loads passwords from an encrypted backup file.
- OnEditPasswordClicked(): Navigates to the EditPasswordPage for the selected password.
- OnButtonPressed() and OnButtonReleased(): Handle button press and release animations.
- LoadPasswordsAsync(): Loads passwords from the storage file, decrypts them, and populates the collections.
- SavePasswordsAsync(): Encrypts and saves the passwords to the storage file.
- OnSearchTextChanged(): Filters the passwords based on the search text.
- ShareFile(): Shares the backup file using the device's sharing capabilities.
- OnBackButtonPressed(): Handles the back button press to confirm logout and navigate to the login page.
The MainPage.xaml.cs file manages the main functionality of the password manager, including loading, saving, searching, and backing up passwords. It also handles user authentication and navigation within the application.
The SecureStorageHelper class in SecureStorageHelper.cs provides utility methods for saving and retrieving passwords securely using encryption. Here's a detailed explanation of how it works:
- SavePasswordAsync(PasswordModel password)
- This method takes a
PasswordModelobject as a parameter. - It encrypts the password using the
EncryptionHelper.Encryptmethod. - It then stores the encrypted password in secure storage using
SecureStorage.Default.SetAsync, with a key composed of the service and username.
- This method takes a
public static async Task SavePasswordAsync(PasswordModel password)
{
string encryptedPassword = EncryptionHelper.Encrypt(password.Password);
await SecureStorage.Default.SetAsync($"{password.Service}_{password.Username}", encryptedPassword);
}
- GetPasswordAsync(string service, string username)
- This method takes the service and username as parameters.
- It retrieves the encrypted password from secure storage using
SecureStorage.Default.GetAsync. - It then decrypts the password using the
EncryptionHelper.Decryptmethod and returns the decrypted password.
public static async Task<string> GetPasswordAsync(string service, string username)
{
string encryptedPassword = await SecureStorage.Default.GetAsync($"{service}_{username}");
return EncryptionHelper.Decrypt(encryptedPassword);
}
-
Saving a Password:
- Create a
PasswordModelobject with the service, username, and password. - Call
SavePasswordAsyncwith thePasswordModelobject to save the encrypted password in secure storage.
- Create a
-
Retrieving a Password:
- Call
GetPasswordAsyncwith the service and username to retrieve and decrypt the password from secure storage.
- Call
var passwordModel = new PasswordModel
{
Service = "exampleService",
Username = "exampleUser",
Password = "examplePassword"
};
// Save the password
await SecureStorageHelper.SavePasswordAsync(passwordModel);
// Retrieve the password
string password = await SecureStorageHelper.GetPasswordAsync("exampleService", "exampleUser");- The
SecureStorageHelperclass provides a simple interface for saving and retrieving passwords securely. - It uses encryption to protect the passwords before storing them in secure storage.
- The
EncryptionHelperclass handles the encryption and decryption of passwords. - The
SecureStorageclass provides a secure way to store and retrieve data.
The AddPasswordPage.xaml.cs file defines the behavior of the AddPasswordPage in a .NET MAUI application. This page allows users to add new passwords to their password manager. Here's a breakdown of how the code works:
public partial class AddPasswordPage : ContentPage
{
private readonly MainPage _mainPage;
public AddPasswordPage(MainPage mainPage)
{
InitializeComponent();
_mainPage = mainPage;
BindingContext = this;
}- The
AddPasswordPageclass inherits fromContentPage, which is a base class for pages in .NET MAUI. - The constructor takes a
MainPageobject as a parameter and initializes the_mainPagefield with it. This allowsAddPasswordPageto interact with theMainPage. InitializeComponent()initializes the components defined in the corresponding XAML file.BindingContext = thissets the binding context of the page to itself, enabling data binding.
private async void OnSaveClicked(object sender, EventArgs e)
{
var newPassword = new PasswordModel
{
Service = ServiceEntry.Text,
Username = UsernameEntry.Text,
Password = PasswordEntry.Text
};
if (_mainPage != null && _mainPage.Passwords != null)
{
_mainPage.Passwords.Add(newPassword);
_mainPage.SavePasswordsAsync();
}
await Navigation.PopAsync();
}- This method is triggered when the save button is clicked.
- It creates a new
PasswordModelobject using the text entered in theServiceEntry,UsernameEntry, andPasswordEntryfields. - If
_mainPageand itsPasswordscollection are not null, the new password is added to the collection, andSavePasswordsAsyncis called to save the passwords. - Finally, the page is popped from the navigation stack, returning to the previous page.
private void OnBackClicked(object sender, EventArgs e)
{
Navigation.PopAsync();
}- This method is triggered when the back button is clicked.
- It pops the current page from the navigation stack, returning to the previous page.
private async void OnButtonPressed(object sender, EventArgs e)
{
if (sender is Button button)
{
await button.ScaleTo(0.95, 100);
}
}
private async void OnButtonReleased(object sender, EventArgs e)
{
if (sender is Button button)
{
await button.ScaleTo(1, 100);
}
}- These methods handle the visual feedback for button presses.
OnButtonPressedscales the button down slightly when pressed.OnButtonReleasedscales the button back to its original size when released.
- The
AddPasswordPageallows users to add new passwords. - It interacts with the
MainPageto add the new password to the collection and save it. - It provides navigation and visual feedback for button interactions.
The EncryptionHelper class in EncryptionHelper.cs provides utility methods for encrypting and decrypting data, specifically for handling passwords in a secure manner. Here's a breakdown of how it works:
public static async Task InitializeAsync()
{
// Verify if the key and IV exists in SecureStorage
if (await SecureStorage.Default.GetAsync("encryption_key") == null ||
await SecureStorage.Default.GetAsync("encryption_iv") == null)
{
// Generate key and IV
using (var aes = Aes.Create())
{
await SecureStorage.Default.SetAsync("encryption_key", Convert.ToBase64String(aes.Key));
await SecureStorage.Default.SetAsync("encryption_iv", Convert.ToBase64String(aes.IV));
}
}
// Recover key and IV from SecureStorage
Key = Convert.FromBase64String(await SecureStorage.Default.GetAsync("encryption_key"));
IV = Convert.FromBase64String(await SecureStorage.Default.GetAsync("encryption_iv"));
}- Purpose: Initializes the encryption key and initialization vector (IV) by either generating new ones or retrieving existing ones from secure storage.
- Steps:
- Checks if the key and IV are already stored in
SecureStorage. - If not, generates a new key and IV using the
Aesclass and stores them inSecureStorage. - Retrieves the key and IV from
SecureStorageand converts them from Base64 strings to byte arrays.
- Checks if the key and IV are already stored in
public static string Encrypt(string plainText)
{
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
aesAlg.GenerateIV(); // Generate a new IV for each operation
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
msEncrypt.Write(aesAlg.IV, 0, aesAlg.IV.Length);
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
}
return Convert.ToBase64String(msEncrypt.ToArray());
}
}
}
}- Purpose: Encrypts a plain text string.
- Steps:
- Creates an
Aesinstance and sets its key. - Generates a new IV for the encryption operation.
- Creates an encryptor using the key and IV.
- Writes the IV to the memory stream.
- Encrypts the plain text and writes it to the memory stream.
- Converts the encrypted data to a Base64 string and returns it.
- Creates an
public static string Decrypt(string cipherText)
{
byte[] cipherBytes = Convert.FromBase64String(cipherText);
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
// Read the IV from the beginning
byte[] iv = new byte[aesAlg.IV.Length];
Array.Copy(cipherBytes, iv, iv.Length);
aesAlg.IV = iv;
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msDecrypt = new MemoryStream(cipherBytes, iv.Length, cipherBytes.Length - iv.Length))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
return srDecrypt.ReadToEnd();
}
}
}
}
}- Purpose: Decrypts a Base64 encoded cipher text string.
- Steps:
- Converts the Base64 encoded cipher text to a byte array.
- Creates an
Aesinstance and sets its key. - Extracts the IV from the beginning of the cipher text byte array.
- Creates a decryptor using the key and IV.
- Decrypts the cipher text and reads the plain text from the memory stream.
- Returns the decrypted plain text.
public static string EncryptBackup(IEnumerable<PasswordModel> passwords)
{
var backupPasswords = passwords.Select(p => new PasswordModel
{
Service = p.Service,
Username = p.Username,
Password = Encrypt(p.Password) // Encrypt each password
}).ToList();
return JsonSerializer.Serialize(backupPasswords);
}
public static List<PasswordModel> DecryptBackup(string encryptedJson)
{
var backupPasswords = JsonSerializer.Deserialize<List<PasswordModel>>(encryptedJson);
return backupPasswords.Select(p => new PasswordModel
{
Service = p.Service,
Username = p.Username,
Password = Decrypt(p.Password) // Decrypt each password
}).ToList();
}-
EncryptBackup:
- Purpose: Encrypts a list of
PasswordModelobjects and serializes them to a JSON string. - Steps:
- Encrypts each password in the list.
- Serializes the list of encrypted
PasswordModelobjects to a JSON string. - Returns the JSON string.
- Purpose: Encrypts a list of
-
DecryptBackup:
- Purpose: Deserializes a JSON string to a list of
PasswordModelobjects and decrypts their passwords. - Steps:
- Deserializes the JSON string to a list of
PasswordModelobjects. - Decrypts each password in the list.
- Returns the list of decrypted
PasswordModelobjects.
- Deserializes the JSON string to a list of
- Purpose: Deserializes a JSON string to a list of
The EncryptionHelper class provides methods to securely encrypt and decrypt individual strings and lists of PasswordModel objects, using AES encryption with keys and IVs stored in secure storage. This ensures that sensitive data, such as passwords, are protected both in storage and during transmission.
The LoginPage.xaml.cs file defines the behavior of the LoginPage in a .NET MAUI application. Here's a breakdown of how the code works:
public partial class LoginPage : ContentPage
{
public LoginPage()
{
InitializeComponent();
}- The
LoginPageclass inherits fromContentPage, which is a base class for pages in .NET MAUI. - The constructor initializes the page by calling
InitializeComponent(), which sets up the UI components defined in the corresponding XAML file.
protected override void OnAppearing()
{
base.OnAppearing();
var buttons = this.GetVisualTreeDescendants().OfType<Button>();
foreach (var button in buttons)
{
button.Scale = 1;
}
}- This method is called when the page appears.
- It resets the scale of all buttons on the page to 1, ensuring they are at their default size.
private async void OnLoginClicked(object sender, EventArgs e)
{
string username = UsernameEntry.Text;
string password = PasswordEntry.Text;
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
{
await DisplayAlert("Error", "Please enter both username and password.", "OK");
return;
}
bool isAuthenticated = await AuthenticateUser(username, password);
if (isAuthenticated)
{
await SecureStorage.Default.SetAsync("current_user", username);
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await Navigation.PushAsync(new MainPage());
});
}
else
{
await DisplayAlert("Error", "Invalid username or password.", "OK");
}
}- This method handles the login button click event.
- It retrieves the username and password from the UI.
- If either field is empty, it displays an error message.
- It calls
AuthenticateUserto check the credentials. - If authentication is successful, it stores the username in secure storage and navigates to the
MainPage. - If authentication fails, it displays an error message.
private async void OnRegisterClicked(object sender, EventArgs e)
{
if (sender is Button button)
{
button.Scale = 1;
}
await Navigation.PushAsync(new RegisterPage());
}- This method handles the register button click event.
- It resets the button scale and navigates to the
RegisterPage.
private async Task<bool> AuthenticateUser(string username, string password)
{
string storedPassword = await SecureStorage.Default.GetAsync(username);
return storedPassword == password;
}- This method checks if the provided username and password match the stored credentials.
- It retrieves the stored password from secure storage and compares it with the provided password.
private async void OnButtonPressed(object sender, EventArgs e)
{
if (sender is Button button)
{
await button.ScaleTo(0.95, 100);
}
}
private async void OnButtonReleased(object sender, EventArgs e)
{
if (sender is Button button)
{
await button.ScaleTo(1, 100);
}
}- These methods handle the button press and release events.
- They animate the button scale to provide visual feedback when the button is pressed and released.
Overall, this code manages user interactions on the login page, including login and registration, and provides visual feedback for button presses.
The PasswordModel class in PasswordModel.cs is a simple model class used to represent a password entry for a service. Here's a breakdown of its components:
- Service: A string property to store the name of the service (e.g., "Gmail").
- Username: A string property to store the username associated with the service.
- Password: A string property to store the password for the service.
- CensoredPassword: A read-only property that returns a censored version of the password. If the
Passwordproperty is empty or null, it returns an empty string; otherwise, it returns "*****".
- Equals: An overridden method to compare two
PasswordModelobjects. It checks if theService,Username, andPasswordproperties are equal. - GetHashCode: An overridden method to generate a hash code for the
PasswordModelobject. It combines the hash codes of theService,Username, andPasswordproperties.
This class can be used to create instances representing different password entries and can be compared for equality or used in collections that require hash codes (e.g., dictionaries or hash sets).
var password1 = new PasswordModel
{
Service = "Gmail",
Username = "user1",
Password = "password123"
};
var password2 = new PasswordModel
{
Service = "Gmail",
Username = "user1",
Password = "password123"
};
bool areEqual = password1.Equals(password2); // True
string censored = password1.CensoredPassword; // "*****"This class is straightforward and provides basic functionality for handling password entries in a .NET MAUI application.
The MainActivity.cs file defines the main activity for the Android platform in a .NET MAUI project. Here's a breakdown of how the code works:
using Android.App;
using Android.Content.PM;
using Android.OS;
using Android.Views;These using directives import necessary namespaces for Android application development.
namespace PasswordManager
{
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
#if ANDROID
// Avoid screenshots
Window.SetFlags(WindowManagerFlags.Secure, WindowManagerFlags.Secure);
#endif
}
}
}The Activity attribute above the MainActivity class provides metadata about the activity:
Theme = "@style/Maui.SplashTheme": Sets the theme for the activity.MainLauncher = true: Indicates that this activity is the main entry point of the application.LaunchMode = LaunchMode.SingleTop: Ensures that if an instance of the activity already exists at the top of the stack, a new instance will not be created.ConfigurationChanges: Specifies the configuration changes that the activity will handle itself, preventing it from being restarted.
The MainActivity class inherits from MauiAppCompatActivity, which is a base class for activities in .NET MAUI.
The OnCreate method is overridden to perform initialization when the activity is created:
base.OnCreate(savedInstanceState): Calls the base class'sOnCreatemethod to ensure proper initialization.- The
#if ANDROIDdirective ensures that the enclosed code is only compiled for the Android platform. Window.SetFlags(WindowManagerFlags.Secure, WindowManagerFlags.Secure): Sets a flag to prevent the activity's window from being captured in screenshots or viewed on non-secure displays.
This setup ensures that the main activity is properly configured and secure for the Android platform in a .NET MAUI application.