Poly1305

Header: #include <cryptopp/poly1305.h> | Namespace: CryptoPP Since: Crypto++ 5.7 Thread Safety: Not thread-safe per instance; use separate instances per thread

Poly1305 is a high-speed one-time authenticator designed by Daniel J. Bernstein. It’s used as the authentication component in ChaCha20-Poly1305 (IETF RFC 8439) and provides 128-bit security with exceptional performance. Poly1305 is a “one-time” MAC, meaning each key must only be used once.

⚠️
Important: In Crypto++, standalone Poly1305 is rarely used directly. The recommended approach is to use ChaCha20Poly1305 which handles key derivation automatically and safely. The Poly1305TLS class is available for IETF-style Poly1305-AES constructions.

Quick Example (ChaCha20-Poly1305 - Recommended)

#include <cryptopp/chachapoly.h>
#include <cryptopp/osrng.h>
#include <cryptopp/filters.h>

using namespace CryptoPP;

AutoSeededRandomPool rng;

// Long-term key (32 bytes)
SecByteBlock key(ChaCha20Poly1305::DEFAULT_KEYLENGTH);
rng.GenerateBlock(key, key.size());

// Unique nonce per message (12 bytes)
byte nonce[12];
rng.GenerateBlock(nonce, sizeof(nonce));

std::string message = "Message to authenticate and encrypt";
std::string ciphertext;

// Encrypt and authenticate in one operation
ChaCha20Poly1305::Encryption enc;
enc.SetKeyWithIV(key, key.size(), nonce, sizeof(nonce));

StringSource(message, true,
    new AuthenticatedEncryptionFilter(enc,
        new StringSink(ciphertext)
    )
);
// ciphertext includes 16-byte Poly1305 tag

Usage Guidelines

ℹ️
  • Use ChaCha20-Poly1305 for combined encryption + authentication
  • For raw Poly1305, use a fresh one-time key per message
  • For ChaCha20-Poly1305, use a fresh nonce per message (the library derives a one-time Poly1305 key internally)
⚠️
  • CRITICAL: Never reuse a Poly1305 key for multiple messages
  • Don’t use standalone Poly1305 without understanding one-time semantics
  • Don’t use for password hashing (use Argon2)
  • Prefer ChaCha20-Poly1305 AEAD over standalone Poly1305

Why One-Time?

Poly1305 is mathematically designed to be used with a fresh key per message. If you reuse a key, an attacker with two MACs from the same key can forge MACs for arbitrary messages.

Solution: Use ChaCha20-Poly1305, which derives unique Poly1305 keys internally:

// CORRECT - ChaCha20-Poly1305 handles key derivation
ChaCha20Poly1305::Encryption enc;
enc.SetKeyWithIV(key, 32, nonce, 12);  // Unique nonce per message
// Poly1305 key is derived from (key, nonce) - safe to reuse key with different nonces

Crypto++ Poly1305 Classes

Crypto++ provides several Poly1305 implementations:

ClassUse Case
ChaCha20Poly1305Recommended - Combined AEAD cipher
XChaCha20Poly1305Extended nonce variant (192-bit nonce)
Poly1305TLSIETF Poly1305-AES for TLS

ChaCha20Poly1305 Constants

ChaCha20Poly1305::DEFAULT_KEYLENGTH   // 32 bytes
ChaCha20Poly1305::IV_LENGTH           // 12 bytes (nonce)
ChaCha20Poly1305::DIGESTSIZE          // 16 bytes (tag)

Complete Examples

Example 1: ChaCha20-Poly1305 Encryption & Authentication

#include <cryptopp/chachapoly.h>
#include <cryptopp/osrng.h>
#include <cryptopp/filters.h>
#include <iostream>

