ChaChaTLS

From Crypto++ Wiki
Jump to: navigation, search

ChaChaTLS is a modified version of Bernstein's ChaCha used in IETF networking protocols including IPSec and TLS. Crypto++ provides the IETF modified algorithm by way of the ChaChaTLS class. ChaChaTLS only offers a 32-byte key with a 12-byte nonce and 20 rounds. Also see RFC 8439, ChaCha20 and Poly1305 for IETF Protocols.

Crypto++ provides all stream ciphers from eSTREAM Phase 3 for Profile 1. The ciphers are ChaCha, HC-128/256, Rabbit, Salsa20 and Sosemanuk. The IETF's version of ChaCha is specified in RFC 8439, ChaCha20 and Poly1305 for IETF Protocols and available as ChaChaTLS.

The library also provides or ChaCha20Poly1305. ChaCha20Poly1305 is a authenticated encryption scheme and creates an authentication tag over the ciphertext to provide authenticity assurances. ChaCha20Poly1305 is also specified in RFC 8439, ChaCha20 and Poly1305 for IETF Protocols.

If you are used to working in languages like Jave 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 understand why you should consider using a block cipher and mode like CCM, GCM, EAX or ChaCha20Poly1305.

Key and IV sizes

The first sample program prints ChaChaTLS's key and iv sizes.

int main()
{
    using namespace CryptoPP;

    ChaChaTLS::Encryption enc;
    std::cout << "key length: " << enc.DefaultKeyLength() << std::endl;
    std::cout << "key length (min): " << enc.MinKeyLength () << std::endl;
    std::cout << "key length (max): " << enc.MaxKeyLength () << std::endl;
    std::cout << "iv size: " << enc.IVSize() << std::endl;

    return 0;
}

A typical output is shown below.

$ ./test.exe
key length: 32
key length (min): 32
key length (max): 32
iv size: 12

Encryption and Decryption

The following example shows you how to use ChaChaTLS::Encryption and ChaChaTLS::Decryption. &cipher[0] may look odd, but its how to get the non-const pointer from a std::string.

#include "cryptlib.h"
#include "secblock.h"
#include "chacha.h"
#include "osrng.h"
#include "files.h"
#include "hex.h"

#include <iostream>
#include <string>

int main()
{
    using namespace CryptoPP;

    AutoSeededRandomPool prng;
    std::string plain("My Plaintext!! My Dear plaintext!!"), cipher, recover;

    SecByteBlock key(32), iv(12);
    prng.GenerateBlock(key, key.size());
    prng.GenerateBlock(iv, iv.size());

    std::cout << "Key: ";
    encoder.Put((const byte*)key.data(), key.size());
    encoder.MessageEnd();
    std::cout << std::endl;

    std::cout << "IV: ";
    encoder.Put((const byte*)iv.data(), iv.size());
    encoder.MessageEnd();
    std::cout << std::endl;

    // Encryption object
    ChaChaTLS::Encryption enc;    
    enc.SetKeyWithIV(key, key.size(), iv, iv.size());

    // Perform the encryption
    cipher.resize(plain.size());
    enc.ProcessData((byte*)&cipher[0], (const byte*)plain.data(), plain.size());

    std::cout << "Plain: " << plain << std::endl;

    std::cout << "Cipher: ";
    encoder.Put((const byte*)cipher.data(), cipher.size());
    encoder.MessageEnd();
    std::cout << std::endl;

    ChaChaTLS::Decryption dec;
    dec.SetKeyWithIV(key, key.size(), iv, iv.size());

    // Perform the decryption
    recover.resize(cipher.size());
    dec.ProcessData((byte*)&recover[0], (const byte*)cipher.data(), cipher.size());

    std::cout << "Recovered: " << recover << std::endl;

    return 0;
}

A typical output is shown below, including the non-printable characters from encryption.

$ ./test.exe
Key: 844A37176AC8A133AADBAA5ACD899D2FD10DEAEAF34364A17E7131F0FD1CFA42
IV: BE3CFFFF0211553A64B28B58
Plain: My Plaintext!! My Dear plaintext!!
Cipher: 12E65CFFDC0F1CCC591113163047B8FB61EAEE270029CD83BA7D5F88F151620C6C59
Recovered: My Plaintext!! My Dear plaintext!!

Resynchronizing

The ChaChaTLS family is self-inverting so you can use the encryption object for decryption (and vice versa). The cipher holds internal state and is resynchronizable. If you want to reuse an encryption or decryption object then you should set the IV with Resynchronize.

#include "cryptlib.h"
#include "secblock.h"
#include "chacha.h"
#include "osrng.h"
#include "files.h"
#include "hex.h"

#include <iostream>
#include <string>

