How to securely save username/password (local)? - c#

I'm making a Windows application, which you need to log into first.
The account details consist of username and password, and they need to be saved locally.
It's just a matter of security, so other people using the same computer can't see everyone's personal data.
What is the best/most secure way to save this data?
I don't want to use a database, so I tried some things with Resource files.
But since I'm kind of new with this, I'm not entirely sure of what I'm doing and where I should be looking for a solution.

If you are just going to verify/validate the entered user name and password, use the Rfc2898DerivedBytes class (also known as Password Based Key Derivation Function 2 or PBKDF2). This is more secure than using encryption like Triple DES or AES because there is no practical way to go from the result of RFC2898DerivedBytes back to the password. You can only go from a password to the result. See Is it ok to use SHA1 hash of password as a salt when deriving encryption key and IV from password string? for an example and discussion for .Net or String encrypt / decrypt with password c# Metro Style for WinRT/Metro.
If you are storing the password for reuse, such as supplying it to a third party, use the Windows Data Protection API (DPAPI). This uses operating system generated and protected keys and the Triple DES encryption algorithm to encrypt and decrypt information. This means your application does not have to worry about generating and protecting the encryption keys, a major concern when using cryptography.
In C#, use the System.Security.Cryptography.ProtectedData class. For example, to encrypt a piece of data, use ProtectedData.Protect():
// Data to protect. Convert a string to a byte[] using Encoding.UTF8.GetBytes().
byte[] plaintext;
// Generate additional entropy (will be used as the Initialization vector)
byte[] entropy = new byte[20];
using(RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
rng.GetBytes(entropy);
}
byte[] ciphertext = ProtectedData.Protect(plaintext, entropy,
DataProtectionScope.CurrentUser);
Store the entropy and ciphertext securely, such as in a file or registry key with permissions set so only the current user can read it. To get access to the original data, use ProtectedData.Unprotect():
byte[] plaintext= ProtectedData.Unprotect(ciphertext, entropy,
DataProtectionScope.CurrentUser);
Note that there are additional security considerations. For example, avoid storing secrets like passwords as a string. Strings are immutable, being they cannot be notified in memory so someone looking at the application's memory or a memory dump may see the password. Use SecureString or a byte[] instead and remember to dispose or zero them as soon as the password is no longer needed.

I have used this before and I think in order to make sure credential persist and in a best secure way is
you can write them to the app config file using the ConfigurationManager class
securing the password using the SecureString class
then encrypting it using tools in the Cryptography namespace.
This link will be of great help I hope : Click here

