next up previous contents index
Next: Secure Byte String ( Up: Version 6.5 The LEDA Previous: Memory Streambuffer ( memory_streambuf   Contents   Index


Symmetric Key Cryptography

LEDA covers the following aspects of symmetric key cryptography:

All these topics will be discussed in detail later. We want to point out that secrecy and authentication are two orthogonal concepts. If you use only authentication, nobody will be able to change your message without being detected but everybody can read it. On the other hand, if you use only encryption then nobody can read your message but an attacker could change it. At first sight this does not seem to make much sense because the attacker can only alter ciphertext and he will not be able to predict what the corresponding plaintext will look like. However, this may still cause damage. Imagine for example a satellite that is remote controlled via encrypted but unauthenticated commands. If an attacker manages to make the satellite listen to his commands, he cannot exploit the device but he might be able to make it leave its orbit and crash.
Hence, sometimes secrecy and authentication must be used together to achieve the desired security. LEDA makes it easy to combine these different aspects. The example below will illustrate this. The program is similar to the opening example of Chapter Compression. It consists of three parts: key generation, encoding and decoding. Each part will be explained below.

#include <LEDA/coding/crypt.h> // contains all cryptography classes

using namespace leda;

void generate_keys(CryptKey& auth_key, CryptKey& cipher_key)
{
  // key generation (two keys)
  CryptByteString passphrase = CryptKey::read_passphrase("Passphrase: ");
  CryptByteString salt(1);
  salt[0] = 'a'; // for authentication
  auth_key = CryptKey::generate_key(128/8, passphrase, salt);
  salt[0] = 'c'; // for enciphering/deciphering
  cipher_key = CryptKey::generate_key(128/8, passphrase, salt);
}

int main()
{
  string str = "Hello World";
  CryptKey auth_key, cipher_key;

  // encode: MAC -> compress -> encipher
  typedef CoderPipe3< OMACCoder<>, PPMIICoder, CBCCoder<> > CryptCoder;
  encoding_ofstream<CryptCoder> out("foo");
  generate_keys(auth_key, cipher_key);
  out.get_coder()->get_coder1()->set_key(auth_key);
  out.get_coder()->get_coder3()->set_key(cipher_key);
  out << str << "\n";
  out.close();
  if (out.fail()) std::cout << "error writing foo" << "\n";

  // decode: decipher -> decompress -> MAC
  decoding_ifstream<CryptCoder> in("foo");
  generate_keys(auth_key, cipher_key);
  in.get_coder()->get_coder1()->set_key(auth_key);
  in.get_coder()->get_coder3()->set_key(cipher_key);
  str.read_line(in);
  in.finish(); // read till EOF is reached and then close "in"
  if (in.fail()) std::cout << "error authenticating foo" << "\n";

  std::cout << "decoded string: " << str << "\n";

  return 0;
}

In the first part of the program (function generate$ \_keys$ ) two keys are generated. The input for each generation is a passphrase and a salt. The passphrase is a human-readable and easy-to-remember string which must be kept secret. The salt is an array of bytes (in the example just one byte) which should be unique but it can be made public without endangering security. It allows to generate different keys from a single passphrase. In the example we generate one key for authentication and one key for encryption. More information on key generation can be found in Section CryptKey.
In the second part (beginning of main ) we encode a message. We use a coder pipe CryptCoder which first authenticates ( OMACCoder ), then compresses ( PPMIICoder ) and finally encrypts ( CBCCoder ) its source stream. Observe that the output of the OMACCoder (in encoding mode) is the original input plus the MAC for this input. (In decoding mode this MAC will be verified automatically.) In order to use the CryptCoder we construct an encoding$ \_ofstream$ called out . After setting the keys of the cryptographic coders we can use out just like an ordinary ofstream .
We want to point out that the order of the coders in the pipe is not arbitrary. It makes sense to put the authentication at the very beginning because you want to authenticate the original plaintext and not some compressed or encrypted version of it. Moreover, it is important to place compression before encryption for two reasons: Since compression usually destroys patterns and structures in the plaintext it can be seen as a way of obscuring the plaintext, which can be a helpful preprocessing step for encryption. But the following is even more important: As encryption transforms its input into a seemingly random stream applying compression after encryption usually increases the length of stream. (Some cryptographers even warn not to trust an encryption algorithm if its output can be compressed well [80].)
Now we describe the decoding process (at the end of main ). It looks very similar to the encoding process but instead of encoding$ \_ofstream$ we use its counterpart decoding$ \_ifstream$ . After the key set up the stream in can be used as a usual C++ input stream. However, there is one subtle point that we want to highlight: When we are done with in we do not call the close but the finish method. This reads the stream till its end before closing it. This ensures that the OMACCoder verifies the MAC and reports an error if the computed MAC and the MAC found in the stream differ. (If in were closed before its end is reached then no authentication would be performed and no error would be signaled. The reader is invited to try this out.)

We want to discuss a feature of LEDA that can simplify the decoding process. Suppose a friend of yours has sent you an authenticated and encrypted file over the internet, and he has provided you the authentication and the encryption key (via a secure channel). But unfortunately he did not tell you which coders he actually used for encoding his message. Then you can use the CryptAutoDecoder to decode his message. This class is able to automatically reconstruct the coder (or the coder pipe) that was used for encoding the stream:

  decoding_ifstream<CryptAutoDecoder> in("foo");
  in.get_coder()->add_key(auth_key);
  in.get_coder()->add_key(cipher_key);

Of course, LEDA also supports encryption and decryption of files:

  // encryption
  CoderPipe3< OMACCoder<>, PPMIICoder, CBCCoder<> > coder;
  coder.set_src_file("input.txt"); coder.set_tgt_file("output");
  coder.get_coder1()->set_key(auth_key);
  coder.get_coder3()->set_key(cipher_key);
  coder.encode();

  // decryption
  CryptAutoDecoder auto("output", "input.txt");
  auto->add_key(auth_key); auto->add_key(cipher_key);
  auto.decode();

We want to discuss some security assumptions made by LEDA, i.e. some preconditions that must be fullfilled in order to ensure the security of your data:

We want to address another important issue. Some readers might be worried about the fact that a potential attacker can purchase a copy of the source code of LEDA. One may wonder if this is a security risk. Fortunately, this is not the case. The cryptographic algorithms in LEDA have been designed under the assumption that a potential attacker knows the algorithm (but not the key). All the algorithms have appeared in scientific publications and successfully passed thorough cryptographic analysis. The fact that the source code can be purchased allows you to verify that the developers of LEDA did not put any trap-doors into the code which allow them to decrypt data without knowing the key. (This means: If you loose your passphrase or your key then we are not able to help you to recover your encrypted data.)



Subsections
next up previous contents index
Next: Secure Byte String ( Up: Version 6.5 The LEDA Previous: Memory Streambuffer ( memory_streambuf   Contents   Index
Christian Uhrig 2017-04-07