|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Introduction: .NET Cryptography.NET has very good support for cryptography as part of the
When implementing a cryptography provider for .NET the best way to organize
it to implement the interfaces of The CBC Stream Cipher LibraryEverything started when I found an AES [FIPS197] implementation in C# in the MSDN magazine [CSAES]. AES is symmetric block cipher algorithm, which means that the same key is use for encryption and decryption. The C# code of [CSAES] is easy to understand but the implementation is very slow for some reason, even if you replace polynomials multiplication with fixed tables as I did. A faster implementation of AES in C, which is freely available, can be found in [CAES]. To use the AES block cipher implementation for real encryption you have to create a stream cipher. The easiest way is to create an ECB (Electronic Codebook) stream cipher which basically encrypts each block of a stream using the block cipher. The ECB mode is also supported in my implementation because it could be useful for testing purposes. However it is not secure because the correlation patterns found in the plaintext will still be visible in the ciphertext. The CBC (Cipher Block Chaining) mode is safe from this weakness and it is relatively easy to implement. It basically XOR-s each plaintext block with the previous ciphertext block before encrypting it. This way the plaintext patterns are no more visible in the output. The CBC mode makes use of an initialization vector (IV) to XOR the first block. The IV does not need to be secret as it shows nothing about the data (plain or cipher) or about the key. The IV must be selected to be different (it has not to be necessarily random) for each stream (file) we encrypt with a given key. In the CBC implementation given here the IV size must be the same as the size of cipher block, which is always 16 bytes for AES. The CBC mode is the default mode for my stream cipher. Any block cipher implementation can used with the library as long as there is a wrapper class that implementsIBlockCipher interface
shown below: interface IBlockCipher {
void InitCipher(byte[] key); // key.Length is keysize in bytes
void Cipher(byte[] inb, byte[] outb);
void InvCipher(byte[] inb, byte[] outb);
int[] KeySizesInBytes();
// iv length will/must be the same as BlockSizeInBytes
int BlockSizeInBytes();
}
I wrote two wrappers that implement this interface: one for the C# AES implementation [CSAES] and another for the fast C AES DLL implementation [CAES]. This way these two algorithms can be use with my CBC stream cipher. See the ‘aes.CAes.cs’ in the code for an example how a wrapper looks like. Using the codeThe C# stream cipher library can be used as follows: IBlockCipher ibc = new CAes(); // new Aes();
byte[] key = ... ;
byte[] iv = ... ;
StreamCtx ctx = StreamCipher.MakeStreamCtx(ibc, key, iv);
The Note that the Stream instr = ... ; // open plaintext stream
Stream outstr = ... ; // create a stream for ciphertext output
StreamCipher.Encrypt(ctx, instr, outstr);
It is recommend to buffer streams before passing them to the
byte[] indata = ... ;
byte[] outdata = StreamCipher.Encode(ctx, indata, StreamCipher.ENCRYPT);
Decryption is done similarly with respective methods. See ‘aes-example.cs’ for a complete example. Key GenerationOne problem we skipped above it how to get a key. Any byte array with size
equal to one of the values returned in
On the other hand, no sane human can remember a 16 or 32 byte key such as 8ea2b7ca516745bfeafc49904b496089 without writing it somewhere. However, most people will find physically storing a secret key somewhere, even when it is encrypted, not a very feasible solution. They would prefer better a form of key which is easy to remember, usually in the form of a string password (or passphrase). Password based cryptography [PBCS] is weaker that using keys directly, given that the search space of a password is smaller than for a key. For example with a 256 bit key in AES there are 2^256 (read as: 2 in the power of 256) possibilities to search in the worst case. A keyboard contains approximately 2^6 unique keys (letters, capital letters, numbers, special characters) that can be use in a password. This means that for a truly random password of 20 characters long (which we will find too complicated to remember), the brute force search space is only (2^6)^20 = 2^120 in the worst case. This is even lower that the smallest key size for AES which is 2^128. So if you use a password encryption scheme with passwords of 20 characters it makes no sense to use a key bigger than 128 bit for AES. In reality the length of the password is not known which means that for passwords up to 20 characters the search space size can be calculated as follows: sum(for i = 0 to 20) of (2^6)^i, which is again approximately the same as its biggest term value, that is 2^120. Another problem with passwords is that people tend to reuse them. That is we prefer to (re)use the same password to encrypt different data units (files). This means that we are using the same key to encrypt a large amount of plaintext, resulting in a large amount of ciphertext which in theory can be used to explore patterns and find the cipher key (not the password) and obtain the plaintext. So, it is better to use different keys to encrypt different data units, which seem to contradicts the idea of having a single reusable password. There are two ways how the situation can be improved without lengthening or changing the password as described in [PBCS]. The first one is to add a few different bytes in the end of the password every time it is used to generated a key, and use different bytes for different data units to be encrypted. These bytes are known as ‘salt’. The salt bytes must be random, or at least different for each stream of data we encrypt. This way we would have a new different key every time, to use for encryption, based on the same root password. A salt between 8 and 16 bytes is usually enough to generate a big possible key space. To decrypt the data we must know the original salt used to encrypt it, apart of the password. The good news is that salt need not to be secret: if you were to keep salt secret then (a) do not use a salt at all, but use a different password/key each time; (b) you would have to remember it, and then you are in no better position that remembering the encryption key itself. Thus, while the salt does not enlarge the password search space, it grows the effective key space that you use for your ciphertext, making it impossible to find the keys by someone which has access to all your ciphertext and all your corresponding salts. The second technique effectively grows the search space for passwords without growing up the password size. It takes into consideration not only the size of the search space but also the time it takes to make a try. When we said that the search space for random 20-character long passwords is around 2^120, which is smaller than 2^128, the space of the smallest AES key size, we assumed that testing for a password takes the same time as testing a key. We can make the situation better by growing the time it makes to generate a key from a password. Of course this cannot be done by adding delays in code, but by growing the amount of calculations needed. In [PBCS] this is controlled by what is called an iteration count. An iteration count of 2^10 will make the effective time grow up around 2^120 * 2^10 = 2^130 time units compared with an iteration count of 1. Bigger values are of course better but make the key generation two slow in practice with the current computation power. So if you know the password you compute the key in around 2^10 time units. If you do not know the password, the worst case to try all passwords up to 20 characters would require around 2^130 time units. These two improvements make using passwords (even when you do not change
them) comparable in security with using different keys. The class
int keySize = 32;
byte[] salt = ...;
byte[] key = KeyGen.DeriveKey(password, keySize, salt); //iteration count 1024
The stream cipher library I implemented does not offer a way to create a salt or an IV, given that different applications may use different ways to generate and distribute them. A simple way to get an IV and salt byte array of a given length is to use a pseudo-random byte generator. Before I close up, I will explain what time means in the calculations above. A number such as 2^120 means that if a computer makes 100 guesses per second, it will take approximately 421495432453359929256661295 years to finish (in the worst case). However if you ever had 421495432453359929256661295 computers, it would take you only one second. And if you use a hidden camera or a keylogger to steal the password from someone as s/he types it in (etc, etc), you do not need such many computers at all. History
AcknowledgementsUlrike Meyer explained to me why the IV-s need not to be secret. My understanding of salt and iteration count also followed a nice discussion with her of [PBCS]. James McCaffrey' comments on the first ECB version of my stream cipher encouraged me to finish the standart-compatible CBC version.References
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||