Salsa20

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

Salsa20 is a stream cipher by Daniel J. Bernstein and part of eSTREAM portfolio Phase 3 (final) for Profile 1 (software). The 20-round stream cipher Salsa20/20 is consistently faster than AES and is recommended by the designer for typical cryptographic applications. The reduced-round ciphers Salsa20/12 and Salsa20/8 are among the fastest 256-bit stream ciphers available and are recommended for applications where speed is more important than confidence. Also see The Salsa20 family of stream ciphers.

Salsa20 provides a 8, 12 or 20 rounds. The default number of rounds is 20. The sample program below shows you how to change the number of rounds.

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.

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 Salsa20's key and iv sizes.

int main()
{
    using namespace CryptoPP;

    Salsa20::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): 16
key length (max): 32
iv size: 8

Encryption and Decryption

The following example shows you how to use Salsa20::Encryption and Salsa20::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 "salsa.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("Salsa20 stream cipher test"), cipher, recover;

    SecByteBlock key(16), 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
    Salsa20::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;

    Salsa20::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: 9692FFCDAD67D6D1FE4575147AD3514F
IV: 4C7D97FDFEC42D8F
Plain: Salsa20 stream cipher test
Cipher: 0E5DC8BE2D07E868E7880D6607EBF13B7CCCB74804D8247DE279
Recovered: Salsa20 stream cipher test

Resynchronizing

The Salsa 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 "salsa.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("Salsa20 stream cipher test"), cipher, recover;

    SecByteBlock key(16), 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
    Salsa20::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;

    // Salsa20::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.

$ ./test.exe
Key: 0C7300E07D8E607A0AD3781C9D4E46DA
IV: CE1A0D2126033712
Plain: Salsa20 stream cipher test
Cipher: B45F2EAB9F591B3F959DB9CFB6B6BAE92CE43A05D35A06322761
Self inverting: 1
Resynchronizable: 1
Recovered: Salsa20 stream cipher test

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 "salsa.h"

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

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

    const uint8_t salsaKey[16] = "012345678901234";
    const uint8_t salsaIV[8] = "0123456";

    Salsa20::Encryption enc;
    Salsa20::Decryption dec;
    enc.SetKeyWithIV(salsaKey, 16, salsaIV, 8);
    dec.SetKeyWithIV(salsaKey, 16, salsaIV, 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(salsaIV, sizeof(salsaIV));
    dec.ProcessData(decrypt.data(), encrpyt.data(), encrpyt.size());

    dec.Resynchronize(salsaIV, sizeof(salsaIV));
    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 Salsa20 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 "salsa.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("Salsa20 stream cipher test"), cipher, recover;

    SecByteBlock key(16), 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
    Salsa20::Encryption enc;    
    enc.SetKeyWithIV(key, key.size(), iv, iv.size());

    // Decryption object
    Salsa20::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: 611F2AB2A64EE564691A67742B7B7ECB
IV: B9214F705114FDEB
Plain: Salsa20 stream cipher test
Cipher: 3F8FDDB34834DCA55D3C14B227C5C0C8C17461994A16E92F569A
Recovered: Salsa20 stream cipher test

Rounds

If you need to set the number of rounds you have two choices. First, you can use SetKeyWithRounds and then call Resynchronize to set the IV. Second, you can use NameValuePairs and SetKey. Below is an example of the second method.

SecByteBlock key(16), iv(8);
...

AlgorithmParameters params = MakeParameters("Rounds", 12)
        ("IV", ConstByteArrayParameter(iv, iv.size()));

Salsa20::Encryption salsa;
salsa.SetKey(key, key.size(), params);