Networking, Threading and DPAPI : Sample App


Networking, Threading and DPAPI : A small project which includes all three of these.

You must be wondering how these three things are related.
For your information they are not related at all, these three(Networking, Threading and DPAPI) are different concepts all together.
However we can make them related in our implementations:

Before starting with the codes snippets, let me tell about my implementation scenario.

The scenario:
I have to build an Peer to Peer application that has only one input field a "password".
There are two users, not more than that, who can input the passwords from there respective systems.
Passwords has to be encrypted, so we will be using here "PBKDF2"
(For more information on PBKDF2: http://dotnetspider.com/resources/40614-PBKDF-Rfc-DeriveBytes-Encryption.aspx)

The Salt for the PBDKF2 Key should be recieved from the other user, that is the password of the other user becomes the Salt of the current user
For Eg:
USER1 & USER2 are on 2 different machines
And PBDKF2 needs a Password and a Salt, so :

p2p PBDKF2
After getting their own individual Keys they should write the Key into the registry in DPAPI format, So that they can retrive the key later, for use, from the registry.

So what about threading?
The look of my app is such
default form

The Password box and the Generate Key button will be enabled only after a Remote system(the system of the other USER ) is selected or
if the client is connected.


Remember this is a Peer 2 peer App. If User1 tries to connect to User2, using Remote system select
then User1 becomes the client and User2 the server.
The concept of peer to peer is that any user can become a server, it depends on the requests from the client machine

And on Generate Key button click the Write to registry button has to be enabled and Password box and the Generate Key button has to be disabled again.

If you simply write the following code:
on remote system selected:
PasswordBox.Enabled = true;
GenKeyButton.Enabled = true;
And on Generate Key button click:
PasswordBox.Enabled = false;
GenKeyButton.Enabled = false;
Writetoregistry_button.Enabled = true;

This will throw an exception: Saying that two threads cannot run at the same time.
Because, remember we are doing networking over here which has to be the default thread, and when we put for eg. PasswordBox.Enabled = false;,
it is not the part of the network thread, so that's the cause of the exception.

So Lets begin:
First we have to populate our "remote system combobox"
remotesystem

To fill the combo box I have used a custom class "network_browser"(which I wont discuss here).
You can find the detailed explanation of network browser class here:
http://dotnetspider.com/resources/40745-Network-Browser.aspx

The implementation of network_browser class:
Its method is : getNetworkComputers() -->
this method will return all the available network connected(as an array of string)to your Lan

NetworkBrowser browser = new NetworkBrowser();
foreach (string pc in browser.getNetworkComputers())
{
OtherComputersCombo.Items.Add(pc);
}


The initialiaser that we will need:

private string hostName; //For Host Machine Name
private Socket serverSocket; //The server has a socket to listen on

//Only one of the server or client will establish a connection
private Socket activeConnection;
private byte[] receiveBuffer;
private byte[] sendBuffer;

//Since only one of the connections get used, the label for the remote system must change
private string remoteLabel;

//Consolidate the received data until there is enough
private byte[] saltBuffer;
private int saltBytesReceived;

//for thread operations.
public Thread thread = null;

//Track the presence of the required data for deriving the key
private bool saltReceived;
private bool passwdCaptured;
private bool Keyfragmentrecieved;

string getKeyFragment ;


Now the Form Initailiazer:

public Form1()
{
InitializeComponent();

hostName = Dns.GetHostName();

//Set up the buffer for receiving the salt value from the remote system
saltBuffer = new byte[16];
saltBytesReceived = 0;

//Initialise the process status flags
saltReceived = false;
passwdCaptured = false;
}


Now the Form Load event:

private void Form1_Load(object sender, EventArgs e)
{
//create a new NetworkBrowser object, and get the list of network computers it found, and add each
//entry to the combo box
try
{
//No access till Remote system is selected
PasswordBox.Enabled = false;
GenKeyButton.Enabled = false;
writetoregistry_button.Enabled = false;

NetworkBrowser browser = new NetworkBrowser();
foreach (string pc in browser.getNetworkComputers())
{
OtherComputersCombo.Items.Add(pc);
}
statusLabel.Text = "List of remote systems loaded.";

}
catch ( Exception ex )
{
statusLabel.Text = "Error retrieving list of remote systems.";
MessageBox.Show(": REASON :\n " + ex);
}
finally
{
//Start the server on the local machine and display the name
startServer();
}

}


Connect to remote system

private void OtherComputersCombo_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
//Set up the client socket
activeConnection = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

//Create the end point based on the newly selected remote system, taking
//the first IP address returned for it
IPAddress remoteAddress = Dns.GetHostAddresses(OtherComputersCombo.Text)[0];
IPEndPoint remoteEndPoint = new IPEndPoint(remoteAddress, 5050);

// and connect the socket to the endpoint
activeConnection.Connect(remoteEndPoint);

//TODO: set the status string to indicate the connection to server

//Set the label for the remote system
remoteLabel = "server";

//Bind the event handler for dealing with incoming data
receiveBuffer = new byte[1];

activeConnection.BeginReceive(
receiveBuffer, 0,
receiveBuffer.Length,
SocketFlags.None,
new AsyncCallback(onDataReceived),
activeConnection
);

statusLabel.Text = " Connected to: " + OtherComputersCombo.Text + " as Server ";

//Enables Password TextBox and GenKeyButton on connection establish.
PasswordBox.Enabled = true;
GenKeyButton.Enabled = true;
}
catch
{
statusLabel.Text = " " + OtherComputersCombo.Text + " Not Found : ";
}
}


