Click here to Skip to main content
Click here to Skip to main content
Go to top

Create and Share (with a client app) a Random Encryption Key

, 5 Oct 2010
Rate this:
Please Sign up or sign in to vote.
One way to create a completely random encryption key, and sharing it with a client application

screenshot.jpg

Introduction

Last week, someone posted a question about producing a random encryption key, and since I'd done something along those lines before, I figured I'd try to reproduce my past efforts, and post an article about it.

General

The idea is to create a completely random key, encrypt the desired data with it, and then somehow share the key with the consumer so that they can decrypt the data, passing both the key and the data to the consumer. This article provides the code to create the key, and leaves the actual encryption stuff and data transfer to the imagination of the programmer.

Creating the Key

I started off with an array of strings. More specifically, an array of guids converted to strings. For the purpose of example, the size of the array was kept to a sane size, but the nature of the code is such that you could easily expand the array to make it even more "interesting" for people wanting to reverse engineer what the code does. The only restriction is that each array element be exactly the same size (in this example, the strings are all 32 characters long).

protected string[] m_markers = new string[11] {"E388e564Bb4040E78fB6268120b5521D",
                                               "84525762d2Ef46178BbEe9CA60cFaBa7",
                                               "245Aa8A0cDe44342A9F0f3E598fD41cA",
                                               "dCc0A41302c345F59698d5CaE32a4AcE",
                                               "08C134fE645847108f5A09a4B7310c10",
                                               "071dBf900Ec1413FaA8bC42f153E1648",
                                               "6A189FdAeD894DaB93590a413CdA1fB6",
                                               "28aCe9C59cE04925815cCfBeA9eC4b04",
                                               "E83a43D7d48E40e5B6b20Df8B72b2708",
                                               "25277a837B554306847dAfDc285E455b",
                                               "2A06dF9f35E04F54a783D4bE6cF00d0B",
                                              };

I also setup some properties to allow the programmer to exercise some control over the creation of the key:

public int        KeyMinLength 
{ 
	get { return m_keyMinLength; }
	set { m_keyMinLength = value; }
}
/// <summary />
/// Get/set the maximum possible key length
/// </summary />
public int        KeyMaxLength 
{ 
	get { return m_keyMaxLength; }
	set { m_keyMaxLength = value; }
}
/// <summary />
/// Get/set the key direction (how the marker array is traversed while 
/// building the key)
/// </summary />
public Directions Direction 
{ 
	get { return m_direction; }
	set { m_direction = value; }
}
/// <summary />
/// Get/set the actual length of the key
/// </summary />
public int        ActualKeyLength 
{ 
	get { return m_keyActualLength; }
	set { m_keyActualLength = value; }
}
/// <summary />
/// Get/set the descriptor length
/// </summary />
public int        StaticDescriptorLength 
{ 
	get { return m_staticDescriptorLength;  }
	set { m_staticDescriptorLength = value; }
}

The most interesting of these is the Direction property. It represents a set of flags that dictate directions and orientation regarding to traverse the marker array while constructing the key. The following directions can be specified:

  • Left
  • Right
  • Up
  • Down
  • Horizontal
  • Vertical
  • Diagonal

Of course, it wouldn't make sense to go both left and right, or up and down, so the class takes reasonable precautions to prevent invalid directions from being specified by calling the ReasonableDirection method before actually trying to build the key. It merely performs a sanity check to make sure the programmer didn't do something strange, like specifying both left and right on a horizontal orietation.

//--------------------------------------------------------------------------------
/// <summary />
/// Determines if the direction specified is "reasonable"
/// </summary />
/// <returns />
public bool ReasonableDirection()
{
	bool reasonable   = true;
	int  orientations = 0;
	int  horizontals  = 0;
	int  verticals    = 0;
	orientations += HasDirection(Directions.Horizontal) ? 1 : 0;
	orientations += HasDirection(Directions.Vertical)   ? 1 : 0;
	orientations += HasDirection(Directions.Diagonal)   ? 1 : 0;
	reasonable    = (orientations == 1);
	if (reasonable)
	{
		horizontals += HasDirection(Directions.Left)  ? 1 : 0;
		horizontals += HasDirection(Directions.Right) ? 1 : 0;
		verticals   += HasDirection(Directions.Up)    ? 1 : 0;
		verticals   += HasDirection(Directions.Down)  ? 1 : 0;
		if (HasDirection(Directions.Horizontal))
		{
			reasonable = (horizontals == 1);
		}
		else if (HasDirection(Directions.Vertical))
		{
			reasonable = (verticals == 1);
		}
		else 
		{
			reasonable = (horizontals == 1 && verticals == 1);
		}
	}
	return reasonable;
}