I wanted to encrypt and decrypt the string as a readable string.
Here is a very simple quick example in C# Visual Studio 2019 WinForms based on the answer from #Pradip.
Right click project > properties > settings > Create a username and password setting.
Now you can leverage those settings you just created. Here I save the username and password but only encrypt the password in it's respectable value field in the user.config file.
Example of the encrypted string in the user.config file.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<userSettings>
<secure_password_store.Properties.Settings>
<setting name="username" serializeAs="String">
<value>admin</value>
</setting>
<setting name="password" serializeAs="String">
<value>AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAQpgaPYIUq064U3o6xXkQOQAAAAACAAAAAAAQZgAAAAEAACAAAABlQQ8OcONYBr9qUhH7NeKF8bZB6uCJa5uKhk97NdH93AAAAAAOgAAAAAIAACAAAAC7yQicDYV5DiNp0fHXVEDZ7IhOXOrsRUbcY0ziYYTlKSAAAACVDQ+ICHWooDDaUywJeUOV9sRg5c8q6/vizdq8WtPVbkAAAADciZskoSw3g6N9EpX/8FOv+FeExZFxsm03i8vYdDHUVmJvX33K03rqiYF2qzpYCaldQnRxFH9wH2ZEHeSRPeiG</value>
</setting>
</secure_password_store.Properties.Settings>
</userSettings>
</configuration>
Full Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace secure_password_store
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Exit_Click(object sender, EventArgs e)
{
Application.Exit();
}
private void Login_Click(object sender, EventArgs e)
{
if (checkBox1.Checked == true)
{
Properties.Settings.Default.username = textBox1.Text;
Properties.Settings.Default.password = EncryptString(ToSecureString(textBox2.Text));
Properties.Settings.Default.Save();
}
else if (checkBox1.Checked == false)
{
Properties.Settings.Default.username = "";
Properties.Settings.Default.password = "";
Properties.Settings.Default.Save();
}
MessageBox.Show("{\"data\": \"some data\"}","Login Message Alert",MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void DecryptString_Click(object sender, EventArgs e)
{
SecureString password = DecryptString(Properties.Settings.Default.password);
string readable = ToInsecureString(password);
textBox4.AppendText(readable + Environment.NewLine);
}
private void Form_Load(object sender, EventArgs e)
{
//textBox1.Text = "UserName";
//textBox2.Text = "Password";
if (Properties.Settings.Default.username != string.Empty)
{
textBox1.Text = Properties.Settings.Default.username;
checkBox1.Checked = true;
SecureString password = DecryptString(Properties.Settings.Default.password);
string readable = ToInsecureString(password);
textBox2.Text = readable;
}
groupBox1.Select();
}
static byte[] entropy = Encoding.Unicode.GetBytes("SaLtY bOy 6970 ePiC");
public static string EncryptString(SecureString input)
{
byte[] encryptedData = ProtectedData.Protect(Encoding.Unicode.GetBytes(ToInsecureString(input)),entropy,DataProtectionScope.CurrentUser);
return Convert.ToBase64String(encryptedData);
}
public static SecureString DecryptString(string encryptedData)
{
try
{
byte[] decryptedData = ProtectedData.Unprotect(Convert.FromBase64String(encryptedData),entropy,DataProtectionScope.CurrentUser);
return ToSecureString(Encoding.Unicode.GetString(decryptedData));
}
catch
{
return new SecureString();
}
}
public static SecureString ToSecureString(string input)
{
SecureString secure = new SecureString();
foreach (char c in input)
{
secure.AppendChar(c);
}
secure.MakeReadOnly();
return secure;
}
public static string ToInsecureString(SecureString input)
{
string returnValue = string.Empty;
IntPtr ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(input);
try
{
returnValue = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr);
}
finally
{
System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr);
}
return returnValue;
}
private void EncryptString_Click(object sender, EventArgs e)
{
Properties.Settings.Default.password = EncryptString(ToSecureString(textBox2.Text));
textBox3.AppendText(Properties.Settings.Default.password.ToString() + Environment.NewLine);
}
}
}

DPAPI is just for this purpose. Use DPAPI to encrypt the password the first time the user enters is, store it in a secure location (User's registry, User's application data directory, are some choices). Whenever the app is launched, check the location to see if your key exists, if it does use DPAPI to decrypt it and allow access, otherwise deny it.

This only works on Windows, so if you are planning to use dotnet core cross-platform, you'll have to look elsewhere. See https://github.com/dotnet/corefx/blob/master/Documentation/architecture/cross-platform-cryptography.md

For simple scenarios can also use Windows Credential Management API using C# wrapper CredentialManagement. It gives single place to store/retrieve passwords, easy to change.
https://stackoverflow.com/a/32550674/1129978

Related

C# Store salt used for the register to use on login

