Title: Make a password manager in C#
Before I discuss this example, let me make a disclaimer and say that I can't guarantee this application's performance. I think it is secure but for something as important as passwords you need to merely use this program as an example and use whatever techniques you think are best to implement your own password tracking application.
|
This is a complicated example so I'm only going to describe the key points here. Sorry, I know I'm skimming over a lot of details. Hopefully you can get the gist of how things work from this and then download the complete example to see the details.
My post Protect a program from SQL injection attacks in C# mentions a 2014 case where Russian hackers stole more than 1.6 billion usernames and passwords from more than 500 million email addresses and 420,000 websites! And things have only gotten worse.
If you use the same password for more than one web site and any one of them is hacked, you run the risk that someone will be able to use your password to break into your accounts on other web sites. (This kind of attack is called "credential stuffing.")
For example, if someone manages to steal your username and password from some innocuous recipe web site, they may be able to use that information to break into one of your bank or credit card sites! Even if your important financial web sites have great security, the recipe site could let hackers in.
For that reason, you should always use a different password for every account and web site that you use.
Unfortunately, every web site out there (no matter how trivial) seems to think you need an account and password to view their cupcake recipes or whatever. Remembering different passwords for all of those zillions of web sites is a real problem. You can write them down on a piece of paper, but then you can lose the paper or someone locally can get hold of it. You can store them in a file, but a hacker who gets into your computer could grab the file.
One solution is to build a password manager. One simple method would be to put your passwords in a file and encrypt it. (I think I'll show how to do that soon.) This example uses an approach that's more complicated but that provides more features.
When the program starts, it asks you for a master password. The first time you run the program, it encrypts the password you enter and stores it for the next time you run the program. When the program runs later, it takes the password you enter at that time, encrypts it, and sees if it matches the encrypted password it has stored. See the post Use the .NET cryptography library to make extension methods that encrypt and decrypt strings in C# for information about encrypting and decrypting strings.
The master password is very important because it secures all of your other passwords. Spend some extra time picking a master password that you can remember and that is as secure as possible.
Next the program gets encrypted values from the Registry. The values include names (such as a web site name as in "Diner's Club"), user names (the user name for that web site), passwords (your password on the site), modification dates (the date you last modified the password). The program uses the master password to decrypt the values and displays the results in the main form's DataGridView control.
(I went back and forth on whether the information should be stored in the Registry, a file, a config file, a database, or somewhere else. In the end I decided that a file might be too vulnerable. A hacker who got hold of the file could attack it at leisure. A hacker with access to your computer could still probably find the values in the Registry, but I figure it might slow them down a little.)
The following code shows how the program verifies the master password.
// Get the master password.
if (InputBox("Master Password", "", out MasterPassword)
== DialogResult.Cancel)
{
Close();
return;
}
// See if we have any data yet.
string crypt_master = SettingStuff.GetSetting(
SettingStuff.APP_NAME, "Passwords", "Master", "");
if (crypt_master.Length == 0)
{
// This is the first time.
// Record the master password change date.
MasterDate = DateTime.Now.ToString();
}
else
{
// Verify that the user entered the correct master password.
string salt_master = SettingStuff.GetSetting(
SettingStuff.APP_NAME, "Passwords", "MasterSalt", "");
string plain_master = Crypto.DecryptFromString(crypt_master,
MasterPassword, salt_master);
if (plain_master != MasterPassword)
{
MessageBox.Show("Incorrect master password",
"Incorrect Password", MessageBoxButtons.OK,
MessageBoxIcon.Exclamation);
MasterPassword = "";
Close();
return;
}
string crypt_masterdate = SettingStuff.GetSetting(
SettingStuff.APP_NAME, "Passwords", "MasterDate",
DateTime.Now.ToString());
MasterDate = Crypto.DecryptFromString(crypt_masterdate,
MasterPassword, salt_master);
}
This code uses the InputBox method (not shown) to display a message box where you can enter the master password. It then gets the encrypted master password from the registry.
If the encrypted password isn't present, the program uses the password you entered as the new master password.
If the encrypted password is present, the program gets a salt array of bytes, also from the registry. It then uses the DecryptFromString method to decrypt the stored master password using the entered master password as a password. If the decrypted password matches the one you entered, then the program lets you continue.
The following code shows how the program gets the username, password, and other information saved in the registry.
// Load the password data.
for (int i = 0; ; i++)
{
// Get the next password's name, username, and value.
string crypt_name = SettingStuff.GetSetting(
SettingStuff.APP_NAME, "Passwords",
"PasswordName" + i.ToString(), "");
if (crypt_name.Length == 0) break;
string crypt_uaername = SettingStuff.GetSetting(
SettingStuff.APP_NAME, "Passwords",
"PasswordUsername" + i.ToString(), "");
string crypt_value = SettingStuff.GetSetting(
SettingStuff.APP_NAME, "Passwords",
"PasswordValue" + i.ToString(), "");
string salt = SettingStuff.GetSetting(
SettingStuff.APP_NAME, "Passwords",
"PasswordSalt" + i.ToString(), "");
string crypt_date = SettingStuff.GetSetting(
SettingStuff.APP_NAME, "Passwords",
"PasswordDate" + i.ToString(), "");
// Decrypt the name, username, password, and date.
string plain_name = Crypto.DecryptFromString(
crypt_name, MasterPassword, salt);
string plain_username = Crypto.DecryptFromString(
crypt_uaername, MasterPassword, salt);
string plain_value = Crypto.DecryptFromString(
crypt_value, MasterPassword, salt);
string plain_date = Crypto.DecryptFromString(
crypt_date, MasterPassword, salt);
// Display the result.
dgvPasswords.Rows.Add(new Object[] { plain_name,
plain_username, plain_value, plain_date, null, null});
}
This code loops through values i = 0, 1, 2, and so forth until it can't find any more saved values. For i = 0, it looks for the values PasswordName0, PasswordUsername0, PasswordValue0, and PasswordSalt0. It uses the master password to decrypt those values and displays them in the form's DataGridView.
The program includes several other useful features such as a menu item that lets you change the master password. If you click a cell in the Copy column, the program copies that row's password to the clipboard so you can paste it into a text box to log in to an application.
The program also watches for Ctrl+C and Ctrl+V. When it sees them, the program copies from or pastes into the DataGridView's current cell.
Finally, if you click a cell in the New column, the program displays the dialog on the right. Use the check boxes to indicate what types of characters are allowed and required for a password. For example, you can require that the password contain between 10 and 20 characters including at least one lowercase letter, one uppercase letter, and a digit. When you've made your selections, click Generate to make the program generate a random password for you. If you don't like the result, click Generate again.
Click OK when you're done to copy the generated password into the DataGridView. That also makes the program save the password's last modification time. (That lets you know if a password is old. Some web sites require that you change passwords periodically.)
Because you're using the password tracker, you don't need to remember the passwords, so they can be completely random and obscure. To get the best security, make passwords reasonably long. Many sites let you use 6 character passwords but there's no reason not to use 8, 10, or even more characters if you're allowed.
For information on how the password generator works, see the post Use cryptographic methods to generate random passwords in C#.
Those are all the major concepts used by the program. If you think the program is insecure, please let me know so I can fix it.
Download the example to experiment with it and to see additional details.
|