screenshot2.jpg

The image above illustrates some of the possibilities made possible by the Direction property.

  • The cyan line indicates two possible Directions - Right|Down|Diagonal (starting at row 0, column 1), or Left|Up|Diagonal (starting at position row 7, column 8).
  • The red line indicates two possible Directions - Right|Horizontal (starting at row 5, column 12), or Left|Horizontal (starting at position row 5, column 20).
  • The green line indicates two possible Directions - Up|Vertical (starting at row 3, column 24), or Down|Vertical (starting at position row 7, column 24). Notice that this will cause the code to wrap to the far edge.
  • The yellow line indicates one possible Direction - Right|Horizontal (starting at row 9, column 28). Notice also that the code will not only wrap, but will move to the next row. This indicates that the Direction property also includes the RowIncrement flag. This flag is only reasonable for a Horizontal direction, and causes the code to wrap to the next lowest row in the array (regardless of the specified horizontal direction.

Keep in mind that if you allow the class to randomly select a direction, it's entirely possible to end up with a key where all of the characters are the same. This would happen if the Direction ended up being Diagonal with a starting position at one of the corners of the array. If this bothers you, you can always change the random position selection code to ommit the extreme corners. Personally I see nothing wrong with this happening once in a while because each data item could be sent with a different encryption key.

Finally, we arrive at the ConstructKey method. This method uses the Direction property to determine how to traverse the markers array. This is controlled by using adjusters that control how to index into the array, and into an item in the array. First, we run our reasonableness check. If there's a problem, we throw an exception:

private void ConstructKey()
{
	if (!ReasonableDirection())
	{
		throw new Exception("The specified Direction value indicates either both left and right, or both up and down.");
	}

Next, we set up our indexing adjusters:

	// assume horizontal left-to-right
	int  rowAdjuster   = 0;
	int  colAdjuster   = 1;
	bool wrapToNextRow = false;

	// establish our adjusters so we can index to the correct spot in the markers array
	if (HasDirection(Directions.Horizontal))
	{
		colAdjuster   = HasDirection(Directions.Left) ? -1 : 1;
		wrapToNextRow = HasDirection(Directions.RowIncrement);
		// wrapping to next row will always wrap down do the next row, even 
		// if you're going left
	}
	else if (HasDirection(Directions.Vertical))
	{
		rowAdjuster = HasDirection(Directions.Up) ? -1 : 1;
		colAdjuster = 0;
	}
	else // Directions.Diagonal
	{
		rowAdjuster = HasDirection(Directions.Up) ? -1 : 1;
		colAdjuster = HasDirection(Directions.Left) ? -1 : 1;
	}

Then, to ease typing, we determine where we wrap, and where we wrap to. I use the term "pivot", simply because I like the word "pivot":

	int rowPivotAt = (rowAdjuster == -1) ? 0                       : m_markers.Length - 1;
	int rowPivotTo = (rowAdjuster == -1) ? m_markers.Length - 1    : 0;
	int colPivotAt = (colAdjuster == -1) ? 0                       : m_markers[0].Length - 1;
	int colPivotTo = (colAdjuster == -1) ? m_markers[0].Length - 1 : 0;
	// this is where the magic will start
	int currRow    = ArrayIndex;
	int currCol    = MarkerIndex;

Finally, we get around to actually traversing the array. Because we setup all of our control variables above, the loop is clean, tight, and easy to read:

	StringBuilder key = new StringBuilder("");
	for (int keyCount = 0; keyCount < ActualKeyLength; keyCount++)
	{
		// snatch the character at the current row/column
		key.Append(m_markers[currRow][currCol]);
		// adjust our row/column based on the adjusters (and where we are i the array)
		currCol = (currCol == colPivotAt) ? colPivotTo : currCol + colAdjuster;
		currRow = (currRow == rowPivotAt) ? rowPivotTo : currRow + rowAdjuster;
		// if we need to, wrap to the next row
		if (wrapToNextRow && currCol == colPivotTo)
		{
			// remember, we always wrap down to the next row
			currRow++;
			currRow = (currRow == rowPivotAt) ? rowPivotTo : currRow;
		}
	}
	// set our Key property with the results
	Key = key.ToString();
}

The Descriptor

By now, you're probably thinking, "Okay, great! We have a randomized key. Now what?"

Anybody that knows anything about transmitting secure info knows you don't just go transmitting this stuff in the clear. The way I see it is that it's perfectly viable to "describe" what you're sending without giving away the whole shebang. Enter "the descriptor".

Remember all of those nifty control variables that were used to actually build our random key? Well, all you have to do is provide that information to a client application that references the same assembly that was used to build the key, and it can decode the key based on those values.

So, let's say our key ended up being 14 characters long, and the starting markers array index was 5, the marker index (where we indexed into the marker in array element 5) was 28, and our Direction was Diagonal | Up | Left. We simply call the GetKeyDescriptor method to build our descriptor string:

public string GetKeyDescriptor()
{
	StringBuilder descriptor = new StringBuilder();
	descriptor.AppendFormat("{0},", ActualKeyLength);
	descriptor.AppendFormat("{0},", ArrayIndex);
	descriptor.AppendFormat("{0},", MarkerIndex);
	descriptor.AppendFormat("{0},", (int)Direction);
	descriptor.Append(MakePadding(descriptor.Length));
	KeyDescriptor = descriptor.ToString();
	return KeyDescriptor;
}

You may have noticed the last call to Append. This serves two purposes - a) to act as a way to verify the payload of the descriptor string, and to drive hackers absolutely batty trying to figure out what that largish block of bytes means. Here's the method:

