Compression (Zlib, Gzip, Deflate)

Compression (Zlib, Gzip, Deflate)

Headers:

  • <cryptopp/zlib.h> - ZlibCompressor, ZlibDecompressor
  • <cryptopp/gzip.h> - Gzip, Gunzip
  • <cryptopp/zdeflate.h>, <cryptopp/zinflate.h> - Deflator, Inflator (raw DEFLATE)

Namespace: CryptoPP

Since: Crypto++ 1.0 | Thread Safety: Not thread-safe (use per-thread instances)

Quick Example

#include <cryptopp/zlib.h>
#include <cryptopp/filters.h>
#include <string>
#include <iostream>

int main() {
    std::string original = "Hello, World! This is some data to compress. "
                           "Repetitive content compresses well well well well.";
    std::string compressed, decompressed;

    // Compress (compressor is a filter, use directly in pipeline)
    CryptoPP::StringSource ss1(original, true,
        new CryptoPP::ZlibCompressor(
            new CryptoPP::StringSink(compressed)
        )
    );

    // Decompress
    CryptoPP::StringSource ss2(compressed, true,
        new CryptoPP::ZlibDecompressor(
            new CryptoPP::StringSink(decompressed)
        )
    );

    std::cout << "Original size: " << original.size() << '\n';
    std::cout << "Compressed size: " << compressed.size() << '\n';
    std::cout << "Match: " << (original == decompressed ? "YES" : "NO") << '\n';

    return 0;
}

Usage Guidelines

🚫

Never compress then encrypt when an attacker can:

  1. Influence part of the plaintext (e.g., HTTP headers, user input)
  2. Observe the ciphertext length

This enables CRIME/BREACH-style attacks where attackers can recover secrets by observing compression ratios. See Security Concepts: Compression Oracles for details.

ℹ️
  • Compressing data where attacker cannot influence plaintext
  • Compressing before encryption when content is fully controlled
  • Compressing after decryption (for storage/transmission efficiency)
  • Non-encrypted compression (archives, backups)

Available Algorithms

ClassFormatUse Case
ZlibCompressor / ZlibDecompressorZlib (RFC 1950)General purpose, includes header/checksum
Gzip / GunzipGzip (RFC 1952)File compression, HTTP Content-Encoding
Deflator / InflatorRaw Deflate (RFC 1951)Custom formats, minimal overhead

All three use the same DEFLATE compression algorithm; they differ only in wrapping format (headers, checksums, metadata).

ZlibCompressor / ZlibDecompressor

Zlib format includes a header and Adler-32 checksum for integrity verification.

Constructor

// Compression
ZlibCompressor(BufferedTransformation* attachment = nullptr,
               unsigned int deflateLevel = DEFAULT_DEFLATE_LEVEL,
               unsigned int log2WindowSize = DEFAULT_LOG2_WINDOW_SIZE);

// Decompression
ZlibDecompressor(BufferedTransformation* attachment = nullptr);

Parameters

ParameterDescriptionDefault
deflateLevel0 (none) to 9 (best)6 (DEFAULT_DEFLATE_LEVEL)
log2WindowSizeWindow size: 2^n bytes (9-15)15 (32KB window)

Compression Levels

LevelNameSpeedCompression
0Store onlyFastestNone
1Best speedVery fastLow
6DefaultBalancedGood
9Best compressionSlowBest

Example: Variable Compression Levels

#include <cryptopp/zlib.h>
#include <cryptopp/filters.h>
#include <iostream>

void compareCompressionLevels(const std::string& data) {
    std::cout << "Original size: " << data.size() << " bytes\n\n";

    for (int level = 0; level <= 9; level++) {
        std::string compressed;

        CryptoPP::ZlibCompressor compressor(
            new CryptoPP::StringSink(compressed),
            level  // Compression level
        );

        CryptoPP::StringSource ss(data, true,
            new CryptoPP::Redirector(compressor)
        );
        compressor.MessageEnd();

        double ratio = 100.0 * compressed.size() / data.size();
        std::cout << "Level " << level << ": "
                  << compressed.size() << " bytes ("
                  << ratio << "%)\n";
    }
}

int main() {
    // Test with repetitive data (compresses well)
    std::string testData(10000, 'A');
    for (int i = 0; i < 10000; i += 100) {
        testData[i] = 'B';
    }

    compareCompressionLevels(testData);
    return 0;
}

Gzip / Gunzip

Gzip format is commonly used for file compression and HTTP content encoding.

Constructor

// Compression
Gzip(BufferedTransformation* attachment = nullptr,
     unsigned int deflateLevel = DEFAULT_DEFLATE_LEVEL,
     unsigned int log2WindowSize = DEFAULT_LOG2_WINDOW_SIZE);

// Decompression
Gunzip(BufferedTransformation* attachment = nullptr);

Example: Gzip File Compression

#include <cryptopp/gzip.h>
#include <cryptopp/files.h>
#include <iostream>

