Introduction
Lets create a cryptographic class which has two methods - encrypt and decrypt. These two methods will allow you to exchange your public key with the other party and decrypt the secret messages with your private key. The secret message will be encrypted using standard AES encryption.
Table of Contents
Glossary
Visualization of Diffie-Hellman
Using the code
Step 01 - Create a Class Library
Step 02 - Add fields
Step 03 - Add a constructor
Step 04 - Expose Public Key and IV
Step 05 - Create an Encrypt method
Step 06 - Create a Decrypt method
Step 07 - Dispose unmanaged resources
Step 08 - Create a test class
Final Words
Glossary
AES (
Advanced Encryption Standard
) - Originally called "
Rijndael
", is a specification for the encryption of electronic data established by the U.S. National Institute of Standards and Technology (NIST) in 2001. The algorithm described by
AES
is a symmetric-key algorithm, meaning the same key is used for both encrypting and decrypting the data.
CNG (
Cryptography Next Generation
) - A cryptographic development platform that allows developers to create, update, and use custom cryptography algorithms in cryptography-related applications.
Diffie-Hellman - A method of securely exchanging cryptographic keys over a public channel and was one of the first public-key protocols as originally conceptualized by Ralph Merkle and named after Whitfield
Diffie
and Martin
Hellman
.
IV (I
nitialization Vector
) - An arbitrary number that can be used along with a secret key for data encryption. This number, also called a nonce, is employed only one time in any session. (We will be using the other party's IV and public key to decrypt the secret message.)
Visualization of Diffie-Hellman
Using the Code
Step 01 - Create a Class Library
Open Visual Studio and go to "
File > New > Project
" and select "
Class Library
".
Give your project a name (e.g.
SecureKeyExchange
) and click "OK".
After you project is created, rename the "Class1.cs" file to "DiffieHellman.cs".
Step 02 - Add fields
We need to add three fields; one that contains a reference to the
Aes
-class, the second field to store a reference to the
ECDiffieHellmanCng
-class and the last fields to store our public key.
The
Aes
-reference will be used to encrypt/decrypt the messages. The
ECDiffieHellmanCng
-reference will be used to create a derived key between the two parties.
Add the following three fields to your class:
private Aes aes = null;
private ECDiffieHellmanCng diffieHellman = null;
private readonly byte[] publicKey;
this.diffieHellman = new ECDiffieHellmanCng
KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash,
HashAlgorithm = CngAlgorithm.Sha256
this.publicKey = this.diffieHellman.PublicKey.ToByteArray();
Once the ECDiffieHellmanCng
instance has been initialized, we can set our publicKey
field to the PublicKey
of the ECDiffieHellmanCng
instance. We are going to send this public key along with the secret message to the other party.
Step 04 - Expose Public Key and IV
Lets expose both our public key and IV through properties. Add the following properties respectively:
public byte[] PublicKey
return this.publicKey;
public byte[] IV
return this.aes.IV;
These properties will be sent to the other party to decrypt the secret message using their own private key.
Step 05 - Create an Encrypt method
We are going to create a method that takes the public key of the other party as well as the secret message to encrypt.
We will use the other party's public key to generate a derived key (see "Common secret" in the paint analogy above) which will be used to encrypt the message. Add the Encrypt
function:
public byte[] Encrypt(byte[] publicKey, string secretMessage)
byte[] encryptedMessage;
var key = CngKey.Import(publicKey, CngKeyBlobFormat.EccPublicBlob);
var derivedKey = this.diffieHellman.DeriveKeyMaterial(key);
this.aes.Key = derivedKey;
using (var cipherText = new MemoryStream())
using (var encryptor = this.aes.CreateEncryptor())
using (var cryptoStream = new CryptoStream(cipherText, encryptor, CryptoStreamMode.Write))
byte[] ciphertextMessage = Encoding.UTF8.GetBytes(secretMessage);
cryptoStream.Write(ciphertextMessage, 0, ciphertextMessage.Length);
encryptedMessage = cipherText.ToArray();
return encryptedMessage;
Now our message is encrypted and we can send it to the other party. But first need to add a function to decrypt this secret message.
Step 06 - Create a Decrypt method
Our Decrypt
function will take in 3 parameters: The public key and IV of the other party as well as the secret message. Lets add the function:
public string Decrypt(byte[] publicKey, byte[] encryptedMessage, byte[] iv)
string decryptedMessage;
var key = CngKey.Import(publicKey, CngKeyBlobFormat.EccPublicBlob);
var derivedKey = this.diffieHellman.DeriveKeyMaterial(key);
this.aes.Key = derivedKey;
this.aes.IV = iv;
using (var plainText = new MemoryStream())
using (var decryptor = this.aes.CreateDecryptor())
using (var cryptoStream = new CryptoStream(plainText, decryptor, CryptoStreamMode.Write))
cryptoStream.Write(encryptedMessage, 0, encryptedMessage.Length);
decryptedMessage = Encoding.UTF8.GetString(plainText.ToArray());
return decryptedMessage;
We can now decrypt the secret message.
Step 07 - Dispose unmanaged resources
The last piece of code we still need to add is to implement the IDisposable
interface to clean up our unmanaged resources.
Lets add the interface to our DiffieHellman
class:
public class DiffieHellman : IDisposable
And add the implementation:
public void Dispose()
Dispose(true);
GC.SuppressFinalize(this);
protected virtual void Dispose(bool disposing)
if (disposing)
if (this.aes != null)
this.aes.Dispose();
if (this.diffieHellman != null)
this.diffieHellman.Dispose();
Our class is complete. Now we need to create a test class to test our functionality.
Step 08 - Create a test class
Right click on the solution and select "Add > New Project > Unit Test Project" and give your project a name (e.g "SecureKeyExchange.Tests"). Rename your "UnitTest1.cs" to "DiffieHellmanTests.cs" and add a reference to the "SecureKeyExchange" project. To do this, right-click on the "References" node under the test project and select "Add Reference > Projects > Select the project > OK".
Add the following test method to our test class:
[TestMethod]
public void Encrypt_Decrypt()
string text = "Hello World!";
using (var bob = new DiffieHellman())
using (var alice = new DiffieHellman())
byte[] secretMessage = bob.Encrypt(alice.PublicKey, text);
string decryptedMessage = alice.Decrypt(bob.PublicKey, secretMessage, bob.IV);
We can now add a breakpoint and debug our test (press Ctrl+R,Ctrl+A) to see the results:
Final Words
The Diffie-Hellman key exchange allows us to send secret information over a public channel. In my next post, we will look at how to implement this into a real world scenario.
I am a software developer that loves creating new things, solving solutions and reading about the latest technologies. I am currently working on MVC projects, creating .NET APIs, deploying applications, writing SQL queries, debugging Windows Services, developing Xamarin mobile applications and working with a bunch of great people. Outside of work I am a brother and father.
Hi. Could you please post code for the generic methods of Encrypt and Decrypt you spoke about?
Thanks
Sign In·View Thread
Thanks for the code sample, it helps "de-encrypting" the concepts.
I still have few question if I may. In your example, what would be reffered as the "Common Paint" (color yellow in the diagram)? Would it be the IV ? HEnce, what would be refered as the "mixed color" (in the diagram again)?
Kind regards,
Benoit
Sign In·View Thread
If both key and IV of the other party (that information that is going to be shared publically via an insecure channel) is used to decrypt the secret message, a man in the middle who spies those public key information, could also decrypt the information; so this would not make sense at all, right? Shouldn´t it be the other way around, that encryption will be done using the public key and IV information, and decryption will be done using any derived key material (the information that did not go public, which should be same for both parties)?
Sign In·View Thread
Console.WriteLine("DecryptedMessage: {0}", decryptedMessage);
and then clicking the test name and "output" in the Test Explorer displays that standard output properly in the Test Output window.
Looking forward to your next article.
Sign In·View Thread
Thanks for your reply asiwel.
I was hoping the readers would know how to use the "Ctrl+." shortcut ("Quick Actions and Refactorings ...") VS-feature to add those required dependencies. I'll put the tip in my next follow up article in this series!
Also, the reason it might complete without hitting the breakpoints in the test could be you are not running in Debug mode or you are not running a "Debug Test". You can run a debug test by hovering over the "Test" menu item at the top, then "Debug > All Tests" or right-clicking on the test in the test explorer and selecting "Debug Test".
Regards
Sign In·View Thread
Thanks for your quick reply. Yes, adding those dependencies was not a problem, just a tiny moment of surprise.
And, indeed, I was not running a "Debug Test." Works fine, just as you described, when you do that!
Encryption is not an area I know very much about. Can you send any object from one app to another using this approach? Or does it just work for text strings or streams (text, XML, JSon, etc.)? In other words, can I send (to myself in this case) a simple or a complex object without serializing it, etc?
Sign In·View Thread
Glad you got it working.
I don't believe you can send complex objects without serializing them.
You could change the Encrypt method to a generic function:
public byte[] Encrypt<T>(byte[] publicKey, T secretMessage)
And use the following methods to serialize/deserialize T
:
Pierre Nortje wrote:
I don't believe you can send complex objects without serializing them.
You could change the Encrypt method to a generic function:
I think that idea and the simple code you provided would work fine most of the time. For instance, you could send an encrypted dictionary or list to a receiving app that way.
Thank you for an interesting article.
[EDIT] In fact, I just copied your test method and used your ToByteArray and FromByteArray methods to serialize and later deserialize a small dictionary. Then after slight changes in the Encrypt and the Decrypt methods (to use byte arrays rather than strings), everything worked fine. So I sent myself an encrypted dictionary object and successfully decrypted it as a new dictionary of the same type. It would be easy, I think, to make both Encrypt and Decrypt generic along the lines you suggested:
Pierre Nortje wrote:
public byte[] Encrypt<T>(byte[] publicKey, T secretMessage)
modified 8-Dec-17 22:05pm.
Sign In·View Thread
Web01
2.8:2023-03-27:1