CTR Mode
Documentation |
#include <cryptopp/modes.h>
|
CTR mode is counter mode and uses a counter rather than a random or unpredictable initialization vector. The counter has additional properties, including an unique value and initial counter block. The mode does not require padding the plain text to the block size of the cipher. CTR mode was standardized in 2001 by NIST in SP 800-38A.
Crypto++ offers several modes of operation, including ECB, CBC, OFB, CFB, CBC-CTS, CTR, XTS, CCM, EAX, GCM and OCB.
Crypto++ does not provide a way to retrieve the current IV or counter used for encryption or decryption. If you need the current IV or counter then you need to manage it yourself. Some ciphers allow you to seek a number of bytes or blocks in the stream.
If you are used to working in languages like Java or libraries like OpenSSL, then you might want to visit the Init-Update-Final wiki page. Crypto++ provides the transformation model, but its not obvious because its often shrouded behind Pipelines.
Note: if your project is using encryption alone to secure your data, encryption alone is usually not enough. Please take a moment to read Authenticated Encryption and consider using an algorithm or mode like CCM, GCM, EAX or ChaCha20Poly1305.
Counter Increment
Broadly speaking, NIST specifies two types of counters. First is a counter which is made up of a nonce and counter. The nonce is random, and the remaining bytes are counter bytes which are incremented. For example, a 16 byte block cipher might use the high 8 bytes as a nonce, and the low 8 bytes as a counter. The second is a counter block, where all bytes are counter bytes and can be incremented as carries are generated. For example, in a 16 byte block cipher, all 16 bytes are counter bytes.
Crypto++ uses the second method, which means the entire byte block is treated as counter bytes. Counter mode is declared in modes.h
:
class CTR_ModePolicy : public ModePolicyCommonTemplate<AdditiveCipherAbstractPolicy> { public: bool CipherIsRandomAccess() const {return true;} IV_Requirement IVRequirement() const {return RANDOM_IV;} static const char * StaticAlgorithmName() {return "CTR";} protected: virtual void IncrementCounterBy256(); ... };
The implementation increments the entire counter block. modes.cpp
offers IncrementCounterBy256
:
void CTR_ModePolicy::IncrementCounterBy256() { IncrementCounterByOne(m_counterArray, BlockSize()-1); }
IncrementCounterByOne
is located in misc.h
, and it performs:
inline void IncrementCounterByOne(byte *inout, unsigned int s) { for (int i=s-1, carry=1; i>=0 && carry; i--) carry = !++inout[i]; }
Sample Program
There are three sample programs for CTR mode. The samples use filters in a pipeline). Pipelining is a high level abstraction and it handles buffering input, buffering output and padding for you. The first sample shows the basic use of a pipeline. The second sample shows how to change padding. The third shows how to manually insert into a filter.
If you are benchmarking then you may want to visit Benchmarks | Sample Program . It shows you how to use StreamTransformation
and its ProcessString
method to process multiple blocks at a time. ProcessString
eventually calls BlockTransformation
and ProcessBlock
. Calling a cipher's ProcessString
or ProcessBlock
eventually call ProcessAndXorBlock
or AdvancedProcessBlocks
, and they are the lowest level API you can use.
Pipelines
The sample program below demonstrates AES in CTR mode using filters (see pipelining). Though the key is declared on the stack, a SecByteBlock is used to ensure the sensitive material is zeroized. Similar could be used for both plain text and recovered text.
AutoSeededRandomPool prng; SecByteBlock key(AES::DEFAULT_KEYLENGTH); prng.GenerateBlock( key, key.size() ); byte ctr[ AES::BLOCKSIZE ]; prng.GenerateBlock( ctr, sizeof(ctr) ); string plain = "CTR Mode Test"; string cipher, encoded, recovered; /*********************************\ \*********************************/ try { cout << "plain text: " << plain << endl; CTR_Mode< AES >::Encryption e; e.SetKeyWithIV( key, key.size(), ctr ); // The StreamTransformationFilter adds padding // as required. ECB and CBC Mode must be padded // to the block size of the cipher. CTR does not. StringSource ss1( plain, true, new StreamTransformationFilter( e, new StringSink( cipher ) ) // StreamTransformationFilter ); // StringSource } catch( CryptoPP::Exception& e ) { cerr << e.what() << endl; exit(1); } /*********************************\ \*********************************/ // Pretty print cipher text StringSource ss2( cipher, true, new HexEncoder( new StringSink( encoded ) ) // HexEncoder ); // StringSource cout << "cipher text: " << encoded << endl; /*********************************\ \*********************************/ try { CTR_Mode< AES >::Decryption d; d.SetKeyWithIV( key, key.size(), ctr ); // The StreamTransformationFilter removes // padding as required. StringSource ss3( cipher, true, new StreamTransformationFilter( d, new StringSink( recovered ) ) // StreamTransformationFilter ); // StringSource cout << "recovered text: " << recovered << endl; } catch( CryptoPP::Exception& e ) { cerr << e.what() << endl; exit(1); }
A typical output is shown below. Note that each run will produce different results because the key and initial counter block are randomly generated.
$ ./Driver.exe key: F534FC7F0565A8CF1629F01DB31AE3CA counter: A4D16CBC010DACAA2E54FA676B57A345 plain text: CTR Mode Test cipher text: 12455EDB41020E6D751F207EE6 recovered text: CTR Mode Test
Non-pipeline
To manually insert bytes into the filter, perform multiple Put
s. Though Get
is used below, a StringSink
could easily be attached and save the administrivia.
const size_t SIZE = 16 * 4; string plain(SIZE, 0x00); for(size_t i = 0; i < plain.size(); i++) plain[i] = 'A' + (i%26); ... CTR_Mode < AES >::Encryption encryption(key, sizeof(key), iv); StreamTransformationFilter encryptor(encryption, NULL); for(size_t j = 0; j < plain.size(); j++) encryptor.Put((byte)plain[j]); encryptor.MessageEnd(); size_t ready = encryptor.MaxRetrievable(); string cipher(ready, 0x00); encryptor.Get((byte*) &cipher[0], cipher.size());
Seeking
The following program seeks in the AES/CTR stream. Using a Crypto++ Pipeline is a tad bit awkward because Discard
or Skip
on a Source
does not work as expected. You have to Pump
data into "nothing" under the current implementation. Also see Skip'ing on a Source does not work as expected on Stack Overflow.
Below is an example of using AES/CTR and seeking in the stream. It needs to perform a "two part" seek. First, it discards bytes on the Source
called cipher
. Second, it seeks in the keystream on the encryption object called enc
to synchronize the counter. Once the seek is performed, the remainder of the cipher text is decrypted by calling PumpAll()
, which pumps the remainder of the data through the pipeline.
#include "modes.h" #include "aes.h" using namespace CryptoPP; int main(int argc, char* argv[]) { string plain = "Now is the time for all good men to come to the aide of their country"; byte key[AES::DEFAULT_KEYLENGTH] = {0}; byte nonce[AES::BLOCKSIZE] = {0}; CTR_Mode<AES>::Encryption enc; enc.SetKeyWithIV(key, sizeof(key), nonce, sizeof(nonce)); string cipher; StringSource ss1(plain, true, new StreamTransformationFilter(enc, new StringSink(cipher))); for(size_t i=0; i<cipher.size(); i++) { CTR_Mode<AES>::Decryption dec; dec.SetKeyWithIV(key, sizeof(key), nonce, sizeof(nonce)); StringSource ss2(cipher, false); ss2.Pump(i); dec.Seek(i); string recover; StreamTransformationFilter stf(dec, new StringSink(recover)); // Attach the decryption filter after seeking ss2.Attach(new Redirector(stf)); ss2.PumpAll(); cout << i << ": " << recover << endl; } return 0; }
Here is the result:
$ ./test.exe 0: Now is the time for all good men to come to the aide of their country 1: ow is the time for all good men to come to the aide of their country 2: w is the time for all good men to come to the aide of their country 3: is the time for all good men to come to the aide of their country 4: is the time for all good men to come to the aide of their country 5: s the time for all good men to come to the aide of their country 6: the time for all good men to come to the aide of their country 7: the time for all good men to come to the aide of their country 8: he time for all good men to come to the aide of their country 9: e time for all good men to come to the aide of their country 10: time for all good men to come to the aide of their country ... 58: eir country 59: ir country 60: r country 61: country 62: country 63: ountry 64: untry 65: ntry 66: try 67: ry 68: y
External Cipher
Counter mode uses the forward transformation for both encryption and decryption. The keystream is generated by encrypting the IV/counter, and then the keystream is XOR'd with the plaintext or ciphertext. If you are using an external cipher, then you should use the encryptor in both directions. Also see AES/CTR Decryption stops unexpectedly using Crypto++ on Stack Overflow.
#include "cryptlib.h" #include "secblock.h" #include "filters.h" #include "modes.h" #include "aes.h" #include <iostream> #include <string> int main (int argc, char* argv[]) { using namespace CryptoPP; SecByteBlock key(16), iv(16); std::memset(key, 0xff, key.size()); std::memset( iv, 0x88, iv.size()); std::string message, encrypted, decrypted; message = "Now is the time for all good men " "to come to the aide of their country."; AES::Encryption aesEncryption(key, key.size()); CTR_Mode_ExternalCipher::Encryption ctrEncryption(aesEncryption, iv); StreamTransformationFilter stfEncryptor( ctrEncryption, new StringSink(encrypted)); stfEncryptor.Put((const byte*)&message[0], message.size()); stfEncryptor.MessageEnd(); AES::Encryption aesDecryption(key, key.size()); CTR_Mode_ExternalCipher::Decryption ctrDecryption(aesDecryption, iv); StreamTransformationFilter stfDecryptor( ctrDecryption, new StringSink(decrypted)); stfDecryptor.Put((const byte*)&encrypted[0], encrypted.size()); stfDecryptor.MessageEnd(); std::cout << "Message: " << message << std::endl; std::cout << "Recovered: " << decrypted << std::endl; return 0; }
Running the program results in the following.
$ ./test.exe Message: Now is the time for all good men to come to the aide of their country. Recovered: Now is the time for all good men to come to the aide of their country.
Downloads
IDEA-CTR-Filter.zip - Demonstrates encryption and decryption using IDEA in CTR mode with filters
Blowfish-CTR-Filter.zip - Demonstrates encryption and decryption using Blowfish in CTR mode with filters
Twofish-CTR-Filter.zip - Demonstrates encryption and decryption using Twofish in CTR mode with filters
AES-CTR-Filter.zip - Demonstrates encryption and decryption using AES in CTR mode with filters
Serpent-CTR-Filter.zip - Demonstrates encryption and decryption using Serpent in CTR mode with filters
Camellia-CTR-Filter.zip - Demonstrates encryption and decryption using Camellia in CTR mode with filters