int main() {
    using namespace CryptoPP;

    AutoSeededRandomPool rng;

    // Long-term key (can be reused with different nonces)
    SecByteBlock key(ChaCha20Poly1305::DEFAULT_KEYLENGTH);  // 32 bytes
    rng.GenerateBlock(key, key.size());

    // Unique nonce per message
    byte nonce[12];
    rng.GenerateBlock(nonce, sizeof(nonce));

    std::string plaintext = "Secret message";
    std::string ciphertext, recovered;

    // Encrypt and authenticate
    ChaCha20Poly1305::Encryption enc;
    enc.SetKeyWithIV(key, key.size(), nonce, sizeof(nonce));

    StringSource(plaintext, true,
        new AuthenticatedEncryptionFilter(enc,
            new StringSink(ciphertext)
        )
    );

    // Decrypt and verify
    ChaCha20Poly1305::Decryption dec;
    dec.SetKeyWithIV(key, key.size(), nonce, sizeof(nonce));

    try {
        StringSource(ciphertext, true,
            new AuthenticatedDecryptionFilter(dec,
                new StringSink(recovered)
            )
        );
        std::cout << "Decrypted: " << recovered << std::endl;
    } catch (const HashVerificationFilter::HashVerificationFailed&) {
        std::cout << "Authentication failed!" << std::endl;
    }

    return 0;
}

Example 3: XChaCha20-Poly1305 (Extended Nonce)

#include <cryptopp/chachapoly.h>
#include <cryptopp/osrng.h>
#include <cryptopp/filters.h>

int main() {
    using namespace CryptoPP;

    AutoSeededRandomPool rng;

    // 32-byte key
    SecByteBlock key(XChaCha20Poly1305::DEFAULT_KEYLENGTH);
    rng.GenerateBlock(key, key.size());

    // 24-byte nonce (extended) - can be random without collision concerns
    byte nonce[24];
    rng.GenerateBlock(nonce, sizeof(nonce));

    std::string plaintext = "Message with extended nonce";
    std::string ciphertext, recovered;

    // Encrypt
    XChaCha20Poly1305::Encryption enc;
    enc.SetKeyWithIV(key, key.size(), nonce, sizeof(nonce));

    StringSource(plaintext, true,
        new AuthenticatedEncryptionFilter(enc,
            new StringSink(ciphertext)
        )
    );

    // Decrypt
    XChaCha20Poly1305::Decryption dec;
    dec.SetKeyWithIV(key, key.size(), nonce, sizeof(nonce));

    StringSource(ciphertext, true,
        new AuthenticatedDecryptionFilter(dec,
            new StringSink(recovered)
        )
    );

    return 0;
}

Example 4: File Encryption with ChaCha20-Poly1305

#include <cryptopp/chachapoly.h>
#include <cryptopp/files.h>
#include <cryptopp/osrng.h>
#include <cryptopp/filters.h>
#include <fstream>

void encryptFile(const std::string& inputFile,
                 const std::string& outputFile,
                 const SecByteBlock& key) {
    using namespace CryptoPP;

    AutoSeededRandomPool rng;

    // Generate random nonce
    byte nonce[12];
    rng.GenerateBlock(nonce, sizeof(nonce));

    // Write nonce to output file first
    std::ofstream out(outputFile, std::ios::binary);
    out.write(reinterpret_cast<const char*>(nonce), sizeof(nonce));

    // Encrypt file
    ChaCha20Poly1305::Encryption enc;
    enc.SetKeyWithIV(key, key.size(), nonce, sizeof(nonce));

    FileSource(inputFile, true,
        new AuthenticatedEncryptionFilter(enc,
            new FileSink(out)
        )
    );
}

Performance

Poly1305 is designed for speed:

ImplementationSpeed
Poly1305 (optimised)3-5 GB/s
HMAC-SHA256500 MB/s - 1 GB/s
AES-CMAC1-2 GB/s

Poly1305 achieves high speed through:

  • Simple arithmetic operations
  • Parallelisable computation
  • No complex S-boxes or permutations

Security Properties

PropertyValue
Tag size128 bits (16 bytes)
Security level128 bits
Forgery probability2^(-128)
Key typeOne-time (single message per key)

Key Reuse Vulnerability

If same key k is used for messages m1, m2:
- Attacker obtains: mac1 = Poly1305(k, m1), mac2 = Poly1305(k, m2)
- Attacker can solve for secret 's' component
- Can then forge MACs for arbitrary messages

SOLUTION: Never reuse Poly1305 keys!

