Conventional Cryptography in the .NET Framework
Simon Thorneycroft 2002
Introduction
In general, key based cryptographic algorithms can be
divided into two types: symmetric (also known as conventional algorithms)
and asymmetric (more commonly referred to as public key algorithms).
The symmetric algorithms are so called because the same
key that is used for the encryption stage is also used in the decryption
stage. Conversely, in an
asymmetric algorithm, different – but intimately related - keys are used
for the encryption stage and the decryption stage.
The type of Cryptography chosen depends on the reason
for its use. Confidentiality (protecting data from prying eyes) is the
first application that springs to mind, however, cryptography can also be
used for authentication, integrity and non-repudiation. We can use cryptography to show a
message is from the person it purports to be from (authentication), that
it has not been tampered with since it was written (integrity) and it is
then impossible for the sender to deny that he sent the message
(Non-repudiation).
Symmetric cryptographical techniques are most often
applied to the first problem, that of confidentiality. A message encrypted with a
symmetric key should be unreadable by anyone who does not hold the same
key. Much like a briefcase
with a combination lock, if you don’t know the combination, you cannot get
at the contents. The keys
that are chosen for the algorithms can vary widely in size and the sum
collection of all the possible keys is referred to as the keyspace.
Cryptographic algorithms can be run in a number of
cipher modes, these modes are chosen based on their applicability to the
encryption task in hand.
Perhaps the simplest of these modes is Electronic Code Book mode
(ECB), in this mode a block of plain text would always encrypt to the same
ciphertext.
It is easy to see why the use of ECB may be
undesirable; if the length of the plaintext to be encrypted was
sufficiently long and contained repeated sequences a cryptanalyst (someone
wishing to break the encryption and read the message), could build a book
of blocks that represented all the possible plaintext blocks and their
encrypted counterparts. This
is not an easy undertaking however, as a separate codebook would have to
be built for each key in the keyspace (even for a 64 bit key there are
2^64 different keys and for a block size of 64 bits then each code book
would have 2^64 entries).
More worrying however is the fact that a message could
be altered. Substituting or
even deleting encrypted blocks from the ciphertext can change the message
considerably with no evidence of tampering. ‘Balance of account X is –5000
GBP’ could become ‘Balance of account X is +5000 GBP’ by placing known
blocks into the stream!
Other cipher modes may be used, such as: cipher block
chaining (CBC), cipher feedback (CFB), output feedback (OFB). In these modes the same piece of
plaintext elsewhere in the stream will encrypt to different ciphertext in
the output. For example, in
CBC mode, the ciphertext from the previous block of encrypted plaintext is
XORed with the plaintext for the current block before encrypting. This leaves the problem of what to
do with the first block of plaintext. CBC solves this by using an
initialisation vector (IV); this block can be any random data and is used
to XOR the first block of plaintext.
Since many of these modes operate on blocks that are in
excess of 1 byte, the encrypted plaintext will be of a different length to
the plaintext itself. This
has obvious implications for the final block of plaintext; it must be
padded to the block length.
This can be done with random data, but more often ciphertext
stealing is performed (CTS, using some of the encrypted plaintext to pad
the final block) or alternatively the size of the final block is appended
with some random data so that the decryption process knows what is real
plaintext and what is padding.
Symmetric Cryptography in .NET
The .NET framework class library contains a number of
classes to make the use of cryptography within your applications
simple. These classes are
located in the System.Security.Cryptography namespace (two further
namespaces within this namespace provide support for X509 certificates and
the XML encryption standard).
The base class for all the symmetric algorithms is not
surprisingly named SymmetricAlgorithm; this abstract class has four
subclasses, one for each of the symmetric algorithms that are currently
supported by the class library.
They are DES, TripleDES (a variant of the DES algorithm), RC2 and
Rijndael. The DES class and
its siblings are not implementations of the cryptographic algorithms
however, they too are abstract classes; implementing the actual algorithms
is left to the DESCryptoServiceProvider, TripleDESCryptoServiceProvider,
RC2CryptoServiceProvider and RijndaelManaged classes. The implementation classes provide
a wrapper around the Cryptographic service provider familiar to those who
have programmed using the Microsoft CryptoAPI. You cannot inherit from the
implementation classes for use in your applications (the classes are
sealed); they must be used as is.
Okay, now that we know what the classes are, let’s take
a quick look at the public methods and properties they provide.
SymmetricAlgorithm Public Properties
|
BlockSize |
Gets/ sets the
block size of the cryptographic operation in
bits. |
|
FeedbackSize |
Gets /sets the
feedback size of the cryptographic operation in
bits. |
|
IV |
Gets / sets the
initialization vector (IV) for the symmetric
algorithm. |
|
Key |
Gets/sets the
secret key for the symmetric algorithm. |
|
KeySize |
Gets / sets the
size of the secret key used by the symmetric algorithm in
bits. |
|
LegalBlockSizes |
Gets the block
sizes that are supported by the symmetric
algorithm. |
|
LegalKeySizes |
Gets the key
sizes that are supported by the symmetric
algorithm. |
|
Mode |
Gets or sets the
mode for operation of the symmetric algorithm. |
|
Padding |
Gets or sets the
padding mode used in the symmetric algorithm. |
SymmetricAlgorithm
Public Methods
|
Clear |
Releases all
resources used by the SymmetricAlgorithm. |
|
Create |
Overloaded.
Creates an instance of a cryptographic object used to perform the
symmetric algorithm. |
|
CreateDecryptor |
Overloaded.
Creates a symmetric decryptor object. |
|
CreateEncryptor |
Overloaded.
Creates a symmetric encryptor object. |
|
GenerateIV |
When overridden
in a derived class, generates a random initialization vector (IV) to be used for the
algorithm. |
|
GenerateKey |
When overridden
in a derived class, generates a random Key to be used for the
algorithm. |
|
ValidKeySize |
Determines
whether the specified key size is valid for the current
algorithm. |
The two most important properties are key and IV (the
initialisation vector), you could set just these two properties and then
use the class. To aid you in
setting these values, the class provides information on legal key sizes;
the LegalKeySizes property is an array of KeySizes classes. The KeySizes class provides a
minimum key size, a maximum key size and a legal increment (called the
skip size). A similar
situation exists with the block size property (here the LegalBlockSizes property
is an array of BlockSizes classes).
Interestingly, when you try to set the key, a check is
performed to see if the key belongs to a set of weak keys for the
algorithm in question, these are keys that have been found to be
significantly easier to break than other keys in the keyspace. If you choose one of these keys
then a Cryptographic exception is thrown. Trying to set either the IV or the
key to an invalid size will also raise an exception. If you do not wish to choose a key
yourself then you don’t have to.
When you use the class, it will call the GenerateKey and GenerateIV
methods for you, but make sure you store these values, otherwise you won’t
be able to decrypt the ciphertext.
Once you have the class set up to your satisfaction,
you can call one of two methods that return an ICryptoTransform interface,
they are CreateEncryptor and CreateDecryptor respectively. These methods are overloaded to
allow you to specify the key and initialisation vector at the same time if
you wish.
Because it is likely that you will wish to encrypt or
decrypt entire files or perhaps a stream of bytes from a network
connection, the framework class library provides a CryptoStream class that
inherits from System.IO.Stream, allowing you to call all the methods that
you could on any other stream.
A couple of words of caution though. Firstly, when encrypting, make
sure you call Close on your Cryptostream instance otherwise the member
method FlushFinalBlock (which writes the padded final block), will never
be called and the decryption step will fail. Secondly, when decrypting, do not
keep reading from the stream until you have read the same number of bytes
as the file size, this will also fail. The final block is probably padded
and therefore when decrypted will be smaller than the actual file
size.
Okay, lets sum up with two pieces of C#, one for
encrypting and the other for decrypting:
Code
Listing One: Encrypting the file
SymmetricAlgorithm
saAlgorithm = new
TripleDESCryptoServiceProvider();
ICryptoTransform
Transform;
FileStream
fsInFile = new FileStream (
sPlaintextFile,
FileMode.Open,
FileAccess.Read,
FileShare.Read
);
FileStream
fsOutFile = new FileStream
(
sEncryptedFile,
FileMode.Create,
FileAccess.Write,
FileShare.None
);
fsOutFile.SetLength(0);
Byte[] byteKey =
{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
Byte[] byteIV =
{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
saAlgorithm.Mode
= CipherMode.CBC;
Transform =
saAlgorithm.CreateEncryptor(byteKey,byteIV);
CryptoStream
cs = new
CryptoStream(fsOutFile,Transform,CryptoStreamMode.Write);
long
lFileLen = fsInFile.Length;
int
iProcessed = 0;
Byte[]
byteBuffer = new
Byte[8];
int
iRead;
while
(iProcessed < lFileLen)
{
//read from the in file and write to the out
file.
iRead =
fsInFile.Read(byteBuffer,0,8);
cs.Write(byteBuffer,0,iRead);
iProcessed +=
iRead;
Console.WriteLine("Processed {0} of
{1}",iProcessed,lFileLen);
}
cs.Close(); // calls
cs.FlushFinalBlock();
fsOutFile.Close();
fsInFile.Close();
Code
Listing Two: Decrypting the
file
SymmetricAlgorithm
saAlgorithm = new
TripleDESCryptoServiceProvider();
ICryptoTransform
Transform;
FileStream
fsInFile = new FileStream (
sEncryptedFile,
FileMode.Open,
FileAccess.Read,
FileShare.Read
);
FileStream
fsOutFile = new FileStream
(
sPlaintextFile,
FileMode.Create,
FileAccess.Write,
FileShare.None
);
fsOutFile.SetLength(0);
Byte[] byteKey =
{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
Byte[] byteIV =
{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
//saAlgorithm.Mode
= CipherMode.CBC;
CryptoStream
cs = new
CryptoStream(fsInFile,
saAlgorithm.CreateDecryptor(byteKey,byteIV),
CryptoStreamMode.Read);
long
lFileLen = fsInFile.Length;
int
iProcessed = 0;
Byte[]
byteBuffer = new
Byte[8];
int
iRead;
do
{
//read from the in file and write to the out
file.
iRead =
cs.Read(byteBuffer,0,8);
fsOutFile.Write(byteBuffer,0,iRead);
iProcessed +=
iRead;
Console.WriteLine("Processed {0} of
{1}",iProcessed,lFileLen);
}while (iRead > 0);
|