User Guide: cryptlib.h

From Crypto++ Wiki
Jump to: navigation, search

Exception

All exception classes in Crypto++ are derived from Exception, which is merely an implementation of the standard C++ library exception interface defined by std::exception. It is therefore possible to catch all Crypto++ exceptions as demonstrated below.

Usage:

 class SpecificException : public CryptoPP::Exception
 {
 public:
     SpecificException(std::string const& strDesc)
         : Exception(strDesc) {}
 };

 ...
 void ProcessTrivialFailure();
 void ProcessCatastrophicFailure();
 ...

 try
 {
     ...
     
     if (!...)
         throw CryptoPP::Exception(
             "This and that failed, releasing such and such resources");

     ...
     
     if (!...)
         throw SpecificException(
             "Something specific went wrong, so we are using a derived "
             "exception type to make it possible for programs to recognize "
             "this kind of exception without having to investigate the "
             "description returned by std::exception::what().");

     ...
 }
 catch (SpecificException const& e)
 {
     cout << "SpecificException caught:" << endl
          << e.what() << endl;

     // In this example, we recognize this type of exception and
     // we know that it is a trivial one; we have a routine to process it.
     ProcessTrivialFailure();
 }
 catch (std::exception const& e)
 {
     // This clause will also catch CryptoPP::Exception as well as
     // all derived exception classes, except for SpecificException
     // which has already been caught above.
     cout << "std::exception caught:" << endl
          << e.what();

     // In this example, an exception that we do not explicitly
     // recognize is by default a catastrophic one.
     ProcessCatastrophicFailure();
 }

Example - CatchExceptions.cpp:

 // Crypto++
 #include "files.h"  // FileSource, FileSink
 #include "osrng.h"  // AutoSeededRandomPool
 
 // std
 #include <iostream>
 #include <cstdio>
 
 int main()
 {
     using namespace std;
     using namespace CryptoPP;
 
     try
     {
         static char const* szFilename = "Message.txt";
 
         string strMessageBefore = "How do you do?";
         cout << "Message before: " << strMessageBefore << endl;
         StringSource(strMessageBefore, true, new FileSink(szFilename));
 
         // This rogue block of code will sometimes remove the
         // file that was just written to, and sometimes not.
         // The decision depends on the system's random data generator.
         AutoSeededRandomPool rng;
         if (!(rng.GenerateByte() % 3))
             remove(szFilename);
 
         string strMessageAfter;
         FileSource(szFilename, true, new StringSink(strMessageAfter));
             // FileSource will throw if the file does not exist

         cout << "Message after: " << strMessageAfter << endl;
     }
     catch (CryptoPP::Exception const& e)
     {
         cout << "CryptoPP::Exception caught:" << endl
              << e.what() << endl;
         return 1;
     }
 
     return 0;
 }

Output - case 1:

 Message before: How do you do?
 Message after: How do you do?

Output - case 2:

 Message before: How do you do?
 CryptoPP::Exception caught:
 FileStore: error opening file for reading: Message.txt

BufferedTransformation

The BufferedTransformation base class is the key to virtually everything else in Crypto++. BufferedTransformation is an abstract base class; it defines a versatile input/output interface that is used throughout the library. In order to make good use of Crypto++, you should be well acquainted with BufferedTransformation as well as its derived classes.

One of the most important characteristics of BufferedTransformation is that, although it provides definitions for a wide range of input/output methods, not all of them need to be supported by its derived classes. For example, the Source classes do not implement output methods, and the Sink classes do not implement input methods. But on the other hand, classes like ByteQueue or MessageQueue; or Base64Encoder or HexDecoder; implement both input as well as output functionality.

(As a consequence, Source classes always appear at the beginning of a Filter chain; Sink classes always appear at the end; whereas other Filter classes may appear at the beginning, at the end, or in the middle. See User Guide: filters.h.)

In a Crypto++ user's code, BufferedTransformation is typically used as an abstraction used in place of any object that might provide an input and/or output interface.

