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.
Related
I am trying to decrypt communication from web sockets secured with SSL certificate. The certificate is installed on the machine and the private key is present. I have this decrypt function
public static string DecryptTry(X509Certificate2 x509, string stringTodecrypt)
{
try
{
if (x509 == null || string.IsNullOrEmpty(stringTodecrypt))
throw new Exception("A x509 certificate and string for decryption must be provided");
if (!x509.HasPrivateKey)
throw new Exception("x509 certificate does not contain a private key for decryption");
if(x509 == null)
{
Console.WriteLine("certificate is null");
return "";
}
Console.WriteLine("decoding text with private key " + x509.HasPrivateKey);//true
using (RSA csp = (RSA)x509.PrivateKey)
{
byte[] bytestoDecrypt = Encoding.UTF8.GetBytes(stringTodecrypt).Take(256).ToArray();
Console.WriteLine("key size: " + x509.PrivateKey.KeySize + " received data length: " + stringTodecrypt.Length + " bytes length: " + bytestoDecrypt.Length);
Console.WriteLine(Encoding.UTF8.GetString(bytestoDecrypt));
byte[] bytesDecrypted = csp.Decrypt(bytestoDecrypt, RSAEncryptionPadding.OaepSHA256); //ERROR HERE
return Encoding.UTF8.GetString(bytesDecrypted);
}
}
catch(Exception e)
{
Console.WriteLine("Error while decrypting text: " + e.Message + e.HResult + e.StackTrace);
return "";
}
}
But the csp.Decrypt is throwing an error
parameter is incorrect
I have tried all padding parameters - none of the seemed to make a difference.
Does anybody know where the problem might be? Am I missing something?
**
EDIT 25.12.2020
**
To add some more background info: The website where the WebSocket client is listening is secured HTTPS, the SSL certificate is signed by CA with my full access to all of its information. The initial problem is handshake for the WebSocket communication which comes encrypted. I was thinking I would be able to decrypt it with the private key and that is where the problem occurs. The length of the incoming request (or handshake) is between 490 and 520 bytes, so that is the reason for .Take(256). I was thinking to split the text into multiple, decode them separately and put together after. That, however, brought me here.
One final thought: This is a .NET console application. Could the problem be possibly fixed by converting it to a format that IIS accepts? The IIS on the machine has the certificate installed... could it possibly make a difference?
Thanks in advance!
I'm not sure what you want to achieve:
Decrypt something using RSA
Decrypt TLS layer of HTTP
Decrypt something in Websocket protocol
I think that you should give some more informations how it works. Can you show example input for function function, means:
DecryptTry(X509Certificate2 x509, string stringTodecrypt) { ... }
How 'strindTodecypt' looks like? What do you put there?
How do you connect to this server to get data to decrypt, via TLS?
Well, most of the questions/answers I've found here are regarding not caching a Smartcard PIN which is the opposite case of what I'm looking for.
We have a console application that signs multiple hashes. For this we use Pkcs.CmsSigner because we need to validate the signed hashes server-side.
Normally a Smartcard's PIN should be cached automatically in the CSP per process and it does in Windows 7, but if we run our code in W10 it does not. Also we support both CNG and non-CNG certificates.
The method we use to sign is the following:
public string SignX509(string data, bool chkSignature, string timestampServer, X509Certificate2 selectedCertificate)
{
CmsSigner oSigner = null;
SignedCms oSignedData = null;
string hashText = String.Empty;
try
{
if (chkSignature)
{
oSigner = new CmsSigner();
oSigner.Certificate = selectedCertificate;
byte[] arrDataHashed = HashSHA1(data);
// hash the text to sign
ContentInfo info = new ContentInfo(arrDataHashed);
// put the hashed data into the signedData object
oSignedData = new SignedCms(info);
if (string.IsNullOrEmpty(timestampServer)) {
oSigner.SignedAttributes.Add(new Pkcs9SigningTime(DateTime.Now));
}
else {
TimeStampToken tsToken = GetTSAToken(arrDataHashed, timestampServer);
AsnEncodedData timeData = new Pkcs9AttributeObject(Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers.IdAASigningCertificate.Id, tsToken.GetEncoded());
oSigner.UnsignedAttributes.Add(timeData);
oSigner.SignedAttributes.Add(new Pkcs9SigningTime(tsToken.TimeStampInfo.GenTime.ToLocalTime()));
}
// sign the data
oSignedData.ComputeSignature(oSigner, false);
hashText = Convert.ToBase64String(oSignedData.Encode());
}
else
{
// just clean the hidden hash text
hashText = String.Empty;
}
}
catch (Exception ex)
{
Console.WriteLine("ERRNO [" + ex.Message + " ]");
return null;
}
return hashText;
}
What we've tried so far:
Using RSACryptoServiceProvider to explicitly persist the key in the CSP
RSACryptoServiceProvider key = (RSACryptoServiceProvider)cmsSigner.Certificate.PrivateKey;
key.PersistKeyInCsp = true;
This works if we use the SignHash method but as I've said before, we need to verify the signed data server-side and we do not have access to the certificate, therefore we need a PKCS envelope. If I set this bool and sign using the CMS code the behaviour is the same.
Setting the PIN programmatically
Another try was setting the PIN programmatically via CryptoContext, based on this answer:
private void SetPinForPrivateKey(X509Certificate2 certificate, string pin) {
if (certificate == null) throw new ArgumentNullException("certificate");
var key = (RSACryptoServiceProvider)certificate.PrivateKey;
var providerHandle = IntPtr.Zero;
var pinBuffer = System.Text.Encoding.ASCII.GetBytes(pin);
// provider handle is implicitly released when the certificate handle is released.
SafeNativeMethods.Execute(() => SafeNativeMethods.CryptAcquireContext(ref providerHandle,
key.CspKeyContainerInfo.KeyContainerName,
key.CspKeyContainerInfo.ProviderName,
key.CspKeyContainerInfo.ProviderType,
SafeNativeMethods.CryptContextFlags.Silent));
SafeNativeMethods.Execute(() => SafeNativeMethods.CryptSetProvParam(providerHandle,
SafeNativeMethods.CryptParameter.KeyExchangePin,
pinBuffer, 0));
SafeNativeMethods.Execute(() => SafeNativeMethods.CertSetCertificateContextProperty(
certificate.Handle,
SafeNativeMethods.CertificateProperty.CryptoProviderHandle,
0, providerHandle));
}
With this approach I am able to disable the PIN prompt by setting the PIN programmatically. The problem here is that I have to read the PIN the first time so I can set it in the subsequent signatures.
I've tried to read the PIN from the prompt using CryptoGetProvParam with the dwParam PP_ADMIN_PIN and PP_KEYEXCHANGE_PIN but without luck. My two guesses here are:
I'm not reading in the right time or way
CMS uses a different handler internally
Question 1:
Is there any way to read the PIN set in the Windows prompt?
Question 2:
If reading the PIN is not possible, is there any other way to force PIN caching?
Only now realized this question is still without an answer although we managed to bypass the whole 'read the PIN from Windows prompt' question.
This method does not answer my first question but I'll be answering the second question.
There was a bug in the smartcard CSP provider that disabled the PIN cache for all requests to SignHash even though they were made in the same process.
The smartcard provider has a SDK that exposes some smartcard operations, being one of those an operation to validate the smartcard PIN.
What we ended up doing was to create a simple WPF window that requests the user's PIN and uses the SDK to validate the PIN. If it is correct we use the method that I posted in the original question to force the PIN cache:
Another try was setting the PIN programmatically via CryptoContext, based on this answer:
private void SetPinForPrivateKey(X509Certificate2 certificate, string pin) {
if (certificate == null) throw new ArgumentNullException("certificate");
var key = (RSACryptoServiceProvider)certificate.PrivateKey;
var providerHandle = IntPtr.Zero;
var pinBuffer = System.Text.Encoding.ASCII.GetBytes(pin);
// provider handle is implicitly released when the certificate handle is released.
SafeNativeMethods.Execute(() => SafeNativeMethods.CryptAcquireContext(ref providerHandle,
key.CspKeyContainerInfo.KeyContainerName,
key.CspKeyContainerInfo.ProviderName,
key.CspKeyContainerInfo.ProviderType,
SafeNativeMethods.CryptContextFlags.Silent));
SafeNativeMethods.Execute(() => SafeNativeMethods.CryptSetProvParam(providerHandle,
SafeNativeMethods.CryptParameter.KeyExchangePin,
pinBuffer, 0));
SafeNativeMethods.Execute(() => SafeNativeMethods.CertSetCertificateContextProperty(
certificate.Handle,
SafeNativeMethods.CertificateProperty.CryptoProviderHandle,
0, providerHandle));
}
With this we are able to request the PIN only one time when signing multiple hashes until the smartcard provider fixes the bug on their side.
I am using RNCryptor in Swift and C#.NET . I need a cross platform AES encryption and because of this, I am using RNCryptor.
When I encrypt some plain text in Swift,I can decrypt it in Swift correctly without any error. But when I encrypt some text in C# and then I want to decrypt it in Swift,I got an error " The operation couldn’t be completed. (RNCryptorError error 2.)"
My code in C# :
public static string EncryptQRCode(string qrCodeString){
var qrEncryptor = new Encryptor ();
return qrEncryptor.Encrypt (qrCodeString, "password");
}
public static string DecryptQRCode(string qrEncryptedString){
var qrDecryptor = new Decryptor();
return qrDecryptor.Decrypt (qrEncryptedString, "password");
}
My Code in Swift:
func Encrypt(msg:String, pwd:String) -> String{
let data = msg.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
let chiper = RNCryptor.encryptData(data!, password: pwd)
let base = chiper.base64EncodedDataWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
let stringBase = String(data: base, encoding: NSUTF8StringEncoding)
return stringBase!
}
func Decrypt(msg:String, pwd:String) -> String{
let encodedData:NSData = NSData(base64EncodedString: msg, options: NSDataBase64DecodingOptions(rawValue: 0))!
do{
let decryptedData = try RNCryptor.decryptData(encodedData, password: pwd)
let decrypytedString = String(data: text, encoding: NSUTF8StringEncoding)
return decryptedString!
}
catch let error as NSError{
print(error.localizedDescription)
print(error.localizedDescription)
}
return "AN ERROR OCCURED"
}
For example:
"ABC", with password "behdad" in C#, Encryptor returned:
"AgHfT2VvVOorlux0Ms47K46fG5lQOP2YhYWq2KeIKh+MisCDqZfrLF+KsJyBR2EBNC3wQpaKev0X4+9uuC5vliVoHkLsEi6ZI7ZIZ8qVUEkYGQ=="
When I decrypt it in C#, it returned "ABC".
But when I pass this Base64Encoded string to my Swift Decryptor function,it returned:
RNCryptorError error 2.
For Example:
qrCodeString = "ABC".
public static string EncryptQRCode returns =
"AgF6P5Ya0SifSymd3LqKdH+kGMCFobiziUhwwB6/lfZgAA9N+F5h350MyigoKo9qgUpMXX3x9FxZXwUOJODL4is3R62EGvZWdJBzjSNCef7Ouw=="
The "msg" is returned data from EncryptQRCode(The Base64 Encoded String).
pwd = "password"
encoded data = <02017a3f 961ad128 9f4b299d dcba8a74 7fa418c0 85a1b8b3 894870c0 1ebf95f6 60000f4d f85e61df 9d0cca28 282a8f6a 814a4c5d 7df1f45c 595f050e 24e0cbe2 2b3747ad 841af656 7490738d 234279fe cebb>
decryptedString and decryptedData do not have values due to the error occurred.
RNCryptorError error 2
UnknownHeader = 2
Unrecognized data format. Usually this means the data is corrupt.
This means that the data passed is not in the correct format.
The best programming advice I ever got was one night in the computer room when I asked Rick Cullman for help and he said: "Read the documentation."
That is why I suggested displaying the inputs and outputs, you will catch that.
There are many places where hexadecimal is need to see what is happening and to debug.
Opened an issue in RNCryptor Swift to add the error codes to the documentation.
In C# when you want to encrypt the text,you have to use Schema.V3 for encryption. Decryptor in Swift cannot identify the Schema Version of Base 64 encoded string.
string encrypted = encryptor.Encrypt (YOUR_PLAIN_TEXT, YOUR_PASSWORD,Schema.V3);
I wrote a C# chat software that uses a new (at least for me) system that I called request system. I don't know if that has been created before, but for now I think of it as my creation :P
Anyhow, this system works like this:
soc receives a signal
checks the signal
if the data it just received is the number 2, the client software knows that the server is about to send a chat message. if the number is 3, so the client knows that the server is about to send the member list, and so on.
The problem is this: when I do step-by-step in VS2012 it works fine, the chat is working properly. When I use it on debug mode or just run it on my desktop, there seems to be missing data, and it shouldn't be because the code is working just fine...
Example of code for the sending&receiving message on client:
public void RecieveSystem()
{
while (true)
{
byte[] req = new byte[1];
soc.Receive(req);
int requestID = int.Parse(Encoding.UTF8.GetString(req));
if (requestID == 3)
{
byte[] textSize = new byte[5];
soc.Receive(textSize);
byte[] text = new byte[int.Parse(Encoding.UTF8.GetString(textSize))];
soc.Receive(text);
Dispatcher.Invoke(() => { ChatBox.Text += Encoding.UTF8.GetString(text) + "\r\n"; });
}
}
}
public void OutSystem(string inputText)
{
byte[] req = Encoding.UTF8.GetBytes("3");
soc.Send(req);
byte[] textSize = Encoding.UTF8.GetBytes(Encoding.UTF8.GetByteCount(inputText).ToString());
soc.Send(textSize);
byte[] text = Encoding.UTF8.GetBytes(inputText);
soc.Send(text);
Thread.CurrentThread.Abort();
}
and on the server:
public void UpdateChat(string text)
{
byte[] req = Encoding.UTF8.GetBytes("3");
foreach (User user in onlineUsers)
user.UserSocket.Send(req);
byte[] textSize = Encoding.UTF8.GetBytes(Encoding.UTF8.GetByteCount(text).ToString());
foreach (User user in onlineUsers)
user.UserSocket.Send(textSize);
byte[] data = Encoding.UTF8.GetBytes(text);
foreach (User user in onlineUsers)
user.UserSocket.Send(data);
}
public void RequestSystem(Socket soc)
{
~~~
}
else if (request == 3)
{
byte[] dataSize = new byte[5];
soc.Receive(dataSize);
byte[] data = new byte[int.Parse(Encoding.UTF8.GetString(dataSize))];
soc.Receive(data);
UpdateChat(Encoding.UTF8.GetString(data));
}
}
catch
{
if (!soc.Connected)
{
Dispatcher.Invoke(() => { OnlineMembers.Items.Remove(decodedName + " - " + soc.RemoteEndPoint); Status.Text += soc.RemoteEndPoint + " Has disconnected"; });
onlineUsers.Remove(user);
Thread.CurrentThread.Abort();
}
}
}
}
What could be the problem?
You're assuming that you'll have one packet for each Send call. That's not stream-oriented - that's packet-oriented. You're sending multiple pieces of data which I suspect are coalesced into a single packet, and then you'll get them all in a single Receive call. (Even if there are multiple packets involved, a single Receive call could still receive all the data.)
If you're using TCP/IP, you should be thinking in a more stream-oriented fashion. I'd also encourage you to change the design of your protocol, which is odd to say the least. It's fine to use a length prefix before each message, but why would you want to encode it as text when you've got a perfectly good binary connection between the two computers?
I suggest you look at BinaryReader and BinaryWriter: use TcpClient and TcpListener rather than Socket (or at least use NetworkStream), and use the reader/writer pair to make it easier to read and write pieces of data (either payloads or primitives such as the length of messages). (BinaryWriter.Write(string) even performs the length-prefixing for you, which makes things a lot easier.)
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.