bool compressFile(const std::string& inputFile,
                  const std::string& outputFile) {
    try {
        CryptoPP::FileSource fs(inputFile.c_str(), true,
            new CryptoPP::Gzip(
                new CryptoPP::FileSink(outputFile.c_str()),
                CryptoPP::Gzip::MAX_DEFLATE_LEVEL  // Best compression
            )
        );
        return true;
    }
    catch (const CryptoPP::Exception& e) {
        std::cerr << "Compression error: " << e.what() << std::endl;
        return false;
    }
}

bool decompressFile(const std::string& inputFile,
                    const std::string& outputFile) {
    try {
        CryptoPP::FileSource fs(inputFile.c_str(), true,
            new CryptoPP::Gunzip(
                new CryptoPP::FileSink(outputFile.c_str())
            )
        );
        return true;
    }
    catch (const CryptoPP::Exception& e) {
        std::cerr << "Decompression error: " << e.what() << std::endl;
        return false;
    }
}

int main() {
    // Compress
    if (compressFile("document.txt", "document.txt.gz")) {
        std::cout << "File compressed successfully" << std::endl;
    }

    // Decompress
    if (decompressFile("document.txt.gz", "document_restored.txt")) {
        std::cout << "File decompressed successfully" << std::endl;
    }

    return 0;
}

Deflator / Inflator

Raw Deflate without headers - useful for custom formats or when you handle framing yourself.

Example: Raw Deflate

#include <cryptopp/zinflate.h>
#include <cryptopp/zdeflate.h>
#include <cryptopp/filters.h>
#include <string>

std::string deflateData(const std::string& input) {
    std::string output;

    CryptoPP::Deflator deflator(
        new CryptoPP::StringSink(output),
        CryptoPP::Deflator::DEFAULT_DEFLATE_LEVEL
    );

    CryptoPP::StringSource ss(input, true,
        new CryptoPP::Redirector(deflator)
    );
    deflator.MessageEnd();

    return output;
}

std::string inflateData(const std::string& compressed) {
    std::string output;

    CryptoPP::Inflator inflator(
        new CryptoPP::StringSink(output)
    );

    CryptoPP::StringSource ss(compressed, true,
        new CryptoPP::Redirector(inflator)
    );
    inflator.MessageEnd();

    return output;
}

Complete Example: Safe Compression with Encryption

This example shows a safe pattern where compression is used on fully-controlled data.

#include <cryptopp/zlib.h>
#include <cryptopp/aes.h>
#include <cryptopp/gcm.h>
#include <cryptopp/osrng.h>
#include <cryptopp/filters.h>
#include <fstream>
#include <iostream>

// SAFE: Compressing application-controlled data before encryption
// The attacker cannot influence the plaintext content
class SecureArchive {
private:
    CryptoPP::SecByteBlock key;
    CryptoPP::AutoSeededRandomPool rng;

public:
    SecureArchive() : key(32) {
        rng.GenerateBlock(key, key.size());
    }

    // Compress then encrypt (safe when content is fully controlled)
    bool saveCompressedEncrypted(const std::string& filename,
                                  const std::string& data) {
        try {
            // Step 1: Compress
            std::string compressed;
            CryptoPP::ZlibCompressor compressor(
                new CryptoPP::StringSink(compressed),
                CryptoPP::ZlibCompressor::DEFAULT_DEFLATE_LEVEL
            );
            CryptoPP::StringSource ss1(data, true,
                new CryptoPP::Redirector(compressor)
            );
            compressor.MessageEnd();

            // Step 2: Generate nonce
            CryptoPP::SecByteBlock nonce(12);
            rng.GenerateBlock(nonce, nonce.size());

            // Step 3: Encrypt compressed data
            std::string ciphertext;
            CryptoPP::GCM<CryptoPP::AES>::Encryption enc;
            enc.SetKeyWithIV(key, key.size(), nonce, nonce.size());

            CryptoPP::StringSource ss2(compressed, true,
                new CryptoPP::AuthenticatedEncryptionFilter(enc,
                    new CryptoPP::StringSink(ciphertext)
                )
            );

            // Step 4: Write [nonce || ciphertext]
            std::ofstream out(filename, std::ios::binary);
            out.write(reinterpret_cast<const char*>(nonce.data()), nonce.size());
            out.write(ciphertext.data(), ciphertext.size());

            std::cout << "Original: " << data.size() << " bytes\n";
            std::cout << "Compressed: " << compressed.size() << " bytes\n";
            std::cout << "Encrypted: " << (nonce.size() + ciphertext.size()) << " bytes\n";

            return true;
        }
        catch (const CryptoPP::Exception& e) {
            std::cerr << "Error: " << e.what() << std::endl;
            return false;
        }
    }

