Init-Update-Final
Many libraries, like Java and OpenSSL, provide an Init-Update-Final model for transformations. While its not readily apparent, Crypto++ provides the same interface by way of BufferedTransformation
and its Put
and Get
functions. This page will show you how to explicitly provide an Init-Update-Final interface with Crypto++ objects.
The JavaCipher
class below exposes a Java-like Init-Update-Final interface. The class is a bastardization of what Java offers, but it includes a minimally featured getInstance
, init
, update
and final
. In addition, a JavaAlgorithmParameter
is provided to allow init
to be called similar to Java's Cipher::init
. Finally, a driver program is provided to show how the JavaCipher
behaves.
Internally, JavaCipher
holds two Crypto++ object pointers. The first is a SymmetricCipher
and it provides the actual encryption or decryption of the data, but that's all it does. A second object is used to layer services like buffering and padding on top of the encryption and decryption. The second object is a StreamTransformationFilter
.
The JavaCipher
class does not track state, so you can probably produce unexpected results, if you try. For example, you can call update
after final
with bytes remaining in the filter from the previous encryption.
Init-Update-Final
The JavaCipher
class has four functions to provide the Init-Update-Final interface. They are:
getInstance
- requests the transformationinit
- initializes the transformationupdate
- inputs plain text to the transformation, and retrieves cipher text from the transformationfinal
- finalizes the plain text and retrieves cipher text from the transformation
getInstance
returns a pointer to a new JavaCipher
object. Crypto++ cannot instantiate an object yet because it needs more information (namely, the cipher's direction). The string transformation
is saved privately for use later in init
.
init
instantiates a concrete SymmetricCipher
for encryption and decryption; and it instantiates a StreamTransformationFilter
for buffering and padding. It also keys the cipher using the key and initialization vector provided in the AlgorithmParameter
.
update
inserts plain text bytes for encryption, and retrieves cipher text when available. If there are not enough plain text bytes to encrypt, then the StreamTransformationFilter
buffers the input. If there is no output buffer to retrieve an encrypted block, then the StreamTransformationFilter
buffers the output.
final
flushes plain text that was buffered for encryption, adds padding and then buffers the encrypted output. If the output buffer is large enough, all the cipher text is passed back to the caller.
To ensure all remaining bytes are retrieved in final
, provide a buffer that is at least 2*BLOCKSIZE
. In the case of TripleDES with a 64-bit (8 byte) block size in CBC mode, use a 16 byte buffer; and for AES with a 128-bit (16 byte) block size in CBC mode, use a 32 byte buffer. CTR mode encrypts 1-for-1, and it does not have block requirements.
Driver Program
The driver program below gets an instance of an AES cipher via getInstance
, initializes it using init
, and then feeds plain text bytes one at a time using update
. As encrypted blocks become available, the driver fetches them through update
. Finally, a call to final
is made to finish the cipher, add padding, and flush the remaining bytes buffered by the filter.
Though the code below pushes one byte at a time, the same technique can be used to encrypt a large number of bytes from a file. In the large file case, you will encrypt 512 or 4096 bytes at a time rather than one byte. In addition, in between calls to update
, you can revise the status of a progress bar for the operation.
The HexEncoder
is used to pretty print the output of the raw bytes.
byte key[32], iv[16]; OS_GenerateRandomBlock(false, key, COUNTOF(key)); OS_GenerateRandomBlock(false, iv, COUNTOF(iv)); HexEncoder encoder(new FileSink(cout)); JavaAlgorithmParameter params; params.key = key; params.ksize = COUNTOF(key); params.iv = iv; params.vsize = COUNTOF(iv); JavaCipher* cipher = JavaCipher::getInstance("AES/CBC/PKCSPadding"); cipher->init(ENCRYPT_MODE, params); cout << "Algorithm: " << cipher->getAlgorithm() << endl; cout << "Key: "; encoder.Put(key, COUNTOF(key)); cout << endl; cout << "IV: "; encoder.Put(iv, COUNTOF(iv)); cout << endl; byte buffer[64]; size_t ready = 0; for(unsigned int i = 0; i <= 255; i++) { byte b = (byte)i; cout << "Put 0x"; encoder.Put(b); cout << endl; ready = cipher->update(&b, 1, buffer, COUNTOF(buffer)); if(ready) { cout << "Get: "; encoder.Put(buffer, ready); cout << endl; } } ready = cipher->final(buffer, COUNTOF(buffer)); if(ready) { cout << "Final: "; encoder.Put(buffer, ready); cout << endl; } delete cipher;
JavaCipher
The JavaCipher
is shown below. The interesting function is init
, which instantiates the Crypto++ objects and sets the padding mode based on Java's transformation
string.
Crypto++ offers an object factory, so the if/then/else
found in init
can probably be cleaned up even further, if desired.
enum {ENCRYPT_MODE=1, DECRYPT_MODE=2}; struct JavaAlgorithmParameter { JavaAlgorithmParameter() : key(NULL), ksize(0), iv(NULL), vsize(0) {} const byte* key; size_t ksize; const byte* iv; size_t vsize; }; ///////////////////////// ///////////////////////// class JavaCipher { public: static JavaCipher* getInstance(const std::string& transformation); void init(int opmode, const JavaAlgorithmParameter& params); size_t update(const byte* in, size_t isize, byte* out, size_t osize); size_t final(byte* out, size_t osize); std::string getAlgorithm() const; protected: JavaCipher(const std::string& transformation); private: std::string m_transformation; member_ptr<SymmetricCipher> m_cipher; member_ptr<StreamTransformationFilter> m_filter; }; ///////////////////////// ///////////////////////// JavaCipher* JavaCipher::getInstance(const std::string& transformation) { return new JavaCipher(transformation); } JavaCipher::JavaCipher(const std::string& transformation) : m_transformation(transformation) { } std::string JavaCipher::getAlgorithm() const { return m_transformation; } ///////////////////////// ///////////////////////// size_t JavaCipher::final(byte* out, size_t osize) { m_filter.get()->MessageEnd(); if(!out || !osize || !m_filter.get()->AnyRetrievable()) return 0; size_t t = STDMIN(m_filter.get()->MaxRetrievable(), (word64)osize); return m_filter.get()->Get(out, t); } ///////////////////////// ///////////////////////// size_t JavaCipher::update(const byte* in, size_t isize, byte* out, size_t osize) { if(in && isize) m_filter.get()->Put(in, isize); if(!out || !osize || !m_filter.get()->AnyRetrievable()) return 0; size_t t = STDMIN(m_filter.get()->MaxRetrievable(), (word64)osize); t = m_filter.get()->Get(out, t); return t; } ///////////////////////// ///////////////////////// void JavaCipher::init(int opmode, const JavaAlgorithmParameter& params) { if(m_transformation == "AES/ECB/PKCSPadding" && opmode == ENCRYPT_MODE) { m_cipher.reset(new ECB_Mode<AES>::Encryption); m_cipher.get()->SetKey(params.key, params.ksize); m_filter.reset(new StreamTransformationFilter(*m_cipher.get(), NULL, BlockPaddingSchemeDef::PKCS_PADDING)); } else if(m_transformation == "AES/ECB/PKCSPadding" && opmode == DECRYPT_MODE) { m_cipher.reset(new ECB_Mode<AES>::Decryption); m_cipher.get()->SetKey(params.key, params.ksize); m_filter.reset(new StreamTransformationFilter(*m_cipher.get(), NULL, BlockPaddingSchemeDef::PKCS_PADDING)); } else if(m_transformation == "AES/CBC/PKCSPadding" && opmode == ENCRYPT_MODE) { m_cipher.reset(new CBC_Mode<AES>::Encryption); m_cipher.get()->SetKeyWithIV(params.key, params.ksize, params.iv); m_filter.reset(new StreamTransformationFilter(*m_cipher.get(), NULL, BlockPaddingSchemeDef::PKCS_PADDING)); } else if(m_transformation == "AES/CBC/PKCSPadding" && opmode == DECRYPT_MODE) { m_cipher.reset(new CBC_Mode<AES>::Decryption); m_cipher.get()->SetKeyWithIV(params.key, params.ksize, params.iv); m_filter.reset(new StreamTransformationFilter(*m_cipher.get(), NULL, BlockPaddingSchemeDef::PKCS_PADDING)); } else if(m_transformation == "AES/CTR/NoPadding" && opmode == ENCRYPT_MODE) { m_cipher.reset(new CTR_Mode<AES>::Encryption); m_cipher.get()->SetKeyWithIV(params.key, params.ksize, params.iv); m_filter.reset(new StreamTransformationFilter(*m_cipher.get(), NULL, BlockPaddingSchemeDef::NO_PADDING)); } else if(m_transformation == "AES/CTR/NoPadding" && opmode == DECRYPT_MODE) { m_cipher.reset(new CTR_Mode<AES>::Decryption); m_cipher.get()->SetKeyWithIV(params.key, params.ksize, params.iv); m_filter.reset(new StreamTransformationFilter(*m_cipher.get(), NULL, BlockPaddingSchemeDef::NO_PADDING)); } else throw NotImplemented(m_transformation + " is not implemented"); }
Driver Output
The following shows the output of the driver program using CBC and CTR modes of operation.
CBC Mode
Below is AES operated in CBC mode. The cipher is setup with JavaCipher* cipher = JavaCipher::getInstance("AES/CBC/PKCSPadding");
.
Notice bytes are fed in one at a time using update
. If a full block is not available for encryption, then the Crypto++ filter buffers the input. When a full encrypted block is available, update
returns it to the caller and it is displayed.
If the caller does not retrieve the encrypted block, then the filter buffers it, too.
$ ./test.exe Algorithm: AES/ECB/PKCSPadding Key: 15ECBA73B482F31033518A69D485585E4D2D927EC5BEF63A095023AFB64A6488 IV: 553BD62B3F5E104905A4E2CC0B2009BC Put 0x00 Put 0x01 ... Put 0x0E Put 0x0F Get: 07E2F449E5A53304581017D240F81E3F Put 0x10 Put 0x11 ... Put 0x1E Put 0x1F Get: 4707189A19E82EFCEC91BC3C15756EEE Put 0x20 Put 0x21 ... Put 0x2E Put 0x2F Get: 746D7A3B76AD90A0C5F470C5E6C14D63 Put 0x30 Put 0x31 ... Put 0x3E Put 0x3F Get: B5D648C9B2E1C71DABF73F3FC59BC6CF Put 0x40 Put 0x41 ... Put 0x4E Put 0x4F Get: 6C9E0F46DE3186CCD587CA7A2DD34C12 Put 0x50 Put 0x51 ... Put 0x5E Put 0x5F Get: F1FD85042FDA8500C1D48BC353AEB7BF Put 0x60 Put 0x61 ... Put 0x6E Put 0x6F Get: AC216AB4E914835C43F8C2EB0123B775 Put 0x70 Put 0x71 ... Put 0x7E Put 0x7F Get: 921CDF1AC38B7407A4640A3D9112E02A Put 0x80 Put 0x81 ... Put 0x8E Put 0x8F Get: 083A23BDBDB70E9E8FCCD0EC67C2986F Put 0x90 Put 0x91 ... Put 0x9E Put 0x9F Get: BF9AE679C366489ED93499CFEEDF9D83 Put 0xA0 Put 0xA1 ... Put 0xAE Put 0xAF Get: 5DC8B090D5F241CC86E54177F7399DA2 Put 0xB0 Put 0xB1 ... Put 0xBE Put 0xBF Get: 45221B4D549BE380679BE1000F45075C Put 0xC0 Put 0xC1 ... Put 0xCE Put 0xCF Get: 0556D818482E8B78D336DEBF59BA3D5C Put 0xD0 Put 0xD1 ... Put 0xDE Put 0xDF Get: D821857AB4CB086B21E998CEC3268F91 Put 0xE0 Put 0xE1 ... Put 0xEE Put 0xEF Get: 1A5668DEDF0D9546CF9EE826CCC416E8 Put 0xF0 Put 0xF1 ... Put 0xFE Put 0xFF Get: 3E7F3B29CED905E20DAEA75EAF7A56DB Final: 2173CEF0F16565AAE9395647960A8E74
CTR Mode
Below is AES operated in CTR mode. The cipher is setup with JavaCipher* cipher = JavaCipher::getInstance("AES/CTR/NoPadding");
.
Notice a cipher text byte is available as soon as plain text byte is input because the plaintext is XOR'd with the CTR's keystream. Its not buffered like in CBC mode. final
does not produce an encrypted block because padding is not used.
If the caller does not retrieve the encrypted byte, then the filter buffers it.
$ ./test.exe Algorithm: AES/CTR/NoPadding Key: 6DDEDEB9EE9EED8CE3F0F8EE17C470EA205A2E36B7F271BB315F88F193664AF3 IV: 68BF4AC6CC400443248437AEB166CA63 Put 0x00 Get: 50 Put 0x01 Get: 29 Put 0x02 Get: C1 Put 0x03 Get: BB ... Put 0xFC Get: 56 Put 0xFD Get: 1E Put 0xFE Get: 70 Put 0xFF Get: BA
Downloads
cryptopp-init-update-final.zip - sample classes and program to demonstrate Init-Update-Final