Poly1305 vs Other MACs

FeaturePoly1305HMAC-SHA256AES-CMAC
Speed⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡
Key reuse❌ One-time✅ Yes✅ Yes
Output size16 bytes32 bytes16 bytes
Security128 bits256 bits128 bits
Common useWith ChaCha20GeneralWith AES

When to Use Poly1305

✅ Use Poly1305 for:

  1. ChaCha20-Poly1305 AEAD - Combined encryption + authentication
  2. TLS 1.3 - As part of cipher suites
  3. WireGuard - VPN authentication
  4. High-performance scenarios - Where speed is critical

❌ Don’t use standalone Poly1305 for:

  1. Multiple messages with same key - Use HMAC instead
  2. Password authentication - Use Argon2
  3. When you need simplicity - HMAC is harder to misuse

Integration with ChaCha20

The recommended way to use Poly1305:

#include <cryptopp/chachapoly.h>
#include <cryptopp/osrng.h>
#include <cryptopp/filters.h>

class SecureChannel {
    SecByteBlock key;
    uint64_t counter;

public:
    SecureChannel() : counter(0) {
        AutoSeededRandomPool rng;
        key.resize(32);
        rng.GenerateBlock(key, key.size());
    }

    std::string encrypt(const std::string& plaintext) {
        using namespace CryptoPP;

        // Construct nonce from counter
        byte nonce[12] = {0};
        memcpy(nonce + 4, &counter, 8);
        counter++;

        ChaCha20Poly1305::Encryption enc;
        enc.SetKeyWithIV(key, key.size(), nonce, sizeof(nonce));

        std::string ciphertext;

        // Prepend nonce to ciphertext
        ciphertext.assign((const char*)nonce, sizeof(nonce));

        std::string encrypted;
        StringSource(plaintext, true,
            new AuthenticatedEncryptionFilter(enc,
                new StringSink(encrypted)
            )
        );

        ciphertext += encrypted;
        return ciphertext;
    }

    std::string decrypt(const std::string& ciphertext) {
        using namespace CryptoPP;

        if (ciphertext.size() < 12 + 16) {
            throw std::runtime_error("Ciphertext too short");
        }

        // Extract nonce
        byte nonce[12];
        memcpy(nonce, ciphertext.data(), 12);

        ChaCha20Poly1305::Decryption dec;
        dec.SetKeyWithIV(key, key.size(), nonce, sizeof(nonce));

        std::string plaintext;
        StringSource(ciphertext.substr(12), true,
            new AuthenticatedDecryptionFilter(dec,
                new StringSink(plaintext)
            )
        );

        return plaintext;
    }
};

Error Handling

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

void safeDecrypt(const std::string& ciphertext,
                 const SecByteBlock& key,
                 const byte* nonce) {
    using namespace CryptoPP;

    try {
        ChaCha20Poly1305::Decryption dec;
        dec.SetKeyWithIV(key, key.size(), nonce, 12);

        std::string plaintext;
        StringSource(ciphertext, true,
            new AuthenticatedDecryptionFilter(dec,
                new StringSink(plaintext)
            )
        );

        std::cout << "Decryption successful" << std::endl;

    } catch (const HashVerificationFilter::HashVerificationFailed& e) {
        std::cerr << "AUTHENTICATION FAILED - data tampered!" << std::endl;
        // Don't use the data!
    } catch (const Exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
}

Thread Safety

ChaCha20Poly1305 objects are not thread-safe:

// WRONG - shared across threads
ChaCha20Poly1305::Encryption sharedEnc;

// CORRECT - per-thread instances
void encryptInThread(const std::string& plaintext,
                     const SecByteBlock& key) {
    AutoSeededRandomPool rng;
    byte nonce[12];
    rng.GenerateBlock(nonce, sizeof(nonce));

    ChaCha20Poly1305::Encryption enc;
    enc.SetKeyWithIV(key, key.size(), nonce, sizeof(nonce));

    std::string ciphertext;
    StringSource(plaintext, true,
        new AuthenticatedEncryptionFilter(enc,
            new StringSink(ciphertext)
        )
    );
}

See Also