XTS Mode

From Crypto++ Wiki
Jump to navigation Jump to search
XTS Mode
Documentation
#include <cryptopp/xts.h>

XTS Mode is a block cipher mode of operation intended for use on storage devices to encrypt disk sectors. XTS mode was originally specified by the IEEE in P1619, and NIST standardized it in SP800-38E.

XTS is only defined when using AES, but Crypto++ allows wider block ciphers if you build the library with CRYPTOPP_XTS_WIDE_BLOCK_CIPHERS defined to non-zero. Be careful when using XTS with wider block ciphers. According to Phillip Rogaway in Evaluation of Some Blockcipher Modes of Operation, the narrow width of the underlying PRP and the poor treatment of fractional final blocks are problems.

The mode does not require a multiple of a block size or padding, which means there is no cipher text expansion. XTS does require at least 16 bytes of plain text. The 16-byte requirement comes from the fact the mode steals cipher text from a previous encrypted block when there are tail bytes present.

XTS mode provides confidentiality only. The mode lacks an authentication tag to detect tampering. Some of the risk is eliminated because the encrypted data is in local storage, and not sent across the network.

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 consider using an algorithm or mode like CCM, GCM, EAX, ChaCha20Poly1305 or XChaCha20Poly1305.

Sample Program

There are three sample programs for XTS mode. The samples use filters in a pipeline. The first dumps the key sizes. The second sample shows the basic use of a pipeline. The third shows how to manually insert into a filter. Pipelining is a high level abstraction and it handles buffering input, buffering output and padding for you.

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.

The first program below shows key lengths and block sizes.

XTS<AES>::Encryption enc;
cout << "key length: " << enc.DefaultKeyLength() << endl;
cout << "key length (min): " << enc.MinKeyLength() << endl;
cout << "key length (max): " << enc.MaxKeyLength() << endl;
cout << "block size: " << enc.BlockSize() << endl;

Running the program results in the following.

key length: 32
key length (min): 32
key length (max): 64
block size: 16

The second program below demonstrates using AES/XTS in a pipeline.

using namespace CryptoPP;

AutoSeededRandomPool prng;

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

std::string plain = "XTS Mode Test";
std::string cipher, encoded, recovered;

/*********************************\
\*********************************/

try
{
    std::cout << "plain text: " << plain << std::endl;

    XTS_Mode< AES >::Encryption enc;
    enc.SetKeyWithIV( key, key.size(), iv );

    // The StreamTransformationFilter adds padding
    //  as requiredec. ECB and XTS Mode must be padded
    //  to the block size of the cipher.
    StringSource ss( plain, true, 
        new StreamTransformationFilter( enc,
            new StringSink( cipher ),
            StreamTransformationFilter::NO_PADDING
        ) // StreamTransformationFilter      
    ); // StringSource
}
catch( const CryptoPP::Exception& ex )
{
    std::cerr << ex.what() << std::endl;
    exit(1);
}

/*********************************\
\*********************************/

// Pretty print cipher text
StringSource ss( cipher, true,
    new HexEncoder(
        new StringSink( encoded )
    ) // HexEncoder
); // StringSource
std::cout << "cipher text: " << encoded << std::endl;

/*********************************\
\*********************************/

try
{
    XTS_Mode< AES >::Decryption dec;
    dec.SetKeyWithIV( key, key.size(), iv );

    // The StreamTransformationFilter removes
    //  padding as requiredec.
    StringSource ss( cipher, true, 
        new StreamTransformationFilter( dec,
            new StringSink( recovered ),
            StreamTransformationFilter::NO_PADDING
        ) // StreamTransformationFilter
    ); // StringSource

    std::cout << "recovered text: " << recovered << std::endl;
}
catch( const CryptoPP::Exception& ex )
{
    std::cerr << ex.what() << std::endl;
    exit(1);
}

A typical output is shown below. Note that each run will produce different results because the key and initialization vector are randomly generated.

$ ./test.exe
plain text: AES/XTS block cipher test
key: 5ED5A947D474DD2C763E537C82BCB9439AB05BD4D57DFA1D549F994F3AE3A5EE
 iv: E063B675FC03F687B50329B36251E725
cipher text: 69D1412A1BCBC90618A4976BAD5D1793549C7107032F332815
recovered text: AES/XTS block cipher test

When using a StreamTransformationFilter, the filter will pad the plain text as required. XTS does not need padding so you should use the NOPADDING flag.

StringSource ss( plain, true, 
    new StreamTransformationFilter( e,
        new StringSink( cipher ),
        StreamTransformationFilter::NO_PADDING
    ) // StreamTransformationFilter      
); // StringSource

If you use a short string for the message, then the XTS object will throw an InvalidArgument exception.

$ ./test.exe
plain text: XTS mode test
XTS: message is too short for ciphertext stealing

To manually insert bytes into the filter, perform multiple Puts. 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);
...

XTS_Mode < AES >::Encryption encryption(key, key.size(), 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());

Downloads

xtstest.zip - Demonstrates encryption and decryption using AES in XTS mode with filters