ChaCha20

From Crypto++ Wiki
Jump to navigation Jump to search
ChaCha20
Documentation
#include <cryptopp/chacha.h>

ChaCha is a family of stream ciphers by Daniel J. Bernstein based on a variant of Salsa20. Also see ChaCha, a variant of Salsa20.

The 20-round stream cipher ChaCha/20 is consistently faster than AES and is recommended by the designer for typical cryptographic applications. The reduced-round ciphers ChaCha/12 and ChaCha/8 are among the fastest 256-bit stream ciphers available and are recommended for applications where speed is more important than confidence.

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 7539, ChaCha20 and Poly1305 for IETF Protocols and available as ChaChaTLS.

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.

ChaCha20 AVX2 implementation had a bug related to carries. On occasion the tail of the cipher text would be incorrect. The bug appeared to be relatively rare when the cpu had AVX2, and it did not appear in other implementations like SSE or NEON. The bug was fixed in Crypto++ 8.6. Also see Issue 1069 and Commit 20962baf4440.

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.

Key and IV sizes

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

int main()
{
    using namespace CryptoPP;

    ChaCha::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.

<console>$ ./test.exe key length: 32 key length (min): 16 key length (max): 32 iv size: 8</console>

Encryption and Decryption

The following example shows you how to use ChaCha::Encryption and ChaCha::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(8);
    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
    ChaCha::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;

    ChaCha::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: F21CD8583F951808A01C16963AC4AD23FC356625D9BACE17825FDEC3BBA1A932
IV: 7BAD60605518A681
Plain: My Plaintext!! My Dear plaintext!!
Cipher: B942A12358DA90A33581BE13CB17BEFA2C37FBA40FDD3A1D42AC0778B824F25F8F85
Recovered: My Plaintext!! My Dear plaintext!!

Resynchronizing

The ChaCha 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(8);
    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
    ChaCha::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;

    // ChaCha::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: A636E0F7E4053DBCDD26F86377EAC4A156D85C0608728BD60EDCFE7DE5969A01
IV: AAB018DBCF485646
Plain: My Plaintext!! My Dear plaintext!!
Cipher: 7327EF99A66F1E4B09910B2DA3F2AB3B508E0EA6AC35DB916D31927DA214A707BEB3
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[16] = "012345678901234";
    const uint8_t chachaIV[8] = "0123456";

    ChaCha::Encryption enc;
    ChaCha::Decryption dec;
    enc.SetKeyWithIV(chachaKey, 16, chachaIV, 8);
    dec.SetKeyWithIV(chachaKey, 16, chachaIV, 8);

    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 ChaCha20 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(8);
    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
    ChaCha::Encryption enc;    
    enc.SetKeyWithIV(key, key.size(), iv, iv.size());

    // Decryption object
    ChaCha::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!!

Rounds

The examples above use ChaCha with 20 rounds. 20 rounds is the default configuration and nothing special needs to be done for it. To use a different number of rounds then pass a Rounds parameter using a NameValuePairs when configuring the ChaCha 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(8);
    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(Name::Rounds(), 12)
                             (Name::IV(), ConstByteArrayParameter(iv, 8));

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

    // Decryption object
    ChaCha::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;
}