Usage:

 BufferedTransformation& bt = ...;
 
 ...

 // Data storage / processing

 byte bByte = ...;
 bt.Put(bByte);
 
 byte const* pbData = ...;
 unsigned int nDataLen = ...;
 bt.Put(pbData, nDataLen);
 
 // The Flush() call is meaningful if bt is actually a network socket,
 // or a compression object, or another object that buffers input in a way
 // that allows for flushing
 bt.Flush(true);

 word32 wWord = ...;
 bt.PutWord32(wWord);  // default is big-endian
 
 bt.MessageEnd();
 
 bt.Put(pbData, nDataLen);
 
 bt.MessageEnd();
 
 ...

 // Data retrieval
 
 unsigned int const nPeekSize = ...;
 byte peekBuffer[nPeekSize];
 unsigned int nNrBytesPeeked = bt.Peek(peekBuffer, nPeekSize);

 if (bt.AnyRetrievable())
 {
     unsigned int nOutputSize = bt.MaxRetrievable();
     byte* pbOutput = new byte[nOutputSize];
     CAutoFreePtr<byte> autoFreeOutput(pbOutput);

     if (bt.Get(pbOutput, nOutputSize) != nOutputSize)
         throw "Unable to retrieve data although MaxRetrievable() says it's there";

     // Do some processing
     ...
 }

 if (!bt.GetNextMessage())
 {
     cout << "No more messages" << endl;
     break;
 }
 
 byte bByte;
 if (bt.Get(bByte) != sizeof bByte)
 {
     cout << "No more data" << endl;
     break;
 }

 ...

Example - DumpMessage.cpp:

 // Crypto++
 #include "cryptlib.h"
 
 // std
 #include <iostream>
 
 void DumpMessage(CryptoPP::BufferedTransformation& bt)
 {
     using namespace std;
     
     static char const* szHexDigits = "0123456789abcdef";
     byte b;
     while (bt.AnyRetrievable())
     {
         if (!bt.Get(b))
             throw "Error: AnyRetrievable() returned true, "
                   "so this shouldn't happen.";

         // It is much easier to implement this using HexEncoder;
         // however, let's not get into that just yet. The below code
         // could do some special kind of processing that is not
         // supported by an off-the-shelf Filter class.

         cout << szHexDigits[(b >> 4) & 0x0f]
              << szHexDigits[b & 0x0f]
              << ' ';
     }
 }
 
 // Crypto++
 #include "filters.h"
 
 int main()
 {
     using namespace CryptoPP;
 
     char const* szMessage = "How do you do?";
     DumpMessage(StringSource(szMessage, true));
         // If we constructed StringSource with 'false' instead of 'true',
         // StringSource wouldn't call its PumpAll() method on construction,
         // and no data would be extractable from the StringSource object
         // until someone called the object's Pump() or PumpAll() method.
 
     return 0;
 }
 

Output:

 48 6f 77 20 64 6f 20 79 6f 75 20 64 6f 3f

RandomNumberGenerator

The class RandomNumberGenerator is a virtual base class for various implementations of random number generation, the most useful of which is AutoSeededRandomPool (defined in osrng.h) or, when the latter is unavailable, RandomPool (defined in randpool.h).

Another useful class derived from RandomNumberGenerator is NullRNG (defined in rng.h), which can be passed to functions and methods which ask for an RNG but do not actually use it.

Usage:

 RandomNumberGenerator& rng = ...;
 
 ...
 
 byte bByte = rng.GenerateByte();
 
 ...
 
 unsigned int const nBlockSize = ...;
 byte abBlock[nBlockSize];
 rng.GenerateBlock(abBlock, nBlockSize);

 ...
 
 word32 wMinimum = ...;
 word32 wMaximum = ...;
 word32 wWord = rng.GenerateWord32(wMinimum, wMaximum);