I am making some kind of thinking problem.
I created a Register/Login system for my Xamarin.Forms app. Now when I register an account, I hash the password and add random salt, but I do the same for the login. The problem is that I need to get the exact same salt for the password that I used on the register.
Here are the 2 functions to create salt and hasing:
public String CreateSalt(int size)
{
var rng = new System.Security.Cryptography.RNGCryptoServiceProvider();
var buff = new byte[size];
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}
public String GenerateSHA256Hash(String input, String salt)
{
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(input + salt);
System.Security.Cryptography.SHA256Managed sha256hashstring =
new System.Security.Cryptography.SHA256Managed();
byte[] hash = sha256hashstring.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
And here is the code I use when registering and logging in:
async void btnLogInClicked(object sender, EventArgs args)
{
string username = txtUsername.Text;
string password = txtPassword.Text;
string salt = CreateSalt(16);
string HashedPassword = GenerateSHA256Hash(password, salt);
HashedPassword = HashedPassword.Replace('+', '-');
HashedPassword = HashedPassword.Replace('/', '_');
User user = new User
{
Username = username,
Password = HashedPassword
};
var result = await App.RestService.Login(user);
if (result != null)
{
App.Current.MainPage = new SideMenuItems();
}
}
How can I do that and still use a random salt for every new generated password?
It seems like every year I have to re-learn how to properly hash passwords. I just did that, and I'll do my best to explain here.
Your problem is that you are generating a new salt every time you call CreateSalt, when user logs in. As mentioned in the comments, you'll want generate the salt and hash once during registration, and save them to a database.
Then, when a user logs in, you can retrieve the salt from the database, and use it along with the password entered by the user to generate the hash. Finally, you'll compare the newly generated hash with the hash saved in the database, to see if the password is correct.
I'd suggest using a slow hash algorithm and not SHA256 for generating the hash. PBKDF2 would be a good option, and comes built-in with .NET as the Rfc2898DeriveBytes class. Another popular option is bcrypt. It necessitates a third-party library in .NET, but I find it to be easy to implement (at least with Node). More general info on password hashing algorithms here.
One last thing to consider is the number of iterations the algorithm uses. The Rfc2898DeriveBytes class has a few constructors, one of which sets a default of 1000 iterations, which should be sufficient. The iterations (or "cost") slows the hash function and helps thwart attacks.

Verify SHA512 Hashed Password in C#

I have designed a signup page in C# and all users have to enter their password then the program will hash the password before saving it on a database with a SHA512 hashing method.
Now, I want to verify entered password on the login page with the saved password on database.
Below code is the method that I used to hash the passwords.
Now how can I verify entered password on login page???
byte[] infos = System.Text.Encoding.ASCII.GetBytes(txtPassword.Text);
infos = new System.Security.Cryptography.SHA512Managed().ComputeHash(infos);
String hash = System.Text.Encoding.ASCII.GetString(infos);
The Sha* hash family is not appropriate to store passwords safely, because they are way too fast and can be brute-forced too easily. You should switch to a dedicated password-hash function like BCrypt, Argon2 or PBKDF2, which apply a salt and use key-stretching.
A good BCrypt library is available via Nuget: https://www.nuget.org/packages/BCrypt.Net-Next/
Its usage is very straight foreward:
// Hash a new password for storing in the database.
// The function automatically generates a cryptographically safe salt.
string hashToStoreInDb = BCrypt.HashPassword(password);
// Check if the hash of the entered login password, matches the stored hash.
// The salt and the cost factor will be extracted from existingHashFromDb.
bool isPasswordCorrect = BCrypt.Verify(password, existingHashFromDb);
What about writing code like this:
using System;
using System.Text;
using System.Security.Cryptography;
using CodeShare.Cryptography;
namespace CodeShare.Cryptography
{
public static class SHA
{
public static string GenerateSHA512String(string inputString)
{
SHA512 sha512 = SHA512Managed.Create();
byte[] bytes = Encoding.UTF8.GetBytes(inputString);
byte[] hash = sha512.ComputeHash(bytes);
return GetStringFromHash(hash);
}
private static string GetStringFromHash(byte[] hash)
{
StringBuilder result = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
result.Append(hash[i].ToString("X2"));
}
return result.ToString();
}
}
}
Example:
public void UsageExample()
{
Console.WriteLine(SHA.GenerateSHA512String("abc"));
//returns DDAF35A193617ABACC417349AE20413112E6FA4E89A97EA20A9EEEE64B55D39A2192992A274FC1A836BA3C23A3FEEBBD454D4423643CE80E2A9AC94FA54CA49F
}

Why is SecureString decryption giving different results between executables?

