How To Symmetrically Encrypt a String

A friend of mine asked me today how one would go about encrypting something using the Rijndael algorithm (also known as AES) using the .NET framework. I’ve done some crypto work, so I whipped up a quick example for him. It turns out to be pretty much exactly the same as using any other symmetric algorithm like DES. The code is below.

Note

A much more complete article covering encryption in .NET can be found at http://www.dotnetdevs.com/articles/UsingEncryption.aspx. Recommended.

It’s pretty simple – it creates a form that allows you to set a secret key and the initialization vector (IV). The key should be secret, but the IV doesn’t have to be, and the form gives you buttons to let you randomly generate both. The Encrypt and Decrypt buttons do what you’d expect, displaying the resulting encrypted text as a base64-encoded array.

The same routine (PerformCryptoOperation) does the actual decryption as does the encryption. The reason this can work is that the ICryptoTransform interface models the actual math – and we can call CreateEncryptor and CreateDecryptor to get the appropriate implementation. Making our job even easier is the CryptoStream class, which sits in front of a “normal” stream, applying the transform modeled by the ICryptoTransform we give it first. Very handy.

Oh – this also demonstrates how to generate a cryptographically secure random number steam using RNGCryptoServiceProvider.

Update!

A bug in the 1.0 framework will zero out the IV when the encryptor/decryptor is finalized – we’re okay because we’re making a new array for the IV every time we encrypt, but if you create the encryptor/decryptor by passing in a reference to the same shared array every time, it can get zeroed out when you’re not expecting it! Use Array.Clone() to solve this problem.

This problem only manifests itself on my machine for the providers that map to the OS-provided implementations of encryption algorithms: DES, Triple DES, and RC2. The managed Rijndael provider doesn’t have this problem.

The Code