Example - TestEncryptionWithRng.cpp:

 // Crypto++
 #include "cryptlib.h"
 #include "filters.h"    // StringSink
 #include "osrng.h"      // AutoSeededRandomPool
 #include "hex.h"        // HexEncoder
 
 // std
 #include <string>
 #include <vector>
 #include <iostream>
 void GeneratePlaintexts(
     std::vector<std::string>& vectPlaintexts,
     unsigned int nNrPlaintexts,
     unsigned int nMinPlaintextLen,
     unsigned int nMaxPlaintextLen,
     CryptoPP::RandomNumberGenerator& rng)
 {
     using namespace std;
     using namespace CryptoPP;
 
     vectPlaintexts.resize(0);
 
     cout << "Generating " << nNrPlaintexts << " plaintexts..." << endl;
 
     while (nNrPlaintexts--)
     {
         word32 nPlaintextLen = rng.GenerateWord32(nMinPlaintextLen, nMaxPlaintextLen);
 
         // Can't instantiate a string, resize it and write to it directly -
         // std::string's internal storage might not be contiguous
         SecByteBlock sbbGenerated(nPlaintextLen);
         rng.GenerateBlock(sbbGenerated.begin(), nPlaintextLen);
 
         vectPlaintexts.push_back(
             std::string(
                 (char const*) sbbGenerated.begin(),
                 sbbGenerated.size()));
     }
 }
 void TestEncryption(
     std::string const& strPlaintext,
     CryptoPP::BufferedTransformation& encryptor,
     CryptoPP::BufferedTransformation& decryptor)
 {
     using namespace std;
     using namespace CryptoPP;
 
     string strEncrypted;
     string strDecrypted;
 
     cout << "Testing plaintext of length " << strPlaintext.size() << ": ";
 
     try
     {
         // Encrypt plaintext
         encryptor.Detach(new StringSink(strEncrypted));
         encryptor.Put((byte const*) strPlaintext.data(), strPlaintext.size());
         encryptor.MessageEnd();
     
         // Decrypt the resulting ciphertext
         decryptor.Detach(new StringSink(strDecrypted));
         decryptor.Put((byte const*) strEncrypted.data(), strEncrypted.size());
         decryptor.MessageEnd();
 
         // Compare decrypted plaintext with the original plaintext
         if (strDecrypted != strPlaintext)
             throw Exception("TestEncryption(): strDecrypted != strPlaintext");
     }
     catch (...)
     {
         string strPlaintextHex;
         string strEncryptedHex;
         string strDecryptedHex;
         StringSource(strPlaintext, true,
             new HexEncoder(new StringSink(strPlaintextHex)));
         StringSource(strEncrypted, true,
             new HexEncoder(new StringSink(strEncryptedHex)));
         StringSource(strDecrypted, true,
             new HexEncoder(new StringSink(strDecryptedHex)));
         cout << "ERROR" << endl
              << "strPlaintext (size " << strPlaintext.size() << "): " << endl
              << strPlaintextHex << endl
              << "strEncrypted (size " << strEncrypted.size() << "): " << endl
              << strEncryptedHex << endl
              << "strDecrypted (size " << strDecrypted.size() << "): " << endl
              << strDecryptedHex << endl;
 
         throw;
     }
 
     cout << "OK" << endl;
 }
 
 // Crypto++
 #include "default.h"    // DefaultEncryptorWithMAC
 
 int main()
 {
     using namespace std;
     using namespace CryptoPP;
 
     try
     {
         unsigned int nPlaintexts = 16;
         vector<string> vectPlaintexts;
 
         AutoSeededRandomPool rng;
         GeneratePlaintexts(vectPlaintexts, nPlaintexts, 0, 1024, rng);
 
         char const* szPassphrase = "some passphrase";
         DefaultEncryptorWithMAC encryptor(szPassphrase);
         DefaultDecryptorWithMAC decryptor(szPassphrase);
 
         for (unsigned int i=0; i!=nPlaintexts; ++i)
         {
             cout << "(" << i << ") ";
             TestEncryption(vectPlaintexts[i], encryptor, decryptor);
         }
 
         cout << "All tests completed successfully." << endl;
     }
     catch (CryptoPP::Exception const& e)
     {
         cout << "main() - CryptoPP::Exception caught:" << endl
              << e.what() << endl;
         return 1;
     }
 
     return 0;
 }