In proxy.exe I am creating a secure string the following way:
public SecureString GetSecureEncryptionKey()
{
string strPassword = "8charPwd";
SecureString secureStr = new SecureString();
if (strPassword.Length > 0)
{
foreach (var c in strPassword.ToCharArray()) secureStr.AppendChar(c);
}
return secureStr;
}
Then in main.exe I am decrypting it using this function:
public string convertToUNSecureString(SecureString secstrPassword)
{
IntPtr unmanagedString = IntPtr.Zero;
try
{
unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(secstrPassword);
return Marshal.PtrToStringUni(unmanagedString);
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
}
}
The issue is that the returned string is empty, unless I encrypt the initial string within main.exe, then the returned decrypted string is indeed "8charPwd". Why is this happening? Is SecureString encryption bound to the executable?
The purpose of SecureString is to keep strings safety inside your application memory(keep the string secure in RAM)
SecureString object is not a serialize-able.
You cannot transfer an instance between applications.
SecureString encrypt the string by using RtlEncryptMemory (WINAPI) with the flag:"0" (only the same process can decrypt the content). RtlEncryptMemory API
if you don't want to expose the password(at any time) in the RAM, you can create a simple obfuscation(or encryption) logic, and then transfer the content.
Edit:
I found 2 old questions that might be helpful for you:
When would I need a SecureString in .NET?
Wcf-Authentication and Logging

Sending a Password via TCP in C#

