Every programmer knows that the early development of the programmable electronic computer was largely spurred by the crypto-war between the British intelligence services and the German navy in WWII. Less known is that the infamous German Enigma cryptography machines were just a modification of off-the-shelf equipment used by banks in the mid-1930s to encode sensitive data about wire transfers.
Nowadays, the need to encode sensitive information is more important than ever. Consider devices such as USB thumb drives, which can store an entire CD’s worth of data in a package the size of a cigarette lighter. Can your data ever be safe if it can all be downloaded in a few clicks and then carried out the door? Encryption of your data gives you one more level of security beyond merely the physical. The good news is that you can encrypt regularly and painlessly with a C++ class library such as Crypto++.
Crypto++: Many Algorithms for Many Platforms
The Crypto++ library is unabashedly a best-of-breed creature: rather than re-implement many popular schemes, the Crypto++ project seeks to integrate them under a uniform framework based on simple abstract base classes. A complete list of features and ciphers would be too long to include here, but the following are a few I found important:
- Symmetric block ciphers: including Advanced Encryption Standard (AES), Triple-DES, Blowfish, Skipjack, and many more
- Generic cipher modes: ECB, CBC, CBC ciphertext stealing (CTS), CFB, OFB, counter mode (CTR)
- Stream cipher modes: Panama, ARC4, SEAL, WAKE, WAKE-OFB, BlumBlumShub
- Public-key cryptography: including RSA, DSA, ElGamal, Nyberg-Rueppel (NR), Rabin, Rabin-Williams (RW), and many others
- Elliptic curve cryptography: doesn’t rely on large random prime numbers
- One-way hash functions: such as SHA-1, MD5, RIPEMD, Panama, Whirlpool
- Message Authentication Codes (MAC): based on MD5, HMAC, XOR, CBC, and others
- Gzip: compression/decompression built-in
- Prime number generation and verification
- OS-independent wrappers: for timers, sockets, named pipes, random number generation, and crypto APIs
Crypto++ supports a wide variety of platforms, including Microsoft Visual C++ version 6.0, 7.0, 7.1, and 8.0, GCC 3.X and 4.0 for Unix and Windows, MacOS X, and Sun Solaris systems, to name a few. A Visual Studio .NET project file was included in Crypto++ 5.2.1 and imported into Visual Studio .NET 2003 without any problems. The project file creates cryptopp.dll, which is about 1.7MB in a DEBUG build. You may add #define CRYPTOPP_DEFAULT_NO_DLL to use a static library implementation rather than a DLL interface, if you desire.
Unlike many such libraries, the Crypto++ distro doesn’t include a lot of convoluted scripts and makefiles. In general, you can simply recompile the source code and make your own library.
Hello, Crypto++ Basic Encryption/Decryption
For a “Hello World” example, I wanted to build the simplest command-line-driven program that could encrypt and decrypt files. Basically, you pass it the following three files on the command line:
- The original file to encrypt
- The filename for encrypted output
- The filename for the result of roundtrip encryption/decryption
The program then prompts for a passphrase, which is used during the encryption/decryption. Let me dissect the code line-by-line to see how it’s done.
1 // testcrypto.cpp 2 3 #define CRYPTOPP_DEFAULT_NO_DLL 4 #include "dll.h" 5 #include "default.h" 6 #include <iostream> 7 8 #ifdef CRYPTOPP_WIN32_AVAILABLE 9 #include <windows.h> 10 #endif 11 12 USING_NAMESPACE(CryptoPP) 13 USING_NAMESPACE(std) 14 15 const int MAX_PHRASE_LENGTH=250; 16 17 void EncryptFile(const char *in, const char *out, const char *passPhrase); 18 void DecryptFile(const char *in, const char *out, const char *passPhrase); 19 20 21 int CRYPTOPP_CDECL main(int argc, char *argv[]) 22 { 23 try 24 { 25 char passPhrase[MAX_PHRASE_LENGTH]; 26 cout << "Passphrase: "; 27 cin.getline(passPhrase, MAX_PHRASE_LENGTH); 28 EncryptFile(argv[1], argv[2], passPhrase); 29 DecryptFile(argv[2], argv[3], passPhrase); 30 } 31 catch(CryptoPP::Exception &e) 32 { 33 cout << "nCryptoPP::Exception caught: " << e.what() << endl; 34 return -1; 35 } 36 catch(std::exception &e) 37 { 38 cout << "nstd::exception caught: " << e.what() << endl; 39 return -2; 40 } 41 } 42 43 44 void EncryptFile(const char *in, const char *out, const char *passPhrase) 45 { 46 FileSource f(in, true, new DefaultEncryptorWithMAC(passPhrase, new FileSink(out))); 47 } 48 49 void DecryptFile(const char *in, const char *out, const char *passPhrase) 50 { 51 FileSource f(in, true, new DefaultDecryptorWithMAC(passPhrase, new FileSink(out))); 52 } 53 54 RandomPool & GlobalRNG() 55 { 56 static RandomPool randomPool; 57 return randomPool; 58 } 59 int (*AdhocTest)(int argc, char *argv[]) = NULL;
To get the ball rolling, you only need to include the two Crypto++ header files, dll.h and default.h (see lines #3-4). The USING_NAMESPACE() macro allows Crypto++ to work around old compilers whose namespace handling is broken (see lines #12-13). Getting into the main() function, you see that all the code is wrapped in a try/catch block. You can expect Crypto++ API failures to result in throwing CryptoPP::Exception, and you also trap std::exception for good measure.
The passphrase is a string of up to 250 characters that you will use to encrypt and later decrypt the file. Now, step inside the little application functions EncryptFile() and DecryptFile() to see how it’s really done (lines #44-52):
FileSource f(in, true, new DefaultEncryptorWithMAC(passPhrase, new FileSink(out)));
In C++ fashion, the FileSource constructor does all the work and then as EncryptFile() returns, the destructor cleans up after it. I don’t think you can get any more object-oriented than this design—at least as far as declarative coding goes. The following are the four possible arguments used in this constructor:
FileSource (const char *filename, // Our input filename line bool pumpAll, // "drain" everything at once? BufferedTransformation *attachment=NULL, bool binary=true) // Assume non-text data
In this case, you let binary mode default to true and create your own BufferedTransformation object with a new DefaultEncryptorWithMAC() call. A BufferedTransformation is an object that takes a stream of bytes as input (this may be done in stages), does some computation on them, and then places the result into an internal buffer for later retrieval. Any partial result already in the output buffer is not modified by further input. (A demonstration of blocking I/O is outside the immediate scope of this article.)
Now, take a closer look at the third argument in the FileSource constructor back on line #46:
... new DefaultEncryptorWithMAC(passPhrase, new FileSink(out)));
Finally, you get to see the use of the passPhrase as sent down to the default encryptor, and you create a FileSink object that will open the output file and manage the buffering to the output file. Again, you could take manual control of the I/O buffering for performance or other reasons. At the end, you have a BufferedTransformation object with an encryptor and its corresponding Message Authentication Code as derived from the key.
To test the encryption/decryption, input the Constitution of the United States as the input file, store the intermediate encrypted file, and decrypt to test it as follows:
D:crypto++>testcrypto.exe constitution.txt encrypt.txt constitution2.txt Passphrase: Meathead D:tempcrypto++>dir *.txt Directory of D:tempcrypto++ 04/06/2006 02:48 PM 28,365 constitution.txt 05/02/2006 03:54 PM 28,365 constitution2.txt 05/02/2006 03:54 PM 28,408 encrypt.txt
As you can see from the above test (and my own diffs later), the input and output files were identical and the intermediate file was precisely 43 bytes larger than the input or output. I would show you what the encrypted file looked like, but it is just so much random-looking data at that point.
Sign and Verify a File with RSA
Another easy-to-implement Crypto++ task is RSA signing and verification of files. RSA was named after its inventors Rivest, Shamir, and Adleman in 1977. RSA involves two keys: public key and private key (a key is a constant number later used in the encryption formula.) The public key is known to everyone and is used to encrypt messages. These messages can be decrypted only by use of the private key. In other words, anybody can encrypt a message, but only the holder of a private key can actually decrypt and read it.
RSA is also often used for signing messages. The most common example of this is protecting software license files from tampering while still keeping them human readable. In this case, the signature data is embedded as a hexadecimal string inside the license file. The function RSASignFile() in the following code takes a private key file and a message to be signed, and then writes the RSA signature to the final named file. To verify the file with RSAVerify(), you must supply the public key file, the message again, and its signature:
void RSASignFile(const char *privFilename, const char *messageFilename, const char *signatureFilename) { FileSource privFile(privFilename, true, new HexDecoder); RSASSA_PKCS1v15_SHA_Signer priv(privFile); FileSource f(messageFilename, true, new SignerFilter(GlobalRNG(), priv, new HexEncoder(new FileSink(signatureFilename)))); } bool RSAVerifyFile(const char *pubFilename, const char *messageFilename, const char *signatureFilename) { FileSource pubFile(pubFilename, true, new HexDecoder); RSASSA_PKCS1v15_SHA_Verifier pub(pubFile) FileSource signatureFile(signatureFilename, true, new HexDecoder); if (signatureFile.MaxRetrievable() != pub.SignatureLength()) return false; SecByteBlock signature(pub.SignatureLength()); signatureFile.Get(signature, signature.size()); VerifierFilter *verifierFilter = new VerifierFilter(pub); verifierFilter->Put(signature, pub.SignatureLength()); FileSource f(messageFilename, true, verifierFilter); return verifierFilter->GetLastResult(); }
Crypto for Data Security
Beyond the obvious financial and military applications, cryptography can be used to store any information you would prefer not to leave accessible to everyone and everything. With seven million Americans reporting identity theft every year, any options you can give your users to increase their data security will surely make them breathe easier.
This article has barely scratched the surface of what Crypto++ can do for you. Perhaps its best aspect is that it can keep up with your needs for more sophisticated algorithms, ciphers, and compression, and thus provide a stable vehicle for your app to enter crypto-land.
About the Author
Victor Volkman has been writing for C/C++ Users Journal and other programming journals since the late 1980s. He is a graduate of Michigan Tech and a faculty advisor board member for Washtenaw Community College CIS department. Volkman is the editor of numerous books, including C/C++ Treasure Chest and is the owner of Loving Healing Press. He can help you in your quest for open source tools and libraries; just drop an e-mail to sysop@HAL9K.com.