using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Security.Cryptography; using System.IO; public class Form1 : System.Windows.Forms.Form { // Make a new instance of the RijndaelManaged object // If you wanted to do TripleDES, you'd write this instead: SymmetricAlgorithm alg = new TripleDESCryptoServiceProvider(); SymmetricAlgorithm alg = new RijndaelManaged(); public Form1() { InitializeComponent(); } static void Main() { Application.Run(new Form1()); } private void Encrypt_Click(object sender, System.EventArgs e) { // Create an encryptor based on the key and IV specified in the UI // WARNING! A bug in the 1.0 framework will zero out the IV when // the encryptor is finalized – we’re okay because we’re making // a new array for the IV every time we encrypt, but if you pass // in a reference to an array, it can get zeroed out! ICryptoTransform enc = alg.CreateEncryptor( Convert.FromBase64String(tbKey.Text), Convert.FromBase64String(tbIV.Text) ); // Turn the input from a string into an array of bytes System.Text.ASCIIEncoding ascii = new System.Text.ASCIIEncoding(); byte[] plaintext = ascii.GetBytes(tbPlaintext.Text); // Encrypt the input byte[] cyphertext = PerformCryptoOperation(enc, plaintext); // Display the resulting byte array as base64 tbCyphertext.Text = Convert.ToBase64String(cyphertext); } private void Decrypt_Click(object sender, System.EventArgs e) { // Create a dencryptor based on the key and IV specified in the UI // WARNING! A bug in the 1.0 framework will zero out the IV when // the decryptor is finalized – we’re okay because we’re making // a new array for the IV every time we decrypt, but if you pass // in a reference to an array, it can get zeroed out! ICryptoTransform dec = alg.CreateDecryptor( Convert.FromBase64String(tbKey.Text), Convert.FromBase64String(tbIV.Text) ); // Get the cyphertext as an array of bytes byte[] cyphertext = Convert.FromBase64String(tbCyphertext.Text); // Decrypt it byte[] plaintext = PerformCryptoOperation(dec, cyphertext); // It's an array of bytes, but we want a string System.Text.ASCIIEncoding ascii = new System.Text.ASCIIEncoding(); tbPlaintext.Text = ascii.GetString(plaintext); } private byte[] PerformCryptoOperation(ICryptoTransform op, byte[] input) { // Create a buffer to hold the output MemoryStream msOutput = new MemoryStream(); // Create a crypto stream that will transform anything // we write into it storing the results in the buffer // we created CryptoStream encStream = new CryptoStream(msOutput, op, CryptoStreamMode.Write); // Write the input through to the output buffer. encStream.Write(input, 0, input.Length); // Important - the CryptoStream caches encStream.Close(); // Turn it back into a byte array return msOutput.ToArray(); } private void RandomKey_Click(object sender, System.EventArgs e) { // We'd like secure random numbers, please RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); // Key size is in bits byte[] keybytes = new byte[alg.KeySize / 8]; rng.GetBytes(keybytes); // Display it in base64 tbKey.Text = Convert.ToBase64String(keybytes); } private void RandomIV_Click(object sender, System.EventArgs e) { // We'd like secure random numbers, please RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); // Block size is in bits - IV must match block size byte[] ivbytes = new byte[alg.BlockSize / 8]; rng.GetBytes(ivbytes); // Display it in base64 tbIV.Text = Convert.ToBase64String(ivbytes); } private System.Windows.Forms.TextBox tbKey; private System.Windows.Forms.Button RandomKey; private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label2; private System.Windows.Forms.TextBox tbPlaintext; private System.Windows.Forms.Label label3; private System.Windows.Forms.TextBox tbCyphertext; private System.Windows.Forms.Button Encrypt; private System.Windows.Forms.Button Decrypt; private System.Windows.Forms.Label label4; private System.Windows.Forms.Button RandomIV; private System.Windows.Forms.TextBox tbIV; private System.ComponentModel.Container components = null; private void InitializeComponent() { this.tbKey = new System.Windows.Forms.TextBox(); this.RandomKey = new System.Windows.Forms.Button(); this.label1 = new System.Windows.Forms.Label(); this.label2 = new System.Windows.Forms.Label(); this.tbPlaintext = new System.Windows.Forms.TextBox(); this.label3 = new System.Windows.Forms.Label(); this.tbCyphertext = new System.Windows.Forms.TextBox(); this.Encrypt = new System.Windows.Forms.Button(); this.Decrypt = new System.Windows.Forms.Button(); this.label4 = new System.Windows.Forms.Label(); this.RandomIV = new System.Windows.Forms.Button(); this.tbIV = new System.Windows.Forms.TextBox(); this.SuspendLayout(); // // tbKey // this.tbKey.Location = new System.Drawing.Point(96, 8); this.tbKey.Multiline = true; this.tbKey.Name = "tbKey"; this.tbKey.Size = new System.Drawing.Size(568, 48); this.tbKey.TabIndex = 0; this.tbKey.Text = "base64-encoded Key"; // // RandomKey // this.RandomKey.Location = new System.Drawing.Point(304, 64); this.RandomKey.Name = "RandomKey"; this.RandomKey.Size = new System.Drawing.Size(88, 23); this.RandomKey.TabIndex = 1; this.RandomKey.Text = "Random Key"; this.RandomKey.Click += new System.EventHandler(this.RandomKey_Click); // // label1 // this.label1.Location = new System.Drawing.Point(16, 8); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(72, 23); this.label1.TabIndex = 2; this.label1.Text = "Key:"; this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; // // label2 // this.label2.Location = new System.Drawing.Point(16, 192); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(80, 23); this.label2.TabIndex = 4; this.label2.Text = "Plaintext:"; this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; // // tbPlaintext // this.tbPlaintext.Location = new System.Drawing.Point(104, 192); this.tbPlaintext.Multiline = true; this.tbPlaintext.Name = "tbPlaintext"; this.tbPlaintext.Size = new System.Drawing.Size(568, 120); this.tbPlaintext.TabIndex = 3; this.tbPlaintext.Text = "Plaintext"; // // label3 // this.label3.Location = new System.Drawing.Point(16, 360); this.label3.Name = "label3"; this.label3.Size = new System.Drawing.Size(80, 23); this.label3.TabIndex = 6; this.label3.Text = "Cyphertext:"; this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleRight; // // tbCyphertext // this.tbCyphertext.Location = new System.Drawing.Point(104, 360); this.tbCyphertext.Multiline = true; this.tbCyphertext.Name = "tbCyphertext"; this.tbCyphertext.Size = new System.Drawing.Size(568, 120); this.tbCyphertext.TabIndex = 5; this.tbCyphertext.Text = "base64-encoded Cyphertext"; // // Encrypt // this.Encrypt.Location = new System.Drawing.Point(200, 328); this.Encrypt.Name = "Encrypt"; this.Encrypt.TabIndex = 7; this.Encrypt.Text = "Enrypt"; this.Encrypt.Click += new System.EventHandler(this.Encrypt_Click); // // Decrypt // this.Decrypt.Location = new System.Drawing.Point(432, 328); this.Decrypt.Name = "Decrypt"; this.Decrypt.TabIndex = 8; this.Decrypt.Text = "Decrypt"; this.Decrypt.Click += new System.EventHandler(this.Decrypt_Click); // // label4 // this.label4.Location = new System.Drawing.Point(16, 96); this.label4.Name = "label4"; this.label4.Size = new System.Drawing.Size(72, 23); this.label4.TabIndex = 11; this.label4.Text = "IV:"; this.label4.TextAlign = System.Drawing.ContentAlignment.MiddleRight; // // RandomIV // this.RandomIV.Location = new System.Drawing.Point(312, 160); this.RandomIV.Name = "RandomIV"; this.RandomIV.Size = new System.Drawing.Size(88, 23); this.RandomIV.TabIndex = 10; this.RandomIV.Text = "Random IV"; this.RandomIV.Click += new System.EventHandler(this.RandomIV_Click); // // tbIV // this.tbIV.Location = new System.Drawing.Point(96, 96); this.tbIV.Multiline = true; this.tbIV.Name = "tbIV"; this.tbIV.Size = new System.Drawing.Size(568, 56); this.tbIV.TabIndex = 9; this.tbIV.Text = "base64-encoded initialization vector"; // // Form1 // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(760, 494); this.Controls.AddRange(new System.Windows.Forms.Control[] { this.label4, this.RandomIV, this.tbIV, this.Decrypt, this.Encrypt, this.label3, this.tbCyphertext, this.label2, this.tbPlaintext, this.label1, this.RandomKey, this.tbKey}); this.Name = "Form1"; this.Text = "Form1"; this.ResumeLayout(false); } }