I'm and out of practice programmer, so basically new to it again.
What I am doing is logging onto a a device over Telnet or TCP. Instead of controlling the device by type command I am using a custom forms application to send the type string commands by pre programmed push button. The device is an old Codec. The purpose of my software is to create a push button controller to be used from a PC.
The problem I am having is that some of my devices are password protected and some are not (different firmware). This cannot be changed. The Password protection is what has me stuck.
I am sending data to the device using ASCII
public void Write(string cmd)
{
if (!tcpSocket.Connected) return;
byte[] buf = System.Text.ASCIIEncoding.ASCII.GetBytes(cmd.Replace("\0xFF", "\0xFF\0xFF"));
tcpSocket.GetStream().Write(buf, 0, buf.Length);
I have been searching on MD5 and have become stuck.
I have tried sending the password by plain text typing the password into a text box and initiating the write command. I have also tried sending the output of this code I found on the internet
public string EncodePassword(string originalPassword)
{
//Declarations
Byte[] originalBytes;
Byte[] encodedBytes;
MD5 md5;
//Instantiate MD5CryptoServiceProvider, get bytes for original password and compute hash (encoded password)
md5 = new MD5CryptoServiceProvider();
originalBytes = ASCIIEncoding.Default.GetBytes(originalPassword);
encodedBytes = md5.ComputeHash(originalBytes);
//Convert encoded bytes back to a 'readable' string
return BitConverter.ToString(encodedBytes);
I even found another MD5 line that forced upper and lower case. I don't know if it wont work because it is still sending the encoded password in ASCII or what.
I do know that my password is right because I can load telnet in windows and log on fine there. Any help in getting this client to authenticate with the server would be most appreciated.
Forgive the length. Since I am unable to reply I had to edit. I think that I was confused on the MD5... After reading the replies I think my problem is the ASCII. I need plain text.
Ok, so this is where my beginner stripes shine brightly. This is my first attempt at programming that involves a network of any sort (if it wasn't already that obvious). From reading the replies I think my first problem is the ASCII. I assumed that being sent though that was plain text. Given that when I connect to a server with the same client that does not require password login... The ASCII works just fine.
So if I am to use plain text, then How would I go about sending in plain text and not a byte conversion? Assuming that my assumption that ASCII was the way to send plain text is wrong...Which I now think that it is.
I have added more code to help this along.
When using the Windows telnet client, the device prompts for password and when you type it into telnet no text is shown until after login. After login all typing is shown immediately.
The Class used for the socket is mostly a code I found on google with some small tweeks.
using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
namespace STC_Control
{
enum Verbs
{
WILL = 251,
WONT = 252,
DO = 253,
DONT = 254,
IAC = 255
}
enum Options
{
SGA = 3
}
class TelnetConnection
{
TcpClient tcpSocket;
int TimeOutMs = 100;
public TelnetConnection(string Hostname, int Port)
{
tcpSocket = new TcpClient(Hostname, Port);
}
public void WriteLine(string cmd)
{
Write(cmd + "\n");
}
public void Write(string cmd)
{
if (!tcpSocket.Connected) return;
byte[] buf = System.Text.ASCIIEncoding.ASCII.GetBytes(cmd.Replace("\0xFF", "\0xFF\0xFF"));
tcpSocket.GetStream().Write(buf, 0, buf.Length);
}
public string Read()
{
if (!tcpSocket.Connected) return null;
StringBuilder sb = new StringBuilder();
do
{
ParseTelnet(sb);
System.Threading.Thread.Sleep(TimeOutMs);
} while (tcpSocket.Available > 0);
return sb.ToString();
}
public bool IsConnected
{
get { return tcpSocket.Connected; }
}
void ParseTelnet(StringBuilder sb)
{
while (tcpSocket.Available > 0)
{
int input = tcpSocket.GetStream().ReadByte();
switch (input)
{
case -1:
break;
case (int)Verbs.IAC:
// interpret as command
int inputverb = tcpSocket.GetStream().ReadByte();
if (inputverb == -1) break;
switch (inputverb)
{
case (int)Verbs.IAC:
//literal IAC = 255 escaped, so append char 255 to string
sb.Append(inputverb);
break;
case (int)Verbs.DO:
case (int)Verbs.DONT:
case (int)Verbs.WILL:
case (int)Verbs.WONT:
// reply to all commands with "WONT", unless it is SGA (suppres go ahead)
int inputoption = tcpSocket.GetStream().ReadByte();
if (inputoption == -1) break;
tcpSocket.GetStream().WriteByte((byte)Verbs.IAC);
if (inputoption == (int)Options.SGA)
tcpSocket.GetStream().WriteByte(inputverb == (int)Verbs.DO ? (byte)Verbs.WILL : (byte)Verbs.DO);
else
tcpSocket.GetStream().WriteByte(inputverb == (int)Verbs.DO ? (byte)Verbs.WONT : (byte)Verbs.DONT);
tcpSocket.GetStream().WriteByte((byte)inputoption);
break;
default:
break;
}
break;
default:
sb.Append((char)input);
break;
}
}
}
}
}
Then the program
public Form1()
{
InitializeComponent();
}
//declorations
TelnetConnection tc;
Int16 vl = 13;
private void connect_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(roomBox.Text))
{
MessageBox.Show("Please enter a selection before continuing");
}
else
{
{
try
{
//Connects to the server
tc = new TelnetConnection(roomBox.Text, 23);
//Enables controls
panelAll.Enabled = true;
}
catch
{
MessageBox.Show("Server Unreachable. ");
panelAll.Enabled = false;
cState.Text = "Disconnected";
}
}
}
// Button to send login password Temp created to test login
public void p_Click(object sender, EventArgs e)
{
try
{
//sends text to server
tc.WriteLine("PASSWORD");
//enables Buttons
panelAll.Enabled = true;
//displays return to textbox to verify login or disconnect
rx.Text = (tc.Read());
}
catch
{
panelAll.Enabled = false;
MessageBox.Show("Communication with device was lost.");
cState.Text = "Disconnected";
}
}
------------------------------------------------------------------------------------------
Your code seems a bit incomplete to give a full answer. If possible, supply a more complete flow of logic stripped out of your program. But from what I can gather, here's some immediate tips...
Don't confuse MD5 with encryption. They're very, very different things (MD5 is a one-way hash while encryption is a two-way encoding). This is likely to be the source of your headaches. Encryption/hashing handshake methods must be agreed upon by both participants. If you're trying to login with an MD5 hash of a password, to a device that only accepts plain-text passwords, then it will not recognize it as a valid password. There is literally nothing you could do about this scenario... plain-text it would have to be.
You assume passwords are ASCII? If not, the ASCII conversion is bad news. Probably not a root problem here, but something to think about.
EDIT: Actually... let me go more into depth because after further thought, I'm about 90% sure this is your problem (without more information of course).
The flow of logic when dealing with a device that only supports plain-text passwords is this (where C is client [you] and S is server [them])...
[C] Send password plain-text
[S] Got your password, thanks! I just checked it against what I know and it's good/bad
When using any kind of hashing.
[C] Hash password (MD5, SHA1, etc)
[C] Send password
[S] Receive hash of password. Check against hashed password stored (or worse, hash of plain-text password stored). Respond good/bad
When encrypting the connection (when client and server know what kind of encryption they both use)...
[C] Garble-garble-garble (password encrypted)
[S] Got it... and garble-garble-garble turns into whatever plain-text password. Checked it against local storage and .... garble-garble-garble (good/bad encrypted)
When encrypting the connection (and the client/server may not necessarily support the same methods but know how to negotiate which to use)...
[C] Which encryption methods do you support? Here's my list...
[S] Oh, well, we both support these methods. Which do you want to use?
[C] IDEA sounds good.
[S] Sounds good to me too. Start using it.
[C] Garble-garble-garble (password encrypted)
[S] Got it... and garble-garble-garble turns into whatever plain-text password. Checked it against local storage and .... garble-garble-garble (good/bad encrypted)
As you can see, very different methods. Those must be built into the communication device (server) for a successful negotiation.
I don't think this is a programming problem. I believe it's more of a problem of understanding how your device actually works. Does it accept password as plain text, or does it accept password in some hashed or encrypted form?
The fact that you can provide the password through telnet suggest that it is plain text, unless the telnet protocol has provision for some for of authentication.
It would be good if you could provide a screenshot of the telnet window. We might be able to get some hints from there.
I would recommend that you submit the plain text password followed by the \n new line character.
It sounds to me that your best solution here would be to download the OpenSSL package, install it on the device and your forms app, and then use the APIs to use a secure shell connection instead of telnet to send device commands.
That way the key management is outside your app and you can use prewritten APIs to do your encryption in a tested piece of code.

Howto save a password in the registry

I have a desktop application with a remote interface. The access to the remote interface is secured by a username and password.
What would be the best way to save these password securely, preferably in the registry?
If you do need to store an unhashed password, look at using the ProtectedData class. This makes use of the Data Protection API (DPAPI) which is the best way of securing data on Windows.
Here's a little class that wraps ProtectedData and provides two extension methods on String to Encrypt and Decrypt data:
public static class DataProtectionApiWrapper
{
/// <summary>
/// Specifies the data protection scope of the DPAPI.
/// </summary>
private const DataProtectionScope Scope = DataProtectionScope.CurrentUser;
public static string Encrypt(this string text)
{
if (text == null)
{
throw new ArgumentNullException("text");
}
//encrypt data
var data = Encoding.Unicode.GetBytes(text);
byte[] encrypted = ProtectedData.Protect(data, null, Scope);
//return as base64 string
return Convert.ToBase64String(encrypted);
}
public static string Decrypt(this string cipher)
{
if (cipher == null)
{
throw new ArgumentNullException("cipher");
}
//parse base64 string
byte[] data = Convert.FromBase64String(cipher);
//decrypt data
byte[] decrypted = ProtectedData.Unprotect(data, null, Scope);
return Encoding.Unicode.GetString(decrypted);
}
}
You would need to save the hashed password (be it in the registry or somewhere else). Then when the user enters their password you check the hashed version of what they enter with the hashed version as stored. If these match then the passwords match and you can let the user in.
This way you're not storing the password in plain text for anyone (including yourself) to get at and gain access as someone else.
As to which hash algorithm to use - I don't know. There are plenty to choose from, so I'm reluctant to recommend one blind. I'd suggest you find several and evaluate them. CSharpFriends has an article which looks like it might be a good starting point.

Categories

Resources