I worry about things. For instance, I collect, from a client, data that is sensitive, and I want to encrypt it. It bothers me to pass this data around my network in the clear, possibly even storing it in session data. Securing data in a distributed application is very difficult, involving several hurdles and issues. One of the common hurdles I run into when I want to encrypt data is Web Farms. If I create a key on a web server, then I have trouble load balancing subsequent requests onto different servers. The problem is that the initial web server contains the encryption key, and that is not available to other servers in the farm.
The following image shows a potential web application configuration. The client connects to a set of web servers in a web farm. The Web Servers access an internal set of Web Services that provide business functions. Additionally, the Web Servers store session data in a central database for long running transactions. Data is passed throughout this configuration, and that data can include sensitive information.
What I need is a central place that I can create and retrieve keys from. This central place needs to be accessible from any server in the web farm. It also needs to use a good encryption scheme since storing raw keys in the clear is just not a good idea. Additionally, I would like to use certificates since a certificate gives me a name to go with an asymmetric key pair. This code sample creates a central web service that can be used to create and retrieve a symmetric encryption key.
Encrypting SOAP messages and data has received a lot of attention lately. WSE 3.0 contains significant infrastructure to support the ability to sign and encrypt data between a client and a server. This sample is not a replacement for any of that functionality. Instead, it presents a methodology for storing temporary keys in a central location. This central location allows data to be encrypted on one server and passed to different servers. If a particular server needs the data, then it can retrieve the session key and decrypt the data, while servers that do not need the data are free to leave it encrypted.
The intent of the Key Manager is to provide a simple way to generate and retrieve session keys. The keys must be stored and transferred in a secure manner. Since the keys are consumed only by internal clients, I do not have to rely on standardized structures (such as PCKS). Instead, the data is stored in XML documents, and a core library is provided to allow for easy serialization.
The Key Manager creates Rijndael Keys, a symmetric block cipher. Rijndael keys were chosen because of their performance encrypting and decrypting larger blocks of data. The keys are wrapped and unwrapped with Asymmetric Keys associated with X.509 Certificates. X.509 Certificates are used because they give a readable name to a key pair. The keys could have been stored by index (or some other technique), but certificates allow for future enhancements around trust models and authentication. The current sample does not use any of these features, instead just relying on the certificate as a reference for the keys.
The KeyManager demo has the following requirements:
- Visual Studio 2005
- .NET 2.0
- SQL Server 2005 (regular or Express)
The KeyManager application relies on several infrastructure pieces. In particular, the following pieces have to be configured:
- Client and Server certificates
- SQL Server 2005 database
Both of these requirements can be configured using the standard tools that ship with Visual Studio 2005. I have listed the steps needed to perform the setup.
Certificates – MakeCert.exe
The certificates that are used in the sample were setup using the MakeCert tool available as part of Visual Studio 2005. The MakeCert tool is available if you open a Visual Studio 2005 Command Prompt. In particular, create a certificate in the user's personal (My) store. The following lines will work:
makecert -n "CN=KeyClient" -ss My
makecert -n "CN=KeyManager" -ss My
If it works, ‘Succeeded’ is returned. At least two certificates should be setup, one for the server and one for the client. The Test Client and Server can be setup on different machines.
Certificate Store Viewer
The available certificates can be viewed using Internet Explorer. There are several tools available (WSE has one), but Internet Explorer is almost always present. They can be viewed by selecting the Tools –> Internet Options menu. In the Internet Options dialog, under the ‘Content’ tab, there is a Certificates button that opens the Certificates dialog. After executing the statements above, the certificates should appear under the personal list. The certificate details can be found by selecting a certificate and clicking View. Under the details, copy the ‘Thumbnail’ property for the server KeyManager certificate, and paste it into the KeyManager Web.Config file, replacing the current setting shown here:
<add key="WrappingCert" value="1a 7d 00 57 a6 27 37 77
aa ff 48 92 34 04 32 78 e2 a9 83 38"/>
SQL Server 2005 Express Edition
A SQL Server database is used to store the wrapped keys. The database is very straightforward, containing one table and two stored procedures. Both the table and stored procedures where setup using Visual Studio 2005. The stored procedures and table layout are listed below:
CREATE TABLE [SessionKeyXml] (
[KeyId] [int] IDENTITY (1, 1) NOT NULL ,
[SessionKeyData] [Xml] NOT NULL
) ON [PRIMARY]
CREATE PROCEDURE dbo.GetSessionKey
WHERE keyId = @keyId
CREATE PROCEDURE dbo.InsertSessionKey
INSERT INTO SessionKeyXml(SessionKeyData)
Once the database has been configured, the database connection string must be copied into the web.config file of the KeyManager project. Edit the following line:
<add name="Crypto" connectionString="YOUR CONNECTION STRING"/>
The provided code contains three projects:
- The Core library
- The KeyManager Web Service
- The TestClient application
The core library contains the data structures and cryptography helper functions. It is shared between the KeyManager service and the TestClient.
The Core Library is a common library that provides both common data structures and common algorithms that can be shared between the client and the server. The library is a convenience, allowing the client and the server to not duplicate code. The main data structures defined are the following:
public class WrappedData :
public class SessionKeyInfo
KeyManager Web Service
The KeyManager web service is the main part of this project. It contains the logic to create, store, and export session keys securely. The KeyManager Web Service exposes two methods:
public SessionKeyInfo GetSessionKey(int keyId, byte rawCertData)
public SessionKeyInfo CreateSessionKey(byte rawCertData)
Both methods take the binary certificate to wrap the session key so it can be securely transmitted.
CreateSessionKey performs the following logic:
- Creates a new session key.
- Wraps the session key with the KeyManager certificate.
- Stores the wrapped session key in the database.
- Wraps the session key a second time with the requestor’s certificate.
- Returns the wrapped session key to the requestor.
GetSessionKey performs the following logic:
- Gets the wrapped session key from the database.
- Unwraps the session key using the KeyManager’s private key.
- Wraps the session key using the requestor’s certificate.
- Returns the wrapped session key to the requestor.
The Test Client is a WinForms application that can be used to exercise the functionality of the KeyManager. It allows a user to:
- Select the client certificate to use.
- Create and/or get session keys.
- Encrypt and decrypt data using the session keys.
Running multiple Test Clients will simulate different processes or machines encrypting and decrypting data. Note that the test client can run on a different machine than the Key Manager since it communicates using a Web Service, though the Key Manager endpoint does have to be configured.
The encryption and decryption performed in this sample is done using the standard .NET 2.0 library. The main classes involved are:
There are several functions that facilitate using certificates, RSA asymmetric keys, and Rijndael symmetric keys, which make working with the cryptographic functions very easy. Additionally, the new .NET 2.0 additions include several new methods that make serialization very straightforward.
Retrieving a certificate from the certificate store is accomplished using the
X509Store class. The following lines are all that is needed to open the store and allow the enumeration of the certificates:
store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
The sample finds certificates by their
Thumbnail, a property exposed on the certificate. Using the Common Name could just as easily work, but the thumbnail ensures the certificate is unique.
A binary serialized certificate is done using:
byte data =
and the binary can be turned back into a certificate using one of the constructors:
X509Certificate2 certificate =
The .NET 2.0 library includes a new ‘
ToXml’ and ‘
FromXml’ set of functions that really make serializing the asymmetric keys easy. Taking a public key from a certificate and creating the
RSACryptoServiceProvider only requires the following line:
The session keys require a bit more work, ironically, because the keys are so simple. Because no helper functions exist to facilitate exporting and importing keys, the sample uses a simple XML structure to store the session key data. The XML includes both the key data and the IV stored as B64 encoded binary. The RijndaelManaged key is generated by getting the binary data and setting the
Encryption and Decryption
The encryption and decryption use a wrapped data method. The clear text is encrypted using a temporary symmetric key. The temporary symmetric key is then encrypted using a public asymmetric key. The client (and only the original client) can decrypt the temporary symmetric key because it has access to the private asymmetric key. The following code shows the process:
public static WrappedData AsymmetricEncrypt(
RijndaelManaged symmetric = new RijndaelManaged();
byte inputBuffer = Encoding.Unicode.GetBytes(plainText);
byte encryptedData =
inputBuffer, 0, inputBuffer.Length);
byte encryptedKeyData = csp.Encrypt(symmetric.Key, true);
WrappedData wrappedData = new WrappedData();
wrappedData.IV = Convert.ToBase64String(symmetric.IV);
The decryption does just the opposite, as shown in the following code:
public static string AsymmetricDecrypt(WrappedData wrappedData)
RSACryptoServiceProvider csp =
byte clearKey = csp.Decrypt(Convert.FromBase64String(
byte iv = Convert.FromBase64String(wrappedData.IV);
byte encryptedData = Convert.FromBase64String(
RijndaelManaged symmetric = new RijndaelManaged();
byte clearData = symmetric.CreateDecryptor(
After performing the setup steps listed above, the test client can be used to verify that everything is setup correctly. Additionally, the timing required to perform the operations can be seen. If the test client is executed twice, the first instance can be used to generate a key and encrypt some clear test. The encrypted text can be copied (using the clipboard) over to the second instance. The second instance can retrieve the session key and decrypt the data. The sample screenshot at the beginning of the article did exactly this.
The certificates in this example are just used to tie a readable name to an asymmetric key pair. Future enhancements would include certificate chains and root certificate trusts to restrict the allowed clients. Currently, any client has access to the stored session keys.
The keys in the session database currently are not cleaned up. The encrypted data could be stored as part of a long running session, so it is difficult to know when the keys are no longer needed. There should be some type of key expiration, though, since the data is supposed to be temporary.
Multiple KeyManager Servers
The KeyManager can not readily be deployed to multiple servers because the wrapping certificate is not available on each machine. To support multiple key servers, the wrapping certificate and key pair would have to be imported on each server. When the certificate is created, it can be marked as ‘exportable’, allowing it to be exported from the original machine and imported into a new machine.
ASP Web Projects
I found that the Key Manager service would have trouble decrypting data because the CSP used for decryption would somehow get into a bad state. The actual error was an 'OAEP padding' failure, indicating that the key used for decryption was not the one used for encryption. For production code, this would require some investigation since startup issues are a problem, but for a sample, I just rebuilt the web project and restarted it. Once I did this, everything would magically started working (go figure).