    // Decrypt then decompress
    bool loadCompressedEncrypted(const std::string& filename,
                                  std::string& data) {
        try {
            // Step 1: Read file
            std::ifstream in(filename, std::ios::binary);
            if (!in) return false;

            CryptoPP::SecByteBlock nonce(12);
            in.read(reinterpret_cast<char*>(nonce.data()), nonce.size());

            std::string ciphertext(
                (std::istreambuf_iterator<char>(in)),
                std::istreambuf_iterator<char>()
            );

            // Step 2: Decrypt
            std::string compressed;
            CryptoPP::GCM<CryptoPP::AES>::Decryption dec;
            dec.SetKeyWithIV(key, key.size(), nonce, nonce.size());

            CryptoPP::StringSource ss1(ciphertext, true,
                new CryptoPP::AuthenticatedDecryptionFilter(dec,
                    new CryptoPP::StringSink(compressed)
                )
            );

            // Step 3: Decompress
            CryptoPP::ZlibDecompressor decompressor(
                new CryptoPP::StringSink(data)
            );
            CryptoPP::StringSource ss2(compressed, true,
                new CryptoPP::Redirector(decompressor)
            );
            decompressor.MessageEnd();

            return true;
        }
        catch (const CryptoPP::Exception& e) {
            std::cerr << "Error: " << e.what() << std::endl;
            return false;
        }
    }

    const CryptoPP::SecByteBlock& getKey() const { return key; }
};

int main() {
    SecureArchive archive;

    // This is SAFE - we fully control the content
    std::string data = "This is application data that we fully control. "
                       "No user input or attacker-controlled content here. "
                       "Repetitive data compresses well well well well.";

    archive.saveCompressedEncrypted("archive.enc", data);

    std::string restored;
    if (archive.loadCompressedEncrypted("archive.enc", restored)) {
        std::cout << "Restored: " << restored << std::endl;
        std::cout << "Match: " << (data == restored ? "YES" : "NO") << std::endl;
    }

    return 0;
}

Dangerous Pattern: Compression Oracle

🚫
The following shows a vulnerable pattern. Never do this when attacker-controlled data is mixed with secrets.
// ❌ VULNERABLE TO COMPRESSION ORACLE ATTACK
std::string encryptRequest(const std::string& userInput,
                            const std::string& secretToken) {
    // Attacker controls userInput, wants to discover secretToken

    std::string request = "GET /api?user=" + userInput +
                          "&token=" + secretToken;  // Secret!

    // Compress - if userInput matches part of secretToken,
    // the compressed size will be SMALLER
    std::string compressed;
    CryptoPP::ZlibCompressor compressor(
        new CryptoPP::StringSink(compressed)
    );
    CryptoPP::StringSource(request, true,
        new CryptoPP::Redirector(compressor)
    );
    compressor.MessageEnd();

    // Encrypt
    std::string ciphertext;
    // ... encryption code ...

    // Attacker observes ciphertext.size()
    // If userInput = "token=A" and size is smaller than userInput = "token=B",
    // attacker learns the token starts with "A"!

    return ciphertext;
}

Attack mechanism:

  1. Attacker tries userInput = "token=A" - observes ciphertext size
  2. Attacker tries userInput = "token=B" - observes ciphertext size
  3. If “A” produces smaller output, the secret token likely starts with “A”
  4. Repeat for each character position to recover entire token

Key takeaway: If attacker-controlled input and secrets share a compressed-and-encrypted channel where the attacker can observe lengths, treat that as unsafe by default (CRIME/BREACH-style risk).

Performance

Compression Speed (approximate)

LevelSpeedUse Case
1~200 MB/sReal-time streaming
6~50 MB/sGeneral purpose
9~10 MB/sArchival, one-time compression

Actual performance depends heavily on CPU, compiler, and data characteristics; treat these numbers as rough order-of-magnitude figures.

Memory Usage

Window SizeMemoryCompression Ratio
2^9 (512B)~1 KBLower
2^12 (4KB)~8 KBModerate
2^15 (32KB)~64 KBBest

Error Handling

#include <cryptopp/zlib.h>
#include <cryptopp/filters.h>
#include <iostream>

bool safeDecompress(const std::string& compressed, std::string& output) {
    try {
        CryptoPP::ZlibDecompressor decompressor(
            new CryptoPP::StringSink(output)
        );

        CryptoPP::StringSource ss(compressed, true,
            new CryptoPP::Redirector(decompressor)
        );
        decompressor.MessageEnd();

        return true;
    }
    catch (const CryptoPP::ZlibDecompressor::Err& e) {
        std::cerr << "Zlib error: " << e.what() << std::endl;
        return false;
    }
    catch (const CryptoPP::Exception& e) {
        std::cerr << "Crypto++ error: " << e.what() << std::endl;
        return false;
    }
}

Common Exceptions

ExceptionCause
ZlibDecompressor::ErrInvalid compressed data, checksum mismatch
Inflator::UnexpectedEndErrTruncated compressed data
Inflator::BadBlockErrCorrupted block in compressed stream

Thread Safety

Compression objects are not thread-safe. Use per-thread instances:

// CORRECT - per-thread compressor
void processThread(const std::string& data) {
    CryptoPP::ZlibCompressor compressor;  // Thread-local
    // ... use compressor ...
}

// WRONG - shared compressor
CryptoPP::ZlibCompressor globalCompressor;  // Race condition!

See Also