Output:

 Generating 16 plaintexts...
 (0) Testing plaintext of length 1007: OK
 (1) Testing plaintext of length 680: OK
 (2) Testing plaintext of length 644: OK
 (3) Testing plaintext of length 601: OK
 (4) Testing plaintext of length 787: OK
 (5) Testing plaintext of length 303: OK
 (6) Testing plaintext of length 531: OK
 (7) Testing plaintext of length 840: OK
 (8) Testing plaintext of length 584: OK
 (9) Testing plaintext of length 598: OK
 (10) Testing plaintext of length 1020: OK
 (11) Testing plaintext of length 753: OK
 (12) Testing plaintext of length 845: OK
 (13) Testing plaintext of length 304: OK
 (14) Testing plaintext of length 266: OK
 (15) Testing plaintext of length 206: OK
 All tests completed successfully.

HashTransformation

All classes that implement one-way hash functions in Crypto++ are derived from HashTransformation. A class derived from HashTransformation can be used either directly or indirectly.

When used directly, the whole hashing operation can be completed in one step by calling the CalculateDigest method, or in multiple steps by first calling the Update method several times and then wrapping up by calling the Final method.

Indirect use of a HashTransformation derivative can be done through a wrapper class like HashFilter, which enables one to use a hashing algorithm in a manner conforming to the Filter paradigm (a family within the BufferedTransformation hierarchy).

After a hash module's Final method is called - either by calling Final directly, or indirectly by calling CalculateDigest or by calling HashFilter::MessageEnd() on a filter initialized with the hash module, the hash module is ready to calculate another hash, just as if the hash module had just been initialized.

The HashTransformation base class also provides the methods Verify and VerifyDigest. These methods are used similarly to Final and CalculateDigest, respectively, with the only distinction that they verify the computed digest to a supplied digest instead of calculating a digest. For implementations of regular one-way hash functions, calling VerifyDigest() is equivalent to calling CalculateDigest() and then manually comparing the calculated digest with a supplied one. However, note that this is not the case with message authentication codes (which are also derived from HashTransformation and use the same interface).

Usage - single-step: (using SHA for illustration)

 byte const* pbData = ...;
 unsigned int nDataLen = ...;
 byte abDigest[SHA::DIGESTSIZE];
 
 SHA().CalculateDigest(abDigest, pbData, nDataLen);
 
 // abDigest now contains the hash of pbData

 ...

 if (!SHA().VerifyDigest(abDigest, pbData, nDataLen))
 	throw "abDigest does not contain the right hash";

Usage - multi-step: (using SHA for illustration)

 byte const* pbData1 = ...;
 byte const* pbData2 = ...
 unsigned int nData1Len = ...;
 unsigned int nData2Len = ...;
 byte abDigest[SHA::DIGESTSIZE];
 
 SHA hash;
 hash.Update(pbData1, nData1Len);
 hash.Update(pbData2, nData2Len);
 hash.Final(abDigest);
 
 // abDigest now contains the hash of pbData1 and pbData2;
 // the object 'hash' can now be reused to calculate another digest
 
 ...
 
 SHA hash;
 hash.Update(pbData1, nData1Len);
 hash.Update(pbData2, nData2Len);
 if (!hash.Verify(abDigest))
 	throw "abDigest does not contain the right hash";

Usage - through HashFilter: (using SHA for illustration)

 byte const* pbData = ...;
 unsigned int nDataLen = ...;
 word32 wNumber = ...
 byte abDigest[SHA::DIGESTSIZE];
 
 // In this example, HashFilter only outputs the hash.
 // A flag can be passed to the HashFilter constructor to make it
 // output the data that is being hashed as well.
 SHA hash;
 HashFilter hf(hash, new ArraySink(abDigest, sizeof abDigest));
 hf.Put(pbData, nDataLen);
 hf.PutWord32(wNumber); // big-endian is default
 hf.MessageEnd();
 
 // abDigest now contains the hash of pbData and wNumber;
 // the object 'hf' can now be used to hash another message.
 
 // In this example, HashVerifier expects the hash to appear
 // at the beginning of input data, and it will only output
 // a single byte which will be non-zero if the hash verifies,
 // and zero otherwise. Again, there is a variety of other
 // behaviors that can be specified through the constructor.
 HashVerifier hv(hash);
 hv.Put(abDigest, sizeof abDigest);
 hv.Put(pbData, nDataLen);
 hv.PutWord32(wNumber);

 if (!hv.GetLastResult())
    throw "abDigest does not contain the right hash";

 // Alternative method of verification:

 byte bResult;
 if (!hv.Get(bResult))
    throw "Unexpected";

 if (!bResult)
    throw "abDigest does not contain the right hash";