int main()
{
    using namespace CryptoPP;

    AutoSeededRandomPool prng;
    HexEncoder encoder(new FileSink(std::cout));
    std::string plain("My Plaintext!! My Dear plaintext!!"), cipher, recover;

    SecByteBlock key(32), iv(12);
    prng.GenerateBlock(key, key.size());
    prng.GenerateBlock(iv, iv.size());

    std::cout << "Key: ";
    encoder.Put((const byte*)key.data(), key.size());
    encoder.MessageEnd();
    std::cout << std::endl;

    std::cout << "IV: ";
    encoder.Put((const byte*)iv.data(), iv.size());
    encoder.MessageEnd();
    std::cout << std::endl;

    // Encryption object
    ChaChaTLS::Encryption enc;    
    enc.SetKeyWithIV(key, key.size(), iv, iv.size());

    // Perform the encryption
    cipher.resize(plain.size());
    enc.ProcessData((byte*)&cipher[0], (const byte*)plain.data(), plain.size());

    std::cout << "Plain: " << plain << std::endl;

    std::cout << "Cipher: ";
    encoder.Put((const byte*)cipher.data(), cipher.size());
    encoder.MessageEnd();
    std::cout << std::endl;

    // ChaChaTLS::Decryption dec;
    // dec.SetKeyWithIV(key, key.size(), iv, iv.size());

    std::cout << "Self inverting: " << enc.IsSelfInverting() << std::endl;
    std::cout << "Resynchronizable: " << enc.IsResynchronizable() << std::endl;

    enc.Resynchronize(iv, iv.size());

    // Perform the decryption
    // recover.resize(cipher.size());
    // dec.ProcessData((byte*)&recover[0], (const byte*)cipher.data(), cipher.size());

    // Perform the decryption with the encryptor
    recover.resize(cipher.size());
    enc.ProcessData((byte*)&recover[0], (const byte*)cipher.data(), cipher.size());

    std::cout << "Recovered: " << recover << std::endl;

    return 0;
}

A typical output is shown below, including the non-printable characters from encryption.

$ ./test.exe
Key: 8A898F142FDF4513A33FA26FA5D2846CDE7FE814AED04577B4C48A42049B0D78
IV: 89FD1C52A0A91A20424108CF
Plain: My Plaintext!! My Dear plaintext!!
Cipher: C8F5951284C6C543D8EE8D48EC10DC19512D29E6D662C81F9668D68E539D4F015CC0
Self inverting: 1
Resynchronizable: 1
Recovered: My Plaintext!! My Dear plaintext!!

The following C++11 program demonstrates resynchronizing without the additional operations like printing a key or iv. The library was built with CXXFLAGS="-DNDEBUG -g2 -O3 -std=c++11.

#include "cryptlib.h"
#include "chacha.h"

#include <iostream>
#include <array>
#include <cstdint>

int main(int argc, char *argv[])
{
    using namespace CryptoPP;

    const uint8_t chachaKey[32] = "0123456789012345678901234";
    const uint8_t chachaIV[12] = "01234567890";

    ChaChaTLS::Encryption enc;
    ChaChaTLS::Decryption dec;
    enc.SetKeyWithIV(chachaKey, 32, chachaIV, 12);
    dec.SetKeyWithIV(chachaKey, 32, chachaIV, 12);

    std::array<byte, 3> origin = { 1,2,3 };
    std::array<byte, 3> encrpyt;
    enc.ProcessData(encrpyt.data(), origin.data(), origin.size());

    std::array<byte, 3> decrypt;
    dec.ProcessData(decrypt.data(), encrpyt.data(), encrpyt.size());

    dec.Resynchronize(chachaIV, sizeof(chachaIV));
    dec.ProcessData(decrypt.data(), encrpyt.data(), encrpyt.size());

    dec.Resynchronize(chachaIV, sizeof(chachaIV));
    dec.ProcessData(decrypt.data(), encrpyt.data(), encrpyt.size());
    
    std::cout << (int)decrypt[0] << " " << (int)decrypt[1] << " ";
    std::cout << (int)decrypt[2] << std::endl;
    
    return 0;
}

It produces the following result.

$ g++ -DNDEBUG -g2 -O3 -std=c++11 test.cxx -o test.exe ./libcryptopp.a
$ ./test.exe
1 2 3

Pipelines

You can also use stream ciphers in a Pipeline. Below is an example of ChaChaTLS participating in a pipeline. Internally, StreamTransformationFilter calls ProcessData on the incoming data stream. The filter also buffers output if there is no attached transformation or sink.

#include "cryptlib.h"
#include "secblock.h"
#include "filters.h"
#include "chacha.h"
#include "osrng.h"
#include "files.h"
#include "hex.h"

#include <iostream>
#include <string>

int main()
{
    using namespace CryptoPP;

    AutoSeededRandomPool prng;
    HexEncoder encoder(new FileSink(std::cout));
    std::string plain("My Plaintext!! My Dear plaintext!!"), cipher, recover;

    SecByteBlock key(32), iv(12);
    prng.GenerateBlock(key, key.size());
    prng.GenerateBlock(iv, iv.size());

    std::cout << "Key: ";
    encoder.Put(key.data(), key.size());
    encoder.MessageEnd();
    std::cout << std::endl;

    std::cout << "IV: ";
    encoder.Put(iv.data(), iv.size());
    encoder.MessageEnd();
    std::cout << std::endl;

    // Encryption object
    ChaChaTLS::Encryption enc;    
    enc.SetKeyWithIV(key, key.size(), iv, iv.size());

    // Decryption object
    ChaChaTLS::Decryption dec;    
    dec.SetKeyWithIV(key, key.size(), iv, iv.size());

    StringSource ss1(plain, true, new StreamTransformationFilter(enc, new StringSink(cipher)));
    StringSource ss2(cipher, true, new StreamTransformationFilter(dec, new StringSink(recover)));

    std::cout << "Plain: " << plain << std::endl;

    std::cout << "Cipher: ";
    encoder.Put((const byte*)cipher.data(), cipher.size());
    encoder.MessageEnd();
    std::cout << std::endl;

    std::cout << "Recovered: " << recover << std::endl;

    return 0;
}