private string MakePadding(int currLength)
{
	int maxLength = StaticDescriptorLength - currLength;
	if (maxLength <= 0)
	{
		// if you get this exception, you need to specify a higher value for 
		// the StaticDescriptorLength property.
		throw new Exception("Descriptor string is already longer than the value indicated by the static descriptor length.");
	}
	StringBuilder padding = new StringBuilder("");
	while (padding.Length < maxLength)
	{
		padding.Append(Guid.NewGuid().ToString("N"));
	}
	return (padding.ToString().Substring(0, maxLength));
}

The object of this method is to create enough random padding characters to fill the string to the specified length. As you can see, it simply creates new guids and appends them to the padding string until it exceeds the necessary length, and then calls the Substring method to cut off everything we don't need.

Finally, the client application receiving the descriptor string needs a way to recreate the key on its end. We do that by overloading the BuildKey method with a version that accepts the descriptor string, and parses that string:

public void BuildKey(string descriptor)
{
	Key             = "";
	KeyDescriptor   = descriptor;
	ActualKeyLength = 0;
	ArrayIndex      = -1;
	MarkerIndex     = -1;
	Direction       = Directions.None;

	if (descriptor.Length == StaticDescriptorLength)
	{
		string[] parts = descriptor.Split(',');
		int temp;
		if (Int32.TryParse(parts[0], out temp))
		{
			ActualKeyLength = temp;
		}
		if (Int32.TryParse(parts[1], out temp))
		{
			ArrayIndex = temp;
		}
		if (Int32.TryParse(parts[2], out temp))
		{
			MarkerIndex = temp;
		}
		if (Int32.TryParse(parts[3], out temp))
		{
			Direction = (Directions)(temp);
		}
		if (ActualKeyLength > 0 && ArrayIndex >= 0 && MarkerIndex >= 0 && Direction != Directions.None)
		{
			ConstructKey();
		}
	}
}

If both your web site (or web service) and your client application use this assembly (or one based on it), you can send data with a different encryption key for every descrete block of data, making it so random that breaking the encryption would become virtually impossible. To further shake up the hackers, you could publish a new assembly containing a completely different set of marker strings every 30 days or so. Coupled with click-once deployemnt of the client-side application, things would stay in sync without you having to bend over backwards.

The Sample Application

The sample app merely allows you to specify some criteria, and then generate a random key (along with the descriptor string). The last control in the form shows the key rebuilt from the information in the descriptor key (just to prove it works).