Start Server Method

private void startServer()
{
try
{
//Set up a socket attached to the relevant port listening on all addresses
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 5050);
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(localEndPoint);
serverSocket.Listen(1);

//Bind the event handler for dealing with connections
serverSocket.BeginAccept(new AsyncCallback(onClientConnect), null);

//Update the status message
statusLabel.Text = "Server Started on " + hostName + ".";
}
catch
{
statusLabel.Text = " Cannot be connected ";
DialogResult dr = MessageBox.Show("Do you want to continue \n" +
" Yes : Continue as server \n No : Exit the application",
"Server Disconnected", MessageBoxButtons.YesNo);
switch (dr)
{
case DialogResult.Yes:
Application.Restart();// restarts the application
break;
case DialogResult.No:
Application.Exit();// Application closes
break;
default:
Application.Exit();
break;
}
}
}


On client connect method
In this method the the new thread is created
It means when the other USER connects to current USER's system, the required fields will get enabled. for both of the users

private void onClientConnect(IAsyncResult asyn)
{
try
{
//Get the connection object
activeConnection = serverSocket.EndAccept(asyn);

//on Client connect
statusLabel.Text = "Client Connected";

//Set the label for the remote system
remoteLabel = "client";

//Bind the event handler for dealing with incoming data
receiveBuffer = new byte[1];

activeConnection.BeginReceive(
receiveBuffer, 0,
receiveBuffer.Length,
SocketFlags.None,
new AsyncCallback(onDataReceived),
activeConnection
);


//Creates a new thread for passwordBox & GenKeyButton enabling
thread = new Thread(delegate()
{
this.BeginInvoke((ThreadStart)delegate()
{
this.PasswordBox.Enabled = true;
this.GenKeyButton.Enabled = true;
});

});
thread.Start();



//If we were going to accept more connections we'd call BeginAccept here, but we only want one
}
catch
{
statusLabel.Text = "Client Not-connected";
}
}


On Generate Key button click

private void GenKeyButton_Click(object sender, EventArgs e)
{
MD5 hashProvider = MD5.Create();

//The hash is returned as an array of 16 bytes, not a 32 char hax encoded string
sendBuffer = hashProvider.ComputeHash(System.Text.Encoding.ASCII.GetBytes(PasswordBox.Text));

activeConnection.Send(sendBuffer);

//Set the status flag and see if we can now derive
passwdCaptured = true;

//Calls the derive key function to get the key fragment
deriveKey();
}


On Data/Salt Recieved Method

public void onDataReceived(IAsyncResult asyn)
{
int receiveLength = 0;

try
{
statusLabel.Text = "Receiving from " + remoteLabel + ".";

// Complete the BeginReceive() asynchronous call by EndReceive() method
// which will return the number of bytes written to the stream
// by the client
receiveLength = activeConnection.EndReceive(asyn);

//Copy the received bytes from the socket buffer to the salt buffer
Buffer.BlockCopy(receiveBuffer, 0, saltBuffer, saltBytesReceived, receiveLength);
saltBytesReceived += receiveLength;

if (saltBytesReceived == 16)
{
//Set the flag for the receipt of the hash and check if we are now ready to derive the key
saltReceived = true;
deriveKey();
}
else
{
//If we haven't got enough data yet, set up the event to handle the receipt of more
activeConnection.BeginReceive(
receiveBuffer, 0,
receiveBuffer.Length,
SocketFlags.None,
new AsyncCallback(onDataReceived),
activeConnection
);
}
}
catch
{
statusLabel.Text = "Transmission Disconnected ";

//Opens a Message box on being disconnected
//Asks for if user want to close the application or continue as a server
DialogResult dr = MessageBox.Show("Do you want to continue as Server \n "+
"Yes : Continue as server \n No : Exit the application",
"Server Disconnected",MessageBoxButtons.YesNo);
switch (dr)
{
case DialogResult.Yes:

activeConnection.Shutdown(SocketShutdown.Both);
activeConnection.Close();
activeConnection = null;
Application.Restart();
break;
case DialogResult.No:
Application.Exit();
break;
default:
Application.Exit();
break;
}
}
}


Derive Key Method: In this method it derives the PBDKF2 key on reciept of Password from the current user and Salt from the Remote user
It is an automated process
In this method another thread is created to disabble the Password box, generate key button, remote system select combobox
and enables write to registry button

private void deriveKey()
{
if (passwdCaptured && saltReceived)
{
//Creates a new thread for Deployment password input
//And restricts primary user to be able for input
thread = new Thread(delegate()
{
this.BeginInvoke((ThreadStart)delegate()
{
this.PasswordBox.Enabled = false;
this.GenKeyButton.Enabled = false;
this.OtherComputersCombo.Enabled = false;
this.writetoregistry_button.Enabled = true;
});

});
thread.Start();

//Derive the key if all the information required is available
Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(PasswordBox.Text,saltBuffer, 3000);
string myString = Convert.ToBase64String(rfc2898.GetBytes(32));
statusLabel.Text = "Key : " + myString;

//copy the key to getKeyFragment
getKeyFragment = myString;

//Call Write_To_Registry method
Write_To_Registry(getKeyFragment);
}
else if (saltReceived)
{
statusLabel.Text = "Salt received from " + remoteLabel;
}
else if (passwdCaptured)
{
statusLabel.Text = "Waiting for salt from " + remoteLabel;
}
}


Write to registry in DPAPI Format method
Why DPAPI ?
It is the most secure way to write registry values
I have registered the value in "\\Hkey Local Machine\\SOFTWARE\\Mydba33"
dpapi2

Have a look at the value: I don't think any one will be cracking that value
So the code :

void Write_To_Registry(string Key)
{
try
{
// Convert the Deployed Key to byte arrays and encrypt
// the data by using the DPAPI [ProtectedData] class.
//write the key to local machine
byte[] encryptedDeployedKey = ProtectedData.Protect(
UnicodeEncoding.ASCII.GetBytes(Key),
null,
DataProtectionScope.LocalMachine);

// Create a security context for the key that we will use to store
// the credentials that will restrict access to only the application user.
string userEnv = Environment.UserDomainName + "\\" + Environment.UserName;

RegistrySecurity security = new RegistrySecurity();
RegistryAccessRule rule = new RegistryAccessRule(userEnv,
RegistryRights.FullControl,
InheritanceFlags.ContainerInherit,
PropagationFlags.None,
AccessControlType.Allow);
security.AddAccessRule(rule);

//Write to the registry
Microsoft.Win32.RegistryKey registryKey;
registryKey = Microsoft.Win32.Registry.LocalMachine.CreateSubKey("SOFTWARE\\Mydba33",
Microsoft.Win32.RegistryKeyPermissionCheck.ReadWriteSubTree, security);

registryKey.SetValue("Deployed Key", encryptedDeployedKey);
registryKey.Close();
}
catch (Exception ex)
{
//Throw an exception if registry not created and written
MessageBox.Show("Registry not created \nReason\n" + ex.ToString());
}

}


THE END


I am also attaching the Application I discussed here.

Any doubts, give a response.

Cheers
Paul

Reference: http://dotnetspider.com/resources/40745-Network-Browser.aspx


Attachments

  • The application (40748-28712-P2PForm.rar)
  • Comments

    No responses found. Be the first to comment...


  • Do not include your name, "with regards" etc in the comment. Write detailed comment, relevant to the topic.
  • No HTML formatting and links to other web sites are allowed.
  • This is a strictly moderated site. Absolutely no spam allowed.
  • Name:
    Email: