Introduction
Sometimes you cannot rely on technologies such as TLS or SSL to protect your passwords on-the-wire: one example is programming against a device with limited capabilities. Furthermore, passwords may need to be stored locally on the machine (e.g. 'remember me' checkbox): this is hard or impossible using salt/salt+hash algorithms.
One solution would be to include the encryption key on both the client at compile time. This opens up a security hole because the source code can simply be decompiled to obtain the secret key.
Another solution would be to send the key over-the-wire when encryption is needed. However, a eavesdropper may be able to sniff out the encryption key.
This solution allows a key to be generated as encryption is required.
A simple explanation that I came across is that you should image a trunk with two padlocks on it. These padlocks have different keys that each party carries with them: the trunk may only be opened once both keys have been obtained.
Background
This article presents a C# implementation of the Diffie-Hellman algorithm. I recommend reading the Wikipedia article to get a grounding in the algorithm: but it is surprisingly easy. Note that the prime and base are transmitted, but if they are compromised it has no effect on the security of the transaction.
Using the Code
The demonstration program should give you everything you need.
The only class you should need to interact with is the DiffieHellman
class.
This class contains three methods.
Generate Request
This generates a request packet. This packet contains the p (shared prime) and g (shared base) fields, as well as the originator's portion of the key. Each field is delimited using a pipe (|).
protected virtual void OnClientConnected(string packet)
{
currentClient.Dh = new DiffieHellman(256).GenerateRequest();
networkSocket.Send(currentClient.Dh.ToString());
}
Generate Response
This generates a response packet. This packet contains only the receiver's portion of the key.
protected virtual void OnDiffieHellmanReceived(string packet)
{
this.Dh = new DiffieHellman(256).GenerateResponse(packet);
this.Key = Dh.Key;
networkSocket.Send(Dh.ToString());
networkSocket.Send(Encrpyt(this.Password));
}
Receive Response
This allows the originator to finalize her/his key.
protected virtual void OnPacketReceived(string packet)
{
if(currentClient.MustDh)
{
currentClient.Dh.HandleResponse(packet);
currentClient.Key = currentClient.Dh.Key;
currentClient.MustDh = false;
}
else
{
currentClient.Password = currentClient.Decrypt(packet);
this.Authenticate(currentClient);
if(!currentClient.Authenticated)
currentClient.ForceDisconnect("Invalid Credentials.");
}
}
Points of Interest
Note that I deliberately chose to call the entities originator and receiver. The originator need not be a server and the receiver need not be a client.
Portions of this code are based on the code provided by Chew Keong TAN.
History
- 25th March, 2008: Initial post
Fixed a small error in the receive response section (MustDh
was set to true
instead of false
)