In Closing

This code is merely one part of a larger suite of code that would allow secure data transmission without sacrificing data integrity, and should be combined with other measures such as application identifiers, assembly obfuscation and signing, and other techniques. With the descriptor string, you could even specify what encryption algorythm to use, and you could even encrypt the descriptor string to add even more security.

When I wrote code for this a few years ago, I simply encrypted the decsriptor string and prepended it to the encrypted data I was sending. The code on the client side knew what to do because it had the same assemblies.

History

  • 10/04/2010 - Original version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

John Simmons / outlaw programmer
Software Developer (Senior)
United States United States
I've been paid as a programmer since 1982 with experience in Pascal, and C++ (both self-taught), and began writing Windows programs in 1991 using Visual C++ and MFC. In the 2nd half of 2007, I started writing C# Windows Forms and ASP.Net applications, and have since done WPF, Silverlight, WCF, web services, and Windows services.
 
My weakest point is that my moments of clarity are too brief to hold a meaningful conversation that requires more than 30 seconds to complete. Thankfully, grunts of agreement are all that is required to conduct most discussions without committing to any particular belief system.

Comments and Discussions

 
GeneralMy vote of 3 PinmemberAmitosh Swain2-Jun-13 3:03 
GeneralRe: My vote of 3 PinmemberJohn Simmons / outlaw programmer7-Jun-13 2:50 
GeneralMy vote of 5 PinmemberAbhinav S7-Nov-10 6:18 
GeneralMy vote of 5 Pinmemberlinuxjr17-Oct-10 12:10 
GeneralMy vote of 4 PinmemberMaximilien15-Oct-10 1:37 
GeneralIncrease the randomness by generating m_markers PinmvpNishant Sivakumar15-Oct-10 1:30 
GeneralRe: Increase the randomness by generating m_markers PinmemberJohn Simmons / outlaw programmer15-Oct-10 1:38 
GeneralRe: Increase the randomness by generating m_markers PinmvpNishant Sivakumar15-Oct-10 1:43 
GeneralRe: Increase the randomness by generating m_markers PinmemberJohn Simmons / outlaw programmer15-Oct-10 2:36 
GeneralRe: Increase the randomness by generating m_markers PinmvpNishant Sivakumar15-Oct-10 3:27 
GeneralNice PinmemberSimon_Whale15-Oct-10 0:08 
General[My vote of 2] Simpler system with same security PinmemberMember 209662914-Oct-10 10:56 
GeneralRe: [My vote of 2] Simpler system with same security PinmemberJohn Simmons / outlaw programmer14-Oct-10 12:40 
GeneralRe: [My vote of 2] Simpler system with same security PinmemberMember 209662914-Oct-10 13:27 
GeneralRe: [My vote of 2] Simpler system with same security PinmemberJohn Simmons / outlaw programmer14-Oct-10 23:37 
GeneralRe: [My vote of 2] Simpler system with same security PinmemberAvitevet15-Oct-10 10:02 
GeneralMy vote of 5 PinmemberMember 33316213-Oct-10 2:35 
GeneralMy vote of 5 PinmemberJonas Hammarberg11-Oct-10 10:29 
GeneralOver the top for the level of security provided PinmemberHenry.Ayoola5-Oct-10 4:26 
GeneralMy vote of 1 PinmemberHenry.Ayoola5-Oct-10 4:21 
GeneralRe: My vote of 1 PinmemberJohn Simmons / outlaw programmer5-Oct-10 4:34 
GeneralRe: My vote of 1 PinmemberHenry.Ayoola13-Oct-10 21:19 
QuestionWhy not use "classic" cryptography (RSA, D.H. key exchange, symmetric key wrapping) ? Pinmemberaccesstomypc4-Oct-10 23:58 
AnswerRe: Why not use "classic" cryptography (RSA, D.H. key exchange, symmetric key wrapping) ? PinmemberJohn Simmons / outlaw programmer5-Oct-10 1:49 
GeneralRe: Why not use "classic" cryptography (RSA, D.H. key exchange, symmetric key wrapping) ? PinmemberdzCepheus5-Oct-10 2:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 5 Oct 2010
Article Copyright 2010 by John Simmons / outlaw programmer
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid