Cryptography is all about converting plain text or clear text into cipher text through a process known as encryption. The cipher text is converted back to plain text or clear text by the opposite process called decryption:
The mathematical cryptography algorithm that performs the encryption and decryption transformations is also called a cipher and the encrypted text is called the cipher text.
Let's look at a simple example. Let's build a simple ASP.NET page.
All this ASPX page does is to display 'Some Information!' in a label server control in the Page_Load() event. Pretty simple, isn't it? Let's see what the page looks like in IE 6:
Let's use a tool called TCPTrace.exe, which is a network-sniffing tool. Microsoft also ships a similar tool called Network Monitor with the Windows Server products.
Important The TCPTrace.EXE tool can be downloaded from www.PocketSOAP.com
My web server (IIS 5.0) is running on port 80 and in order to sniff the packets sent to my web server, we've to change the port on the TCPTrace utility to 81 and the TCPTrace utility will forward all the requests received at port 81 to port 80 (to the web server). When accessing the site from IE use port 81. For example, if you're accessing your localhost then you've to access it like this: http://localhost:81/
As you can see, anyone who has access to a simple network-sniffing tool can read the information that we've transmitted over the public network. So how do you prevent this information leak? Cryptography plays a major
Why Use Cryptography?
If your computer is connected to or transmits information over an electronic network your data is visible to everyone and is available for hackers. Today, more and more companies are doing business online and this increases the security risk for the companies online business as well as the customers and partners that interact with the company. To address these problems, each and every company must take strong steps to protect their online business as well as their customers and partners.
When used properly, cryptography addresses the following problems:
Confidentiality- confidentiality assures that your information is protected.
Authentication- authentication assures that you know who is accessing your private network.
Integrity- integrity assures that information is not being tampered with during transit.
Non-repudiation- non-repudiation assures that the sender can't deny sending the message.
Cryptography provides all the services to address the security and privacy concerns of transmitting sensitive data over a public network.
Concepts of Cryptography
A cryptography algorithm is a mathematical function that transforms the readable plain text message into unreadable text garbage, and reverses the process to produce readable text from an encrypted message. All cryptographic algorithms are based on two simple principles:
Substitution- the concept of substitution is based on simply replacing every character in the message with another one. For example, when using the Caesar cipher, for a given letter in the message, shift to the right (in the alphabet) by three. That is, an 'a' becomes 'd', 'b' becomes 'e', and so on. This can be generalized to work for any number n not greater than 25 (assuming a 26 letter alphabet). In this cipher, the number n is the 'key'.
There are many varieties of the substitution ciphers available, including mono-alphabetic substitution ciphers, poly-alphabetic substitution ciphers, and perfect substitution ciphers.
Transposition- the concept of transposition is based on scrambling the characters that are in the message. Some common forms of transposition algorithm involve writing the message into a table row-by-row and reading them column-by-column. Some of the transposition algorithms such as Triple-DES perform this process three times to create the cipher text.
The math formula of cryptography is simple. When you pass the plain text message into the encryption function, the function should provide the CipherMessage, and when you pass the CipherMessage into the decryption function, it should return the original message:
Encryption(Message) = CipherMessage
And:
Decryption(CipherMessage) = Message
In the same way, the following formula should also be true:
Decryption(Encryption(Message)) = Message
In the simple encryption/decryption algorithms, the algorithm is well known, and anyone who knows the algorithm should be able to decrypt the CipherMessage. Therefore, to provide more security for the algorithms, a key is added.
We need a key to lock and unlock a lock. In the same way, a cipher is a math algorithm and a key is a sequence of bytes that is used to encrypt and decrypt the information. If we don't have the key, we can't unlock the lock. In the same way, if we lose the key we can't decrypt the encrypted data. The keys come in different sizes based on the cryptography algorithms. For example, the DES algorithm is based on 56 bits and the RC2 algorithm is based on 128 bits.
In order to encrypt or decrypt the message we have to pass the appropriate key into the function:
Encryption(Message, Key) = CipherMessage
And:
Decryption(CipherMessage, Key) = Message
In the same way, the following formula should also be true:
Decryption(Encryption(Message, Key), Key) = Message
Cryptographic Algorithms
Cryptographic algorithms can be divided into three types:
Symmetric algorithms- in symmetric cryptographic algorithms, the same key is used for encrypting and decrypting the message.
Asymmetric algorithms- in asymmetric cryptography algorithms, different keys are used for encrypting and decrypting the messages. The asymmetric cryptography algorithms are also known as public key infrastructure (or PKI).
Hashing or Message Digest algorithms- in hash cryptographic algorithms, the original text is transformed into a fixed-length cipher text. The hash algorithms also perform one-way encryption meaning the hashed cipher text can't be decrypted to its original clear text version. The fixed length of the cipher text changes based on the algorithm from 128 to 256 bits.
Symmetric Algorithms
When encrypting and decrypting the information, the same key is used for the encrypting and decrypting process in symmetric algorithms, as shown in the following figure:
With this method, the data can be transmitted in an insecure network and the receiver can decrypt the information using the same cryptography algorithm used by the sender. Of course, the key that is used to encrypt and decrypt should be kept secret if you want this method to work for you. Another problem with this approach is distributing the key to the other end where it needs to be encrypted or decrypted.
For more information about the key sizes, read the paper 'Selecting Cryptographic Key Sizes' at www.Cryptosavvy.com/Cryptosizes.pdf.
Some of the common symmetric encryption algorithms are:
DES- the Data Encryption Standard was adopted by the US Government in 1977, and by ANSI in 1981. DES follows 56-bit key for encryption and decryption. DES is a very famous algorithm but due to its small key length support, its use is very limited in today's world.
TripIe-DES- Triple-DES (or 3DES) is a very secure algorithm when compared with DES, since Triple-DES encrypts the message three times using the DES algorithm with different keys. The total key length of Triple-DES is 168 bits.
Blowfish- Blowfish is a fast, compact, and simple encryption algorithm invented by the famous author Bruce Schneier, who is the author of the celebrated book 'Applied Cryptography' (John Wiley & Sons, ISBN 0471128457). This algorithm allows a variable key length up to 448 bits.
IDEA- the International Data Encryption Algorithm (IDEA) was developed by James L. Massey and Xuejia Lai in Switzerland. Widespread use of this algorithm was hindered by several patent problems.
RC2, RC4, RC5- the RC2 and RC4 algorithms were originally developed by Ronald Rivest for RSA Security. Both RC2 and RC4 allow key lengths between 1 and 2,048 bits. On the other hand, RC5 allows a user-defined key length, data block size, and number of encryption rounds.
Rijndael (AES)- this algorithm was originally developed by Joan Daemen and Vincent Rijmen. Rijndael is a fast and compact algorithm that supports keys of 128, 192, and 256 bits in length.
Important The .NET Framework supports DES, Triple-DES, RC2 and Rijndael symmetric encryption algorithms.
Symmetric key algorithms are much faster than PKI algorithms, so they are the preferred choice for encrypting and decrypting large blocks of data. Also, they're also very easy to implement. The main disadvantage of symmetric encryption is that we need to protect the keys, and it is a challenge to exchange the keys between the encryption source and the decryption destination. The security of the algorithms is also related to the length of the key: the longer the key, the slimmer the probability of the information being decrypted. The possibility of information being decrypted is also based on the complexity of the key that you've chosen. The more complex the key, the slimmer the probability of the information being decrypted.
Asymmetric Algorithms
With asymmetric algorithms, one key encrypts the message and the other key decrypts the information as shown in the following figure:
Both the keys are different but they're related to each other. Therefore, we can publish the public key without worrying about the possibility of our encryption being compromised. This encryption is also called public-key encryption, or Public Key Infrastructure (PKI), since the public key is available publicly without compromising the integrity and security of the key or message. The decryption key is normally called the private key or secret key.
Public-key cryptography and related standards and techniques underlie security features of many products, including signed and encrypted e-mail, form signing, object signing, single sign-on, and the most popular Secure Sockets Layer (SSL)/TLS protocol.
As shown in the figure above, the public key can be freely distributed, but only you will be able to read a message encrypted using this key. When someone sends an encrypted message to you, they encrypt the message with your public key, and upon receiving the encrypted data you can decrypt it with the corresponding private key. The message encrypted with the public key can be decrypted only with the corresponding private key.
Important The PKI systems also use key exchange methods such as 'Diffie-Hellman key exchange'. The Diffie-Hellman key exchange is not an algorithm-it is a method that allows us to develop secure key exchange between two parties. The other methods are Digital Signature Standard (or DSS) and Elliptic Curve Cryptosystems.
The other way is to download the public key from an online repository. For example, if you get a client certificate from Thawte, you've an option to add your public key to the online repository. When someone wants to send something very secure all they've to do is to get the public key from you or from the online repository and sign the information with your public key and send it to you. When you receive the message, you decrypt it with your private key. This is one of the most common ways of exchanging the key.
Compared with symmetric-key encryption, public-key encryption requires more computation and is therefore not always appropriate for large amounts of data.
The .NET Framework supports two asymmetric algorithms:
DSA/DSS- Digital Signature Standard (DSS) was developed by the National Security Agency. DSS is based on the Digital Signature Algorithm (DSA), and it supports any key length.
RSA- RSA is a well-known public key algorithm developed by Ronald Rivest, Adi Shamir, and Leonard Adleman, and supports variable key length based on the implementation.
Message Digest Algorithms
Message Digest Algorithms (also known as MAC or hash algorithms) transform a variable-size input and return a fixed-size string as shown in the following figure:
The hash algorithms are also called one-way hash, since the hashed string can't be converted back to the original state-once the original clear text value has been hashed, it is not possible to get the original hashed value from the MAC.
When we hash a clear text message with a hash algorithm, it uses a key to produce the hash value. Once the hash value is generated, we can send the clear text and the hash to the other end, where the clear text value can be hashed with the same key and algorithm-the generated hash value can then be compared with the one supplied by the other end. If the values match, we can be sure that the message has not been altered in transit.
Important ASP.NET's Forms authentication module supports the one-way hash algorithms MD5 or SHA3 to authenticate the usernames/passwords stored in the config.web file. The same functionality can also be extended by storing the hashed passwords in the database using the HashPasswordForStoringInConfigFile method of the FormsAuthentication class available in the System.Web.Security namespace. For more information, have a look at 'Professional ASP.NET Security' (Wrox Press, ISBN: 1-86100-620-9).
Some of the common MAC functions are MD2, MD4, MD5, SHA, SHA-1, SHA-256, SHA-384, and SHA-512. The .NET Framework supports the following hash functions:
HMACSHA-1
MACTripleDES
MD-5
SHA-1
SHA-256
SHA-384
SHA-512
A Hash Message Authentication Code (or HMAC) function is a technique for verifying the integrity of a message transmitted between two parties that agree on a shared key.
Digital Signatures
Although encryption and decryption address a few problems, there are two important problems they don't address:
Tampering
Impersonation
Digital signatures use the one-way hashing functions for tamper detection and related authentication problems. Since the value of the hash is unique for the hashed data, any change in the data, even deleting or altering a single character, results in a different value. Moreover, the content of the hashed data cannot, for all practical purposes, be deduced from the hash. This therefore becomes the best way to detect tampering.
In public key encryption, it's possible to use the private key for encryption and the public key for decryption. Since this could create problems when encrypting sensitive information, we can digitally sign any data, instead of encrypting the data itself. Signing the data creates a one-way hash of the data, which can then be encrypted using the private key. The encrypted hash, along with other information, such as the hashing algorithm, is known as a digital signature. The following figure shows a simplified view of the way a digital signature can be used to validate the integrity of signed data:
If you look at the above figure, the sender sends the clear text (or encrypted) message with the Digital Signature. The Digital Signature is computed based on the clear text message and the clear text message is hashed using a hashing algorithm such as MD5 and the hashed value will be signed by the private key. At the other end, we'll receive the clear text message (or encrypted) with the digital signature. Then we'll compute a hash value for the clear text and compare it with the Digital Signature. If the Digital Signature verification process was successful then, we're assured that the data has not been tampered with in transit.
Cryptography Terminology
Before diving into the world of crypto coding, we need to understand some cryptographic terminology.
Block Ciphers and Stream Ciphers
Cryptographic ciphers handle data in two formats:
Block ciphers
Stream ciphers
Block ciphers are traditionally the most popular ones. A block cipher transforms a fixed-length block of plain text data into a block of cipher text data of the same length and then repeats the process until the entire message has been processed. This transformation takes place under the action of a user-provided secret key. Decryption is performed by applying the reverse transformation to the cipher text block using the same secret key. The fixed length is called the block size, and for many block ciphers, the block size is 64 bits. Typically, symmetric algorithms are based on the block cipher format. For example, the DES and RC2 algorithms use 8 bytes, 3DES uses 16 bytes, and Rijndael uses 32 bytes as input. Using this scale each algorithm splits the input into the blocks and performs the transformation.
More recent symmetric encryption algorithms are based on stream ciphers. Every stream cipher generates a keystream and encryption is provided by combining the keystream with the plain text (usually with the bitwise XOR operator). Stream ciphers can be designed to be exceptionally fast, much faster in fact than any block cipher. While block ciphers operate on large blocks of data, stream ciphers typically operate on smaller units of plain text, usually bits. The encryption of any particular plain text with a block cipher will result in the same cipher text when the same key is used. With a stream cipher, the transformation of these smaller plain text units will vary, depending on when they are encountered during the encryption process.
Padding
Block ciphers deal with blocks of bits (usually 64 bits), and the last remaining bits may not fit in a block. For example, suppose we have 136 bits of information that we are trying to encrypt using a block cipher that takes 64 bits (or 8 bytes) at a time to encrypt. The following figure shows how the 136-bits input is split into 64-bit blocks for the padding process.
In the process of encryption, the first two blocks will contain 128 bits, and the remaining eight bits will not fit in the block cipher's buffer. To address the incomplete block, padding is needed. A padding scheme will define how the last incomplete block of data will be handled in the process of encryption. The padding will be addressed in the process of decryption by removing all the padded characters and restoring the original text.
PKCS#5 (or Public Key Cryptography Standard) is one of the most famous padding schemas and it was published by RSA Security, Inc. For more information, please visit the RSA web site at http://www.rsa.com/rsalabs/pubs/PKCS/.
The .NET Framework handles padding using the PaddingMode enumeration. The PaddingMode enumeration supports three values:
None
PKCS7
Zeros
As the name suggests, when we use None, no padding is done. When PKCS7 is used, the remaining number of blocks will be filled with the remaining number of bits. For example, if six bits are free in a given byte, the last six bits will be padded with the value six. If four bits are free, the last four bits will be padded with the value four:
When PaddingMode.Zeros is used, the remaining bytes will be filled with the value zero:
Modes
The mode of a cipher determines how blocks of plain text will be encrypted into blocks of cipher text, and decrypted back. The CipherMode enumeration defines the block cipher mode to be used when performing the encryption or decryption process. You can specify the mode using the Mode property of many of the cipher algorithms. The .NET Framework supports CBC, CFB, CTS, ECB, and OFB modes.
When using ECB (or Electronic Code Book) mode, each block of plain text is encrypted to a block of cipher text. The main drawback to ECB mode is that the same plain text will always encrypt to the same cipher text when the same key is used.
The CBC (or Cipher Block Chaining) mode overcomes the drawbacks of ECB mode. When using CBC mode, each block of plain text is combined with the previous block's cipher text (using an XOR operation), which produces encrypted blocks of cipher text:
Since there is no previous cipher text block when starting the process, an initialization vector (or IV) is used for the first block of plain text.
The CFB (or Cipher Feedback) mode allows a block cipher to act like a stream cipher by processing the small increment of plain text into cipher text instead of processing it block by block. CFB mode also uses an IV to process the initial plain text.
The CTS (or Cipher Text Stealing) mode is a very versatile mode, which behaves pretty much like CBC mode. The CTS mode handles any length of plain text and produces cipher text that matches the length of the plain text.
The OFB (or Output Feedback) mode works pretty much like CFB mode; the only difference is the way that the internal buffer (shift register) is handled.
The System.Security.Cryptography Namespace
The System.Security.Cryptography namespace provides a simple way to implement security in your .NET application using the cryptography classes. Some of the cryptography classes are pure .NET managed code, and some of them are wrappers for the unmanaged Microsoft Crypto API. You can find out if the cryptography class is a managed or unmanaged code by looking at the class name. All the unmanaged providers end with the suffix CryptoServiceProvider, and all managed providers end with the Managed suffix. For example, if you look at the hashing classes such as MD5CryptoServiceProvider, SHA1Managed, and SHA256Managed, you can figure out that SHA1Managed and SHA256Managed are pure .NET managed implementations of the SHA algorithm.
The Crypto API is Microsoft's API for accessing cryptographic functions built in to the Windows platform. Microsoft recently released CAPICOM, an ActiveX wrapper around the Crypto API to simplify Crypto API programming in Visual Basic 6, but it implements only a subset of the API.
Note that the .NET Framework supports the use of strong key lengths in all encryption algorithms. However, for encryption algorithms that are implemented on top of Crypto API, you need to install a High Encryption Pack to upgrade your version of Windows:
For Windows 2000 users, Service Pack 2 includes the High Encryption Pack. It can also be obtained from the following URL: http://www.microsoft.com/windows2000/downloads/recommended/encryption/.
For Windows NT 4.0 users, Service Pack 6a includes the High Encryption Pack; this can be downloaded from: http://www.microsoft.com/ntserver/nts/downloads/recommended/SP6/allSP6.asp.
For Windows ME, Windows 98, and Windows 95 users, Internet Explorer 5.5 includes the High Encryption Pack, or you can download it from: http://www.microsoft.com/windows/ie/download/128bit/default.asp.
Cryptography Class Hierarchy
The System.Security.Cryptography namespace provides three top-level classes, SymmetricAlgorithm, AsymmetricAlgorithm, and HashAlgorithm, representing the three main areas of cryptography as shown below:
This model also brings the flexibility of extending the namespace. For example, the System.Security.Cryptography namespace doesn't currently support the Blowfish symmetric algorithm. If we wanted to add this algorithm to this namespace, all we would have to do is to derive our Blowfish algorithm class from the SymmetricAlgorithm class, and we'd get most of the standard functionality for free. The next advantage is that all the algorithm provider classes are inherited from their algorithm implementation classes. For example, the SHA1 hashing algorithm provider (the SHA1Managed class) is derived from the SHA1 hash algorithms implementation (the SHA1 class).
Hashing with .NET
The System.Security.Cryptography namespace in the .NET Framework provides several interfaces, known as Cryptographic Service Providers (CSPs), which implement a variety of hashing algorithms and make hashing simple and straightforward.
As we saw earlier, the .NET Framework implements several well-known, secure hash algorithms, including Message Digest 5 (or MD5) and Secure Hash Algorithm (or SHA). The MD5 provider generates 128-bit hash values, and the SHA provider can generate 160-bit, 256-bit, 384-bit, and 512-bit hash values. The ComputeHash() method of both the MD5 and SHA CSPs accepts a byte array or a Stream object, and returns a hash value.
The .NET Framework also implements a key-based hash, which is often used to generate digital signatures.
The HashAlgorithm Class
All the hash algorithm classes are inherited from the HashAlgorithm abstract class. The HashAlgorithm class exposes some common methods and properties that can be used across all the hashing algorithms. The following table discusses a few of them:
Class Member
Description
Hash
Returns the computed hash value in a byte array. Hash is a public property and HashValue is a protected field-both return a byte array.
HashValue
HashSize
Returns the size of the hash in bits. HashSize is a public property and HashSizeValue is a protected field-both return an integer value.
HashSizeValue
InputBlockSize
A public property that returns an integer representing the input block size in bits.
OutputBlockSize
A public property that returns an integer representing the output block size in bits.
ComputeHash()
Computes the hash value for the given input byte array or Stream. ComputeHash() is a public method that returns the output in a byte array or Stream object.
Create()
Creates an instance of the hash algorithm that is currently in use. For example, if you are using an MD5 hash algorithm then it'll create an object type of that algorithm. Create() is a static method.
TransformBlock()
Generates the hash value for a given range of the input byte array and copies the result into another byte array. TransformBlock() is a public method that returns a byte array.
TransformFinalBlock()
Generates the hash value for a given range and returns a byte array. TransformFinalBlock() is a public method that returns a byte array.
State
Returns the state of the hash computation. This property will contain a zero before the computations and a non-zero value after a successful hash computation. State is a protected field that returns an integer value representing the current state of the hash value computation.
Let's see a simple example to compute an MD5 hash. This method takes a byte array (clear text) and returns a byte array (hash value):
byte[] ComputeMD5(byte [] input)
{
MD5CryptoServiceProvider md5Provider = new MD5CryptoServiceProvider();
return md5Provider.ComputeHash(input);
}
As you can see, the MD5 provider is very easy to use. All we have to do is create a new object of the type MD5CryptoServiceProvider and pass the byte array to the ComputeHash() method, which returns a byte array. The same technique can be used for all the hash algorithms available in the .NET Framework. Let's write a simple Windows application to hash a string using multiple hash algorithms. Here is the application in action:
The implementation of the application is quite simple. We accept a string from the user and hash it using different hash algorithms. Here is the code for the Compute Hash button's Click event handler:
private void btnCompute_Click(object sender, System.EventArgs e)
{
if (txtHash.Text.Trim() != "")
{
// Generate bytes for the input string
byte[] inputData = ASCIIEncoding.ASCII.GetBytes(txtHash.Text);
// Display the hash value in textbox
txtMDS.Text = ASCIIEncoding.ASCII.GetString(new
MD5CryptoServiceProvider().ComputeHash(inputData));
txtSHAl.Text = ASCIIEncoding.ASCII.GetString(new
SHA1Managed().ComputeHash(inputData));
txtSHA256.Text = ASCIIEncoding.ASCII.GetString(new
SHA256Managed().ComputeHash(inputData));
txtSHA384.Text = ASCIIEncoding.ASCII.Getstring(new
SHA384Managed().ComputeHash(inputData));
txtSHA512.Text = ASCIIEncoding.ASCII.Getstring(new
SHA512Managed().ComputeHash(inputData));
}
}
First, we call the GetBytes() method of the ASCIIEncoding class to convert the string variable into a byte array. Then, we create a new object for each algorithm, and pass the input byte array to the ComputeHash() method. Then, we call the GetString() method of the ASCIIEncoding class again to convert the byte array into a string. Quite simple, isn't it-don't forget to add using directives for the System.Text namespace and System.Security.Cryptography namespaces to run this code.
Important If you're dealing with non-ASCII strings, you can use the UnicodeEncoding class in the System.Text namespace.
Using Hash Values for Authentication
Hashing techniques are very useful when it comes to authenticating users. We're going to see a simple authentication method for a Windows application using the MD5 algorithm. The username and the password supplied by the user will be authenticated against, for the purposes of illustration, an Access database.
Let's create an Access database called WroxDBAuth.mdb with a single table called Tbl_MA_Users. The table is going to store the user ID, e-mail address, password, first name, and last name, as shown below:
The e-mail address will be the login name for the users, and the password is stored in the database in the MD5 hash format.
This assures the users that their password can't be hacked. For example, the user's password "MyPass" is stored in the database as "{?*H!8*XJ~*N"H*S" in MD5 hash format. Let's build a simple authentication screen to authenticate users against the Access database:
Here is the code for the Login button:
private void btnLogin_Click(object sender, System.EventArgs e)
{
if (txtEmail.Text.Trim() != "" && txtPwd.Text.Trim() != "")
authenticateUser();
}
First, we check that something was entered in the username and the password text boxes. If something was entered, we call the authenticateUser() method. In the authenticateUser() method, we're connecting to the Access database and querying the table that matches the login name entered by the users.
eprivate bool authenticateUser()
{
bool bRtnValue = false;
string strConn = "PROVIDER=Microsoft.Jet.OLEDB.4.0;" +
"DATA SOURCE=c:\\DB\\WroxDBAuth.mdb;";
OleDbConnection Conn = new OleDbConnection(strConn) ;
Conn.Open();
String strSQL = "SELECT Pwd FROM Tbl_MA_Users WHERE Email = '" +
txtEmail.Text + "'";
OleDbCommand Cmd = new OleDbCommand(strSQL,Conn);
//Create a datareader, connection object
OleDbDataReader Dr = Cmd.ExecuteReader(
System.Data.CommandBehavior.CloseConnection);
//Get the first row and check the password.
if (Dr.Read())
(
Next, we pass the clear text password entered by the user into the GenerateMD5Hash() method, which returns the hashed string. If the current hashed password stored in the database and the hash generated by the GenerateMD5Hash() method are the same, we display the message Password was successful!; otherwise we displaying the message Invalid password:
if (Dr["Pwd"].ToString() == GenerateMD5Hash(txtPwd.Text))
{
MessageBox.Show(this,"Password was successful!");
bRtnValue = true;
}
else
{
MessageBox.Show(this,"Invalid password.");
}
}
else
{
MessageBox.Show(this,"Login name not found.");
}
Dr.Close();
return bRtnValue;
}
The GenerateMD5Hash() method is very simple. First, we convert the input string into a byte array using the ASCIIEncoding class. Next, we create a new object of type MD5CryptoServiceProvider and call its ComputeHash() method to generate the hash value. Then, we convert the hash value into a string and send it back to the caller:
string GenerateMD5Hash(string input)
{
// Generate bytes for the input string
byte[] inputData = ASCIIEncoding.ASCII.GetBytes(input);
// Compute the MD5 hash
MD5 md5Provider = new MD5CryptoServiceProvider();
byte[] hashResult = md5Provider.ComputeHash(inputData);
return ASCIIEncoding.ASCII.GetString(hashResult);
}
This is a simple procedure to implement, and it gives us an excellent security model for applications. In this example, we've used the MD5 algorithm. In the same way, we could use any of the other hash algorithms such as SHA1 to implement the application.
Important The only problem with this approach is that if the user wants his or her password e-mailed back to him or her, we won't be able to do it, since we can't convert the hash value back to clear text. However, we can always reset the password and send the new password back to the user.
Keyed Hash Values
The keyed hash or HMAC (Hash Message Authentication Code) algorithms are very similar to the hash algorithms, except that they generate the hash values based on a key. The HMAC algorithms are useful in the same way as the hash algorithms. For example, an HMAC value can be used to verify the integrity of a message transmitted between two parties that agree on a shared secret key. This is similar to the symmetric algorithm.
HMAC combines the original message with the key to compute a hash value. The sender computes the HMAC of the clear text and sends the HMAC with the clear text. The recipient recalculates the HMAC using the clear text and the sender's copy of the key. If the computed HMAC matches with the one sent from the other end, then the recipient knows that the original message has not been modified, since the message digest hasn't changed. In this way, the receiver can test the authenticity of the transmission. HMACs are commonly used as digital signatures.
The .NET Framework supports the HMACSHA1 and MACTripleDES algorithms. The HMACSHA1 algorithm computes keyed hash values using the SHA1 algorithm, and the MACTripleDES algorithm computes it based on the Triple-DES algorithm. We're going to see a simple example on how to use the HMAC classes. We'll build a Windows application that shows the HMAC value for the given clear text value and key.
First, we convert the input string and the key into byte arrays. Then we create an object of type HMACSHA1 and pass this object into a CryptoStream object. Then, we use the standard stream operations to read the input array and close the stream. The Hash property of the HMACSHA1 object returns the HMAC value. The same process is repeated for the MACTripleDES algorithm:
void ProcessKeyedHash(string input, string key)
{
try
{
// Generate bytes for the input string
byte[] inputData = ASCIIEncoding.ASCII.GetBytes(input);
byte[] keyBytes = new byte[16];
keyBytes = ASCIIEncoding.ASCII.GetBytes(key);
// Compute HMACSHA1
HMACSHA1 hmac = new HMACSHA1(keyBytes);
CryptoStream cs = new CryptoStream(Stream.Null, hmac,
CryptoStreamMode.Write);
cs.Write(inputData, 0, inputData.Length);
cs.Close();
txtHMACSHA1.Text = ASCII Encoding.ASCII.GetString(hmac.Hash);
// Compute the MACTripleDES
MACTripleDES macTripleDES = new MACTripleDES(keyBytes);
txtMACTripleDES.Text = ASCIIEncoding.ASCII.GetString(
macTripleDES.ComputeHash(inputData));
}
catch (Exception e)
{
MessageBox.Show(this, e.ToString());
}
}
Here is the application in action:
The only difference between these two algorithms is that the HMACSHA1 algorithm accepts keys of any size, and produces a hash value that is 20 bytes long. On the other hand, the MACTripleDES algorithm uses key lengths of 8, 16, or 24 bytes, and produces a hash value eight bytes long. If the key length is different from the requirement then an exception is thrown.
Symmetric Transformation with .NET
The System.Security.Cryptography namespace supports the DES, Triple-DES, RC2, and Rijndael symmetric algorithms. In this list, only the Rijndael algorithm is a managed implementation; the other algorithms use their counterparts in the Microsoft Crypto API.
The SymmetricAlgorithm Class
All the symmetric algorithm classes are inherited from the SymmetricAlgorithm class. The SymmetricAlgorithm class exposes some common methods and properties that can be used across all the hashing algorithms. The following table discusses a few of them:
Class Member
Description
Key
KeyValue
Specifies the secret key for the symmetric algorithm. Key is a public property and KeyValue is a protected field-both return a byte array.
KeySize
KeySizeValue
Specifies the size of the secret key in bits. KeySize is a public property and the KeySizeValue is a protected field-both return an integer value representing the length of the key in bits.
LegalKeySizes
LegalKeySizesValue
Specifies the valid key sizes in bytes for the current symmetric algorithm. LegalKeySizes is a public property and LegalKeySizesValue is a protected field-both return a KeySizes array.
IV
IVValue
Specifies the initialization vector for the symmetric algorithm. IV is a public property and IVValue is a protected field-both return a byte array.
BlockSize
BlocksizeValue
Specifies the block size in bits for the current symmetric algorithm. BlockSize is a public property and BlockSizeValue is a protected field-both return an integer.
LegalBlockSizes
LegalBlockSizesValue
Specifies the valid block size supported by the current symmetric algorithm. LegalBlockSizes is a public property and LegalBlockSizesValue is a protected field-both return a KeySizes array.
Mode
ModeValue
Specifies the mode of symmetric operation used by the current algorithm. Mode is a public property and ModeValue is a protected field-both return a CipherMode.
Padding
PaddingValue
Specifies the padding mode used by the current symmetric algorithm. Padding is a public property and PaddingValue is a protected field-both return a PaddingMode.
CreateEncryptor()
The CreateEncryptor() method creates a symmetric encryption object using the key and the initialization vector specified. CreateEncryptor() is a public method and returns an ICryptoTransform interface.
CreateDecryptor()
The CreateDecryptor() method creates a symmetric decryption object using the key and the initialization vector specified. CreateDecryptor() is a public method and returns an ICryptoTransform interface.
GenerateKey()
The GenerateKey() method generates a random key for the symmetric algorithm and overrides the value stored in the Key property. GenerateKey() is a public method and returns a random key in a byte array.
GenerateIV()
The GenerateIV() method generates a random initialization vector for the symmetric algorithm and overrides the value stored in the IV property. GenerateIV() is a public method and returns a random vector in a byte array
ValidKeySize()
Indicates whether the specified key size is valid for the current symmetric algorithm. ValidKeySizes() is a public method and returns an integer.
Let's start out by exploring the symmetric algorithms using the DES algorithm. Since symmetric algorithms tend to be faster than asymmetric ones, symmetric algorithms are good candidates for bulk encryption/decryption operations such as encrypting and decrypting entire files. We'll write a Windows application that will encrypt and decrypt files using the DES algorithm.
The user interface will provide options for locating a file using the Windows Common Dialog controls. There will be options provided to encrypt and decrypt a file with a secret key.
The Encrypt button will add a .enc extension to the source file when generating the encrypted destination file. Here is what the code looks like for the Encrypt button.
private void button1_Click(object sender, System.EventArgs e)
{
if (encryptData(encFile.Text, encFile.Text + ".enc" , txtKey.Text) == true)
MessageBox.Show(this, "Done!", "Encryption Status", MessageBoxButtons.OK,
MessageBoxIcon.Information);
else
MessageBox.Show(this, "The encryption process failed!", "Fatal Error",
MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
The Encrypt button event calls the encryptData() method, passing in the source filename, destination filename, and the secret key for the encryption operation. Let's take look at the encryptData() method.
First, we create an object type of DESCryptoServiceProvider and assign the secret key supplied by the user to the Key property. Then we call the GenerateIV() method to generate an initialization vector for the encryption operation. Next, we create the DES encryption object by calling the CreateEncryptor() method of the DESCryptoServiceProvider class. After that, we instantiate two FileStream objects, one in read mode (the source file), and the other in write mode (the destination file) to encrypt the file:
// The encryptData method will encrypt the given file using the DES algorithm
public bool encryptData(string sourceFile, string destinationFile,
string cryptoKey)
{
try
{
// Create the DES Service Provider object and assign the key and vector
DESCryptoServiceProvider DESProvider = new DESCryptoServiceProvider();
DESProvider.Key = ASCIIEncoding.ASCII.GetBytes(cryptoKey);
DESProvider.GenerateIV();
ICryptoTransform DESEncrypt = DESProvider.CreateEncryptor();
// Open the source and destination file using the file stream object
FileStream inFileStream = new FileStream(sourceFile,
FileMode.Open, FileAccess.Read);
FileStream outFileStream = new FileStream(destinationFile, FileMode.Create,
FileAccess.Write);
The initialization vector (or IV) is always used to initialize the first block of plain text for encryption. We‘ve already talked about this in the Modes section.
Once we've created these objects, we need a CryptoStream object to which we write the encrypted file. We pass the DES encryption object and the output file stream into the CryptoStream's constructor. Then we read the content of the input file and write it back into the CryptoStream. Then, we close all the stream objects and return true:
// Create a CrytoStream class and write the encrypted out
CryptoStream CryptoStream = new CryptoStream(outFileStream, DESEncrypt,
CryptoStreamMode.Write);
// Declare the byte array of the length of the input file
byte[] bytearrayinput = new byte[inFileStream.Length - 1];
// Read the input file stream in to the byte array and write
//it back in the CryptoStream
inFileStream.Read(bytearrayinput, 0, bytearrayinput.Length);
CryptoStream.Write(bytearrayinput, 0, bytearrayinput.Length);
// Close the stream handlers
CryptoStream.Close();
inFileStream.Close();
outFileStream.Close();
return true;
}
catch (Exception e)
{
MessageBox.Show(this, e.ToString(), "Encryption Error",
MessageBoxButtons.OK, MessageBoxIcon.Stop);
return false;
}
}
The decryption process does the opposite of the encryption process. Let's look at the Decrypt button code. First, we retrieve the original filename from the selected one by removing the .enc extension from the filename. Then we call the decryptData() method with the source, destination, and the secret key.
private void button2_Click(object sender, System.EventArgs e)
{
string decFileHame = decFile.Text.Replace(".enc", "");
if (decryptData(decFile.Text, decFileName, txtKey.Text) == true)
MessageBox.Show(this, "Done!", "Decryption Status", MessageBoxButtons.OK,
MessageBoxIcon.Information);
else
MessageBox.Show(this, "The decryption process failed!", "Fatal Error",
MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
The decryptData() method works in a very similar way to encryptData(). We create an object of type DESCryptoServiceProvider and assign the key to it. Then, we generate a new VI and create a new DES decryption object by calling the CreateDecryptor() method. Next, we read the source file into the CrytoStream and transform the content into a new file. Finally, we close the stream objects and return true:
// The decryptData method will decrypt the given file using the DES algorithm
public bool decryptData(string sourceFile, string destinationFile,
string cryptoKey)
{
try
{
// Create the DES Service Provider object and assign the key and vector
DESCryptoServiceProvider DESProvider = new DESCryptoServiceProvider();
DESProvider.Key = ASCIIEncoding.ASCII.GetBytes(cryptoKey);
DESProvider.GenerateIV();
FileStream DecryptedFile = new FileStream(sourceFile, FileMode.Open,
FileAccess.Read);
ICryptoTransform desDecrypt = DESProvider.CreateDecryptor();
CryptoStream cryptostreamDecr = new CryptoStream(DecryptedFile, desDecrypt,
CryptoStreamMode.Read);
StreamWriter DecryptedOutput = new StreamWriter(destinationFile);
DecryptedOutput.Write(new StreamReader(cryptostreamDecr).ReadToEnd());
DecryptedOutput.Flush();
DecryptedOutput.Close();
return true;
}
catch (Exception e)
{
MessageBox.Show(this, e.ToString(), "Decryption Error",
MessageBoxButtons.OK, MessageBoxIcon.Stop);
return false;
}
}
Using Other Symmetric Algorithms
Since all the symmetric algorithms are derived from the SymmetricAlgorithm class, it is very easy to implement the encryption/decryption process with the previous code base. For example, if you want to use the RC2, Triple-DES, or Rijndael algorithm, all you've to do is replace the following declaration in the encryptData() and decryptData() methods with the appropriate declarations shown below:
DES
DESCryptoServiceProvider DESProvider = new DESCryptoServiceProvider();
RC2
RC2CryptoServiceProvider RC2Provider = new RC2CryptoServiceProvider();
Triple-DES
TripleDESCryptoServiceProvider tDESProvider = new
TripleDESCryptoServiceProvider();
Rijndael
RijndaelManaged RijndaelProvider = new RijndaelManaged();
If you make this change, our file encrypter/decrypter application will work fine.
The success of the symmetric encryption and decryption process is based on the key value. If you don't supply a proper key length to the algorithm, then a CryptographicException will be raised. The key sizes supported by the algorithm can be fetched by accessing the LegalKeySizes property. This property returns an array of KeySizes objects. The KeySizes class has three integer public properties -MaxSize, MinSize, and SkipSize. The MaxSize and MinSize properties specify the maximum key size (in bits) and the minimum key size (in bits) respectively. The SkipSize returns the interval between the valid key sizes in bits.
The following table lists the key sizes supported by the major algorithms:
Algorithm
Key Size
DES
64 bits or 8 bytes
RC2
128 bits or 16 bytes
Triple-DES
192 bits or 24 bytes
Rijndael
256 bits or 32 bytes
The strength of the encryption is also based on the key. The larger the key, the better the encryption. Thus the likelihood of a hacker being able to decrypt the data with a brute-force attack is decreased. However, there is one more constraint that we should remember-the bigger the key, the more time required for the encryption and decryption process.
Asymmetric Transformation with .NET
As we discussed earlier in the chapter, asymmetric algorithms are based on the concept of public and private keys (PKI). The System.Security.Cryptography namespace supports two asymmetric algorithms: RSA and DSA.
The AsymmetricAlgorithm Class
Both the RSA and DSA algorithms inherit from the base class AsymmetricAlgorithm. The AsymmetricAlgorithm class exposes some common methods and properties that can be used across all the hashing algorithms:
Class Member
Description
KeySize
KeySizeValue
Specifies the size of the key modules in bits. KeySize is a public property and KeySizeValue is a protected field-both return an integer value representing the length of the key in bits.
LegalKeySizes
LegalKeySizesValue
Specifies a valid key size in bytes for the current asymmetric algorithm. LegalKeySizes is a public property and LegalKeySizesValue is a protected field-both return a KeySizes array.
KeyExchangeAlgorithm
Specifies the key exchange algorithm used when communicating between two ends and the way the public key and private key will be exchanged. This is a public property that returns a string representing the name of the key exchange algorithm used.
SignatureAlgorithm
Specifies the name of the algorithm used to sign the current object. This is a public property that returns a string representing the name of the signature algorithm used.
FromXmlString()
Reconstructs an Asymmetric object from an XML file. This is a public method that takes a string as input.
ToXmlString()
Returns an XML representation of the current algorithm object. This is a public method that returns a string.
As we've already discussed, many of the cryptography algorithms are implemented on top of the Crypto API library. The .NET Framework wraps the Crypto API library with sets of managed classes, called Cryptographic Service Providers (or CSPs). The CspParameters class is used to send values to and receive values from the unmanaged Crypto API.
The Cryptographic Service Providers are plug-ins for the Crypto API. These plug-ins are encryption engines that perform the encryption/decryption process.
The CSP operation is based on an enumeration value called CspProviderFlags. The CspProviderFlags enumeration supports two values: UseDefaultKeyContainer and UseMachineKeyStore. If the UseDefaultKeyContainer option is specified, the key information is read from the default key container. If the UseMachineKeyStore option is specified, the key information is read from the computer's key container.
CSPs maintain a database to store public/private key pairs. Some CSPs maintain their key container in the Registry, whereas others maintain it in other locations, such as in Smart Cards or encrypted, hidden files.
Using the RSA Algorithm
The RSA algorithm is implemented in the RSACryptoServiceProvider class, which inherits from the RSA class. The RSA class inherits from the AsymmetricAlgorithm class. The RSA algorithm allows us to encrypt, decrypt, sign data with a digital signature, and verify the signature. We'll look at these one by one in this section.
Let's start with a simple encryption/decryption approach. Since the RSA algorithm is an asymmetric algorithm, it is slower than its symmetric counterpart. Therefore, the RSA algorithm is best for small amounts of message encryption. Accordingly, we'll build a Windows application that will encrypt and decrypt the message entered by the user using PKI technology.
Whenever you create a new default constructor instance of the RSACryptoServiceProvider class, it automatically creates a new set of public/private key information, ready to use. We can also store the PKI values into XML files. This demonstration will be our first example. Let's build a UI as shown below.
This UI allows us to see the clear text as well as the encrypted cipher text. Our code will also show the public key and the private key used in this process and we'll have an option to store the public and private keys in different XML files.
Since we're going to use the RSA auto generated public/private keys, we declare a class level static object.
static RSACryptoServiceProvider objRSAProvider;
Here is the code for the Encrypt button:
if (txtClearText.Text.Trim() != "")
{
// Initialize the RSA Cryptography Service Provider (CSP)
rsaProvider = new RSACryptoServiceProvider();
UTFSEncoding utf8 = new UTFSEncoding();
byte[] clearText = utf8.GetBytes(txtClearText.Text.Trim());
// Encrypting the data received
txtCipherText.Text = Convert.ToBase64String(rsaProvider.Encrypt(clearText,
false));
// Show the private key
txtPrvKey.Text = rsaProvider.ToXmlString{true);
// Show the public Key
txtPubKey.Text = rsaProvider.ToXmlString(false);
}
We create a new object type of RSACryptoServiceProvider. Then we transform the user-entered message into a byte array using the UTF8Encoding class. Then we call the Encrypt() method of the RSACryptoServiceProvider class, pass the input byte array and a second parameter of false.
The second parameter in the Encrypt() method deals with the mode of operation. If you are running Windows 2000 OS with SP2 or higher then you can set this parameter to true, which will use the OAEPpadding method. When set to false it'll use PKCS version 1.5.
The Encrypt() method returns an encrypted byte array. We're using the ToBase64String() method of the Convert class to convert the byte array into a string for display in the textbox. We also display the private/public key generated by the RSA algorithm in two text boxes using the ToXmlString() method of the RSACryptoServiceProvider class. ToXmlString() takes a Boolean as input and if the value is false, it generates the public key value as an XML string; if the value is true, it generates the private key.
Here is the code for our Decrypt button. This method just calls the Decrypt() method of the RSACryptoServiceProvider object:
private void btnDecrypt_Click(object sender, Systern.EventArgs e)
{
if (txtCipherText.Text.Trim() ! = "")
{
//Convert the input string into a byte array
byte[] bCipherText =
Convert.FromBase64String(txtCipherText.Text.Trim());
//Decrypt the data and convert it back to a string
string strValue =
ASCIIEncoding.ASCII.GetString(objRSAProvider.Decrypt(
bCipherText, false));
//Display the decrypted string in a MessageBox
MessageBox.Show(this, strValue, "Decrypted value",
MessageBoxButtons.OK,MessageBoxIcon.Information);
}
}
Now let's look at saving the public/private key into an XML file. We display the SaveFile common dialog box to get the desired filename from the user. Then we load the XML data into an XmlDocument object and call its Save() method to save the data to disk:
private void btnSave_Click(object sender, System.EventArgs e)
{
SaveFileDialog saveFileDialog1 = new SaveFileDialog();
saveFileDialogl.Filter = "XML files (*.xml)|*.xml|All files(*.*)|*.*" ;
saveFileDialog1.FilterIndex = 2 ;
saveFileDialog1.RestoreDirectory = true ;
if(saveFileDialog1.ShowDialog() == DialogResult.OK)
{
// Write the content to an XML file
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(this.txtPubKey.Text);
// Save the document to a file.
xmlDoc.Save(saveFileDialog1.OpenFile());
}
}
Here is the application in action:
As you can see, the clear text, encrypted cipher text, and the decrypted clear text is shown on the screen with the public and private key information.
Loading the Public and Private Keys
In the previous example, we saw how to encrypt and decrypt data using the auto-generated public and private keys. We also saw how to save the keys into an XML file. However, if you want to reuse previously created or saved keys, you can do this by initializing the class with a populated CspParameters object. Let's see an example of this. Create a UI like the previous one, but this time we'll load the public and private keys from the XML files:
Here is the code to load a key from XML. First, we display an OpenFile dialog to get the filename from the user. Then, we load the file into an XmlTextReader object and display the key information in the appropriate text box:
private void btnLoadPub_Click(object sender, Systern.EventArgs e)
{
// Show the open file dialog
openFileDialog1.Title = "Select the Public Key file";
openFileDialog1.Filter = "XML Files (*.xml)|*.xml";
if(openFileDialog1.ShowDialog() == DialogResult.OK)
{
string fileName = openFileDialog1.FileName;
btnEncryptl.Enabled = true;
// Load the document
Xml Text Reader xmlReader = new XmlTextReader(fileName);
xmlReader.WhitespaceHandling = WhitespaceHandling.None;
xmlReader.Read();
// Assign the public key to the textbox
txtPubKeyl.Text = xmlReader.ReadOuterXml();
}
}
Now let's look at the code behind the Encrypt button. First, we create an object of the CspParameters class and set the flag to use the machine store to look for the PKI keys. Then we give the key container the name WroxRSAStore.
private void btnEncryptl_Click(object sender, System.EventArgs e)
{
if (txtClearText1.Text.Trim() != "")
{
try
{
CspParameters cspParam = new CspParameters();
csp Param.Flags = CspProviderFlags.UseMachineKeyStore;
cspParam.KeyContainerName = "WroxRSAStore";
cspParam.ProviderName = "MS Strong Cryptographic Provider";
// CryptoAPI constant -> PROV_RSA_FULL = 1
// This provider type supports both digital
// signatures and data encryption, and is considered
// general purpose. The RSA public-key algorithm
// is used for all public-key operations.
cspParam.ProviderType = 1;
Once we've initialized the parameters for the CSP, we create a new object of type RSACryptoServiceProvider using our CspParameters object. Then, we assign the public key by calling the FromXml String() method. After that, we perform our usual process of converting the string into a byte array and passing the byte array into the Encrypt() method and converting the byte array back to a string:
// Initializing the RSA Cryptography Service Provider (CSP)
RSACryptoServiceProvider rsaProvider1 = new
RSACryptoServiceProvider(cspParam);
// Load the public key
rsaProvider1.FromXmlString(txtPubKey1.Text);
UTF8Encoding utf8 = new UTF8Encoding();
byte[] clearText = utf8.GetBytes(txtClearText1.Text);
// Convert encrypted text to base64
txtCipherText1.Text = Convert.ToBase64String(
rsaProvider1.Encrypt(clearText, false));
}
catch (Exception e)
{
MessageBox.Show(this, e.ToString());
}
}
}
The decryption method is again very simple-we just create an RSA CSP object and assign the private key to it. We then decrypt the message using the Decrypt() method:
private void btnDecrypt1_Click(object sender, System.EventArgs e)
{
if (txtClearText1.Text.Trim() != "")
{
// Initialize the RSA Cryptography Service Provider (CSP)
RSACryptoServiceProvider rsaProvider1 = new RSACryptoServiceProvider();
// Load the private key
rsaProvider1.FromXmlString(this.txtPriKey1.Text);
// Encrypt the data received
MessageBox.Show(this, ASCIIEncoding.ASCII.GetString(
rsaProvider1.Decrypt(Convert.FromBase64String(
txtCipherText1.Text.Trim()), false)), "Decrypted value",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
Here is the application in action:
There is one limitation of the RSA algorithm that you should know-the Encrypt() method can only encrypt up to 16 bytes if the High Encryption pack is installed. Otherwise, it can only encrypt 5 bytes.
Reading an X509 Certificate
A certificate is like a 'voucher' that contains information about the person holding the voucher, such as who authorized the certificate, its public keys, and its expiration information. Certificates are signed by a certifying authority (or CA), such as VeriSign or Thawte. The Microsoft Certificate Server also allows us to create 'self-signed' certificates. However, they may not be trusted by the rest of the world, since this is not issued by a well-known CA, but they are very useful in an intranet scenario.
Server certificates are used to identify the trustworthiness of the server, and client certificates to identify a client to the server. A CA issues both client and server certificates after verifying the identity. For example, when the client is requesting a web resource, it can also send a client certificate along with the request. The server can then determine who the client is and authorize or deny them.
Thawte provides the client certificate free of charge but VeriSign charges a fee for it.
Client certificates are usually installed on web clients such as browsers and e-mail clients. You can view all the client certificates installed in IE by clicking on Tools | Internet Options… and selecting the Content tab in the dialog box. Click on the Certificates… button here. As you can see, | have two client certificates installed in IE 6:
The Cryptography namespace also contains the child namespace X509Certificates. This contains just three classes used to represent and manage Authenticode X509 v.3 certificates. The X509Certificate class exposes the static methods CreateFromCertFile() and CreateFromSignedFile() to create an instance of the certificate.
The CreateFromCertFile() method reads the content of the X509 certificate from a certificate file and the CreateFromSignedFile() method reads the content of the X509 certificate from a Digitally Signed file. We will use the CreateFromCertFile() method to read the contents of the X509 client certificate:
private void btnView_Click(object sender, System.EventArgs e)
{
// Read the client certificate from the file
// into the object variable of the type X509Certificate
X509Certificate clientCert =
X509Certificate.CreateFromCertFile("C:\\test.cer");
StringBuilder sb = new StringBuilder();
sb.Append("Issuer Name: " + clientCert.GetIssuerName() + "\n");
sb.Append("Public Key String: " + clientCert.GetPublicKeyString() + "\n");
sb.Append{"Key Algorithm: " + clientCert.GetKeyAlgorithm().ToString() + "\n");
sb.Append("Serial Number: " + clientCert.GetSerialNumberString() + "\n");
sb.Append("Effective Date: " +
clientCert.GetEffectiveDateString().ToString() + "\n");
sb.Append("Expiration Date: " +
clientCert.GetExpirationDateString().ToString() + "\n");
MessageBox.Show(this, sb.ToString());
}
We've declared an object of type X509Certificate, and we've used the CreateFromCertFile() method to read the certificate content into the object. Then we've read the main properties of the certificate and disolaved it to a message box:
Cryptography and Network Programming
So far in this chapter we've learnt all about cryptography-now it's time to use some of the techniques in network programming. Remember the simple UDP chat utility that we wrote in Chapter 6? The UDP chat application is very simple. It takes the local and remote port numbers and the IP address and passes the information back and forth.
Here is the code before adding any cryptography algorithms. The Send() method here gets the datagram, converts it into a byte array, and sends the data using the Send() method of the UdpClient class. The Receiver() method here also works in the same way.
private static void Send(string datagram)
{
// Create UdpClient
UdpClient sender = new UdpClient();
// Create IPEndPoint with details of remote host
IPEndPoint endPoint = new IPEndPoint(remoteIPAddress, remotePort);
try
{
// Convert data to byte array
byte[] bytes = Encoding.ASCII.GetBytes(datagram);
// Send data
sender.Send(bytes, bytes.Length, endPoint);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
finally
{
// Close connection
sender.Close();
}
}
public static void Receiver()
{
// Create a UdpClient for reading incoming data.
UdpClient receivingUdpClient = new UdpClient(localPort);
// IPEndPoint with remote host information
IPEndPoint RemoteIpEndPoint = null;
try
{
Console.WriteLine(
"-----------*******Ready for chat!!!*******-----------");
while(true)
{
// Wait for datagram
byte[] receiveBytes = receivingUdpClient.Receive(
ref RemoteIpEndPoint);
// Convert and display data
string returnData = Encoding.ASCII.GetString(receiveBytes);
Console.WriteLine("−" + returnData.ToString());
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString ());
}
}
Let's use the Rijndael symmetric algorithm to send secure information between two chat clients. The Rijndael algorithm is a symmetric algorithm and it is best for bulk data transfer. This is the main reason that I've chosen this algorithm. We're going to add encryptData() and decryptData() methods to this class to take care of the secure communication process. When we're sending information to the remote socket we'll call the encryptData() method to encrypt the data, and then we'll pass the encrypted byte array to the other end. When we receive the encrypted byte array on the other end, we'll call the decryptData() method to decrypt the byte array.
As you can see, our design is very simple. To support the symmetric algorithms, we've added two private class level members that store the shared key and vector.
private static IPAddress remoteIPAddress;
private static int remotePort;
private static int localPort;
private static UTF8Encoding Utf8Encod;
private static string CryptoKey = "!i~6ox1i@]t2K'y$";
private static string CryptoVT = "!~x7Oq{6+q1@#VI$";
The encryptData() method takes a string as input and returns a byte array.
static byte[] encryptData(string theDataGram)
{
byte[] bCipherText = null;
try
{
We create a new object type of RijndaelManaged, and assign the shared key and vector. Then we create an ICryptoTransform object using the CreateEncryptor() method.
// Create the Rijndael Service Provider object and assign the
// key and vector to it
RijndaelManaged Rijndael Provider = new RijndaelManaged();
Rijndael Provider.Key = Utf8Encod.GetBytes(CryptoKey);
RijndaelProvider.IV = Utf8Encod.GetBytes(CryptoVI);
ICryptoTransform RijndaelEncrypt = Rijndael Provider.CreateEncryptor();
Now, we convert the datagram into a byte array using the UTF8Encoding class. Then we declare a MemoryStream object and use a CryptoStream object to perform the cryptographic transformation- you may recall that we had a fleeting look at the CryptoStream class in Chapter 2.
// Convert string to byte array
byte[] bClearText = Utf 8Encod.GetBytes{theDataGram);
MemoryStream Mstm = new MemoryStream();
//Create Crypto Stream that transforms a stream using the encryption
CryptoStream Cstm = new CryptoStream(Mstm, RijndaelEncrypt,
CryptoStreamMode.Write);
//Write out encrypted content into MemoryStream
Cstm.Write (bClearText, 0, bClearText.Length);
Ctms.FlushFinalBlock();
We create the byte array back from the MemoryStream and return the byte array to the caller.
//Get the output
bCipherText = Mstm.ToArray();
//Close the stream handlers
Cstm.Close();
Mstm.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString ());
}
return bCipherText;
}
The decryptData() method, which does the decryption process, is similar to the encryptData() method. The decryptData() method takes a byte array as input and returns a string as output.
static string decryptData(byte[] bCipherText)
{
string sEncoded ="";
try
{
// Create the RijndaelManaged Service Provider object and assign
// the key and vector to it
RijndaelManaged RijndaelProvider = new RijndaelManaged();
RijndaelProvider.Key = Utf8Encod.GetBytes(strCryptoRey);
RijndaelProvider.IV = Utf8Encod.GetBytes(strCryptoVI);
ICryptoTransform RijndaelDecrypt= RijndaelProvider.CreateDecryptor();
//Create a MemoryStream with the input
MemoryStream Mstm = new MemoryStream(bCipherText, 0, bCipherText.Length);
//Create Crypto Stream that transforms a stream using the decryption
CryptoStream Cstm = new CryptoStream(Mstm, RijndaelDecrypt,
CryptoStreamMode.Read);
// read out the result from the Crypto stream
StreamReader Sr = new StreamReader(Cstm);
sEncoded = Sr.ReadToEnd();
Sr.Close();
Cstm.Close();
Mstm.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString ());
}
return sEncoded;
}
Let's call the encryptData() and decryptData() methods in the proper places in the chat application. The Send() method calls the encryptData method, with the string entered by the user, and sends the result via the Send() method of the UdpClient. The Receiver() method passes the received byte array into the decryptData() method and displays the decrypted message.
private static void Send(string datagram)
{
...
try
{
// Convert string to byte array
//byte[] bClearText = Utf8Encod.GetBytes(datagram);
// Encrypting the data recived
byte[] bytes = encryptData(datagram);
// Send data
sender.Send(bytes, bytes.Length, endPoint);
}
...
}
public static void Receiver()
{
...
while(true)
{
// Wait for datagram
byte[] receiveBytes = receivingUdpClient.Receive(ref RemoteIpEndPoint);
//Decrypt the incoming byte array
string returnData = decryptData(receiveBytes);
Console.WriteLine("−" + returnData.ToString());
}
...
}
We can also implement this with an asymmetric algorithm such as RSA-let's see an example of this. We'll just rewrite the encryptData() and decryptData() methods using the RSA algorithm. The rest of the implementation will remain the same.
Before writing the encryptData() and decryptData() methods we've to store the public key and the private key in private class level variables.
private static IPAddress remoteIPAddress;
private static int remotePort;
private static int localPort;
private static UTF8Encoding Utf8Encod;
private static string PubKey =
"
e8g/yby624K85qJLwMMzwCru7b+kNTA2dYaK4Nk+FkZMLCVmomiW1zns2KsT1aF9hwr32Nyje3OuJDlHqB
tcOpCGbo+kJ+JC88BM1J9AkdoAa+SE=
private static string PriKey =
"
e8g/yby624K85qJLwMMzwCru7b+kNTA2dYaK4Nk+FkZMLCVmomiW1zns2KsT1aF9hwr32Nyje3OuJ01HqB
tcOpCGbo+kJ+JC88BM1J9AkdoAa+SE=
3BoisxTvnh8Xt
g/02fTGtr/k8OXUOiEfKwAKzWje36v8zkTfIc4EzdZbRskJywq1NMo9U1EHM3DUv+Ya/KGPzQ==
0AcCph/CdQeB2/M+q3BSlzimr9Chw9zaHk1x8MBCHdRB9c26VcS0AmKW+G4Vz jWJjI6cK8j/GQjhnRn7Ub
BypQ==
GwcmBIAHa1hez2yJTQ==
guEeFYjp2RIatLMrcA4QV4KV3+DzQWaeQ==
yhKD7J5zLGIzz+71C5QjVi2dRwtvjGJaexOTi+TRIv2fT/LhWmsCDQ==
1gXY8AznfnLC05eXrDIuo/YBsY2qredFDQaLqWIZiiq4ur7kWoFHakAbHCGeC3p2+bmLyrYr2nm8OgjOc1
NUneE8ASoKWfnbcWxW377Oeogj16frPUoAgwU1gFURdTxozgNLThVtNItrc3Doa5eJ+U7pRSz2edE=
Here is how the encryptData() and decryptData() methods look. In the encryptData() method we create a new CspParameters object, and use the object to create a new RSA algorithm object. Next, we assign the public key from the private class member. Then, we call the Encrypt() method of the RSA object.
static byte[] encryptData(string strDataGram)
{
CspParameters theCspParam = new CspParameters();
theCspParam.Flags = CspProviderFlags.UseMachineKeyStore;
theCspParam.KeyContainerName = "WroxRSAStore";
// Initializing the RSA Cryptography Service Provider (CSP)
RSACryptoServiceProvider theRSAProvider = new
RSACryptoServiceProvider(objCspParam);
//Set the Load the public key
theRSAProvider.FromXmlString(PubKey);
// Convert string to byte array
byte[] bClearText = Utf8Encod.GetBytes(strDataGram);
// Encrypting the data received
byte[] bytes = theRSAProvider.Encrypt(bClearText, false);
theRSAProvider.Clear();
return bytes;
}
The decryptData() method does pretty much the same thing. It creates a new RSA object and assigns the private key from the class member. Then it calls the Decrypt() method and sends the string back to the caller.
static string decryptData(byte[] bCipherText)
{
// Initializing the RSA Cryptography Service Provider (CSP)
RSACryptoServiceProvider theRSAProvider = new RSACryptoServiceProvider();
//Load the private key
theRSAProvider.FromXmlString(PriKey);
// Encrypting the data received
string strRtnData = Utf8Encod.GetString(theRSAProvider.Decrypt(bCipherText,
false));
theRSAProvider.Clear();
return strRtnData;
}
This implementation of the encryptData() and decryptData() methods can be replaced with the previous example and the code will work fine. The only thing that we've to be careful about in this example is the size limitations of the Encrypt() method-we talked about these limitations earlier.