Example - DumpHashes.cpp:

  // Crypto++
  #include "hex.h"        // HexEncoder

  // std
  #include <iostream>
  #include <string.h>

  void DumpHash_SingleStep(
      '''CryptoPP::HashTransformation& hash''',
      char const* szModuleName,
      std::string const& strData)
  {
      using namespace std;
      using namespace CryptoPP;
  
      // Cannot use std::string for buffer;
      // its internal storage might not be contiguous
      SecByteBlock sbbDigest('''hash.DigestSize()''');
  
      '''hash.CalculateDigest(
          sbbDigest.begin(),
          (byte const*) strData.data(),
          strData.size());'''
  
      cout << szModuleName << " SS: ";
      HexEncoder(new FileSink(cout)).Put(sbbDigest.begin(), sbbDigest.size());
      cout << endl;
  }

  void DumpHash_MultiStep(
      '''CryptoPP::HashTransformation& hash''',
      char const* szModuleName,
      std::string const& strDataPart1,
      std::string const& strDataPart2,
      std::string const& strDataPart3)
  {
      using namespace std;
      using namespace CryptoPP;
  
      '''hash.Update((byte const*) strDataPart1.data(), strDataPart1.size());'''
      '''hash.Update((byte const*) strDataPart2.data(), strDataPart2.size());'''
      '''hash.Update((byte const*) strDataPart3.data(), strDataPart3.size());'''
  
      // Cannot use std::string for buffer;
      // its internal storage might not be contiguous
      SecByteBlock sbbDigest('''hash.DigestSize()''');
  
      '''hash.Final(sbbDigest.begin());'''
  
      cout << szModuleName << " MS: ";
      HexEncoder(new FileSink(cout)).Put(sbbDigest.begin(), sbbDigest.size());
      cout << endl;
  }

  void DumpHash_HashFilter(
      '''CryptoPP::HashTransformation& hash''',
      char const* szModuleName,
      std::string const& strDataPart1,
      std::string const& strDataPart2,
      std::string const& strDataPart3)
  {
      using namespace std;
      using namespace CryptoPP;
  
      // Here, we are free to use std::string as the destination,
      // because StringSink uses the correct std::string interface to append data
      string strDigest;
      '''HashFilter hashFilter(hash,''' new StringSink(strDigest)''');'''
      '''hashFilter.Put((byte const*) strDataPart1.data(), strDataPart1.size());'''
      '''hashFilter.Put((byte const*) strDataPart2.data(), strDataPart2.size());'''
      '''hashFilter.Put((byte const*) strDataPart3.data(), strDataPart3.size());'''
      '''hashFilter.MessageEnd();'''
  
      cout << szModuleName << " HF: ";
      StringSource(strDigest, true,
          new HexEncoder(
              new FileSink(cout)));
      cout << endl;
  }

  void DumpHash(
      '''CryptoPP::HashTransformation& hash''',
      char const* szModuleName,
      std::string const& strDataPart1,
      std::string const& strDataPart2,
      std::string const& strDataPart3)
  {
      DumpHash_SingleStep('''hash''', szModuleName, strDataPart1 + strDataPart2 + strDataPart3);
      DumpHash_MultiStep('''hash''', szModuleName, strDataPart1, strDataPart2, strDataPart3);
      DumpHash_HashFilter('''hash''', szModuleName, strDataPart1, strDataPart2, strDataPart3);
  }

  // Crypto++
  #include "sha.h"        // SHA-1, SHA-256, SHA-384, SHA-512
  #include "ripemd.h"     // RIPEMD160
  #include "md5.h"        // MD5
  #include "crc.h"        // CRC-32

  int main()
  {
      using namespace std;
      using namespace CryptoPP;
  
      std::string strDataPart1 = "part 1; ";
      std::string strDataPart2 = "part two; ";
      std::string strDataPart3 = "PART THREE.";
  
      try
      {
          DumpHash('''SHA()''',       "SHA      ", strDataPart1, strDataPart2, strDataPart3);
          DumpHash('''SHA256()''',    "SHA256   ", strDataPart1, strDataPart2, strDataPart3);
          DumpHash('''RIPEMD160()''', "RIPEMD160", strDataPart1, strDataPart2, strDataPart3);
          DumpHash('''MD5()''',       "MD5      ", strDataPart1, strDataPart2, strDataPart3);
          DumpHash('''CRC32()''',     "CRC32    ", strDataPart1, strDataPart2, strDataPart3);
      }
      catch (CryptoPP::Exception const& e)
      {
          cout << "CryptoPP::Exception caught: " << endl
               << e.what() << endl;
          return 1;
      }
  
      return 0;
  }

Output:

 SHA       SS: 83F4A5C2778C5FC3FA4DC91BDE16203126E41D56
 SHA       MS: 83F4A5C2778C5FC3FA4DC91BDE16203126E41D56
 SHA       HF: 83F4A5C2778C5FC3FA4DC91BDE16203126E41D56
 SHA256    SS: 972226D00A46823E0A1FD0EB44EB7A865CA3AB981DE3927F8C1A98F4A0436AF9
 SHA256    MS: 972226D00A46823E0A1FD0EB44EB7A865CA3AB981DE3927F8C1A98F4A0436AF9
 SHA256    HF: 972226D00A46823E0A1FD0EB44EB7A865CA3AB981DE3927F8C1A98F4A0436AF9
 RIPEMD160 SS: 56583FCE48F5A443F3C7BE04DEA08465FCF8659F
 RIPEMD160 MS: 56583FCE48F5A443F3C7BE04DEA08465FCF8659F
 RIPEMD160 HF: 56583FCE48F5A443F3C7BE04DEA08465FCF8659F
 MD5       SS: F8F523DCDA45D469FCEFCCF09BC3D6D5
 MD5       MS: F8F523DCDA45D469FCEFCCF09BC3D6D5
 MD5       HF: F8F523DCDA45D469FCEFCCF09BC3D6D5
 CRC32     SS: CE4562A8
 CRC32     MS: CE4562A8
 CRC32     HF: CE4562A8

MessageAuthenticationCode

All classes that implement message authentication codes in Crypto++ are derived from MessageAuthenticationCode. MessageAuthenticationCode, in turn, is derived from HashModule; hence, a class derived from MessageAuthenticationCode is used more or less the same as a class implementing a one-way hash function.

There are two primary distinctions between a message authentication code module and a hash module:

  • A class implementing a message authentication code needs to be supplied a key on construction;
  • Applying a MAC to the same input twice generally does not produce the same authentication code. Hence, verifying a MAC is not equivalent to calling CalculateDigest() and then manually comparing the calculated hash with the supplied one. Rather, either Verify or VerifyDigest must be used to verify a MAC.

Usage: (using HMAC for illustration)

 byte const* pbData = ...;
 unsigned int nDataLen = ...;

 byte const* pbKey = ...;
 unsigned int nKeyLen = ...;

 byte abMac[HMAC::DIGESTSIZE];
 
 HMAC(pbKey, nKeyLen).CalculateDigest(abMac, pbData, nDataLen);
 
 // abMac now contains the HMAC of pbData, keyed with pbKey

 ...

 if (!HMAC(pbKey, nKeyLen).VerifyDigest(abMac, pbData, nDataLen))
     throw "abMac does not contain a valid MAC for pbData, given pbKey";

To find other topics in the User Guide, visit Category:User Guide.