The program produces the expected output:

$ ./test.exe
Key: 784175D9D0E9C5DD30EE0D8F4580EED256BF39DF1E425CF8621155D3636C38CE
IV: A85CFCD4DFBB47F1
Plain: My Plaintext!! My Dear plaintext!!
Cipher: FA51C2BD26615D7CE04A97553E35E3A849419D60B7B5711586DC20611B86B08E0082
Recovered: My Plaintext!! My Dear plaintext!!

Initial Block Counter

The examples above use ChaChaTLS with and initial block counter of 0. A counter of 0 is the default configuration and nothing special needs to be done for it. RFC 8439, Section 2.4 allows an arbitrary initial counter block. Since the counter block is 32-bits, the value can be 0 to 0xffffffff.

To use a different initial block counter then pass a InitialBlock parameter using a NameValuePairs when configuring the ChaChaTLS object.

#include "cryptlib.h"
#include "secblock.h"
#include "algparam.h"
#include "argnames.h"
#include "filters.h"
#include "chacha.h"
#include "osrng.h"
#include "files.h"
#include "hex.h"

#include <iostream>
#include <string>

int main()
{
    using namespace CryptoPP;

    AutoSeededRandomPool prng;
    HexEncoder encoder(new FileSink(std::cout));
    std::string plain("My Plaintext!! My Dear plaintext!!"), cipher, recover;

    SecByteBlock key(32), iv(12);
    prng.GenerateBlock(key, key.size());
    prng.GenerateBlock(iv, iv.size());

    std::cout << "Key: ";
    encoder.Put(key.data(), key.size());
    encoder.MessageEnd();
    std::cout << std::endl;

    std::cout << "IV: ";
    encoder.Put(iv.data(), iv.size());
    encoder.MessageEnd();
    std::cout << std::endl;

    // Additional parameters to configure object
    const AlgorithmParameters params = MakeParameters("InitialBlock", 0x0000ffff)
                             (Name::IV(), ConstByteArrayParameter(iv, 12));

    // Encryption object
    ChaChaTLS::Encryption enc;    
    enc.SetKey(key, key.size(), params);

    // Decryption object
    ChaChaTLS::Decryption dec;    
    dec.SetKey(key, key.size(), params);

    StringSource ss1(plain, true, new StreamTransformationFilter(enc, new StringSink(cipher)));
    StringSource ss2(cipher, true, new StreamTransformationFilter(dec, new StringSink(recover)));

    std::cout << "Plain: " << plain << std::endl;

    std::cout << "Cipher: ";
    encoder.Put((const byte*)cipher.data(), cipher.size());
    encoder.MessageEnd();
    std::cout << std::endl;

    std::cout << "Recovered: " << recover << std::endl;

    return 0;
}

The initial counter block is latched as the default value for the object. If you call Resynchronize then the counter block is reset to the value passed in during SetKey. If you want to change the default value, then you need to call SetKey again.

Sharp Edges

ChaChaTLS has some sharp edges, just like other ciphers. Both ChaCha and ChaChaTLS require the application rekey at 232 blocks or 256 GB. However, unlike ChaCha, ChaChaTLS allows (1) the user to use an arbitrary initial block counter; and (2) uses a single 32-bit block counter. Combined they wander into areas that are unspecified by the RFC.

Bernstein's ChaCha uses a 64-bit word for the counter block and it cannot overflow for all intents and purposes. In addition Bernstein did not specify an initial counter block value other than 0. ChaChaTLS can overflow quite easily by setting the initial counter block to 0xfffffffe and then encrypting or decrypting 256-bytes. The problem is, the RFC does not say what is supposed to happen in this case.

Several things could happen in this case. They include wrapping the block counter, setting the counter to 0 and carrying into the nonce, setting the counter to 0 and re-keying, or throwing an exception. The Crypto++ library silently allows the counter to wrap in Release builds, and fires an assert in Debug builds. The application is responsible for managing the parameters. If the application does not want to wrap then it must perform the book keeping and take the appropriate action.

Another sharp edge is, we did not have a reference implementation to work from. When we needed additional test vectors we hacked Bernstein's reference implementation of ChaCha. Our hacks included a wrap on the block counter without the carry, and it could be wrong. We hope the IETF will publish a reference implementation so we can use a program with a pedigree that will provide provenance for the additional test vectors.

Also see Issue 790, ChaChaTLS results when counter block wraps and How to handle block counter wrap in IETF's ChaCha algorithm? on the CFRG mailing list.