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.
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:
| Class | Use Case |
|---|---|
ChaCha20Poly1305 | Recommended - Combined AEAD cipher |
XChaCha20Poly1305 | Extended nonce variant (192-bit nonce) |
Poly1305TLS | IETF 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:
| Implementation | Speed |
|---|---|
| Poly1305 (optimised) | 3-5 GB/s |
| HMAC-SHA256 | 500 MB/s - 1 GB/s |
| AES-CMAC | 1-2 GB/s |
Poly1305 achieves high speed through:
- Simple arithmetic operations
- Parallelisable computation
- No complex S-boxes or permutations
Security Properties
| Property | Value |
|---|---|
| Tag size | 128 bits (16 bytes) |
| Security level | 128 bits |
| Forgery probability | 2^(-128) |
| Key type | One-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
| Feature | Poly1305 | HMAC-SHA256 | AES-CMAC |
|---|---|---|---|
| Speed | ⚡⚡⚡⚡⚡ | ⚡⚡⚡ | ⚡⚡⚡⚡ |
| Key reuse | ❌ One-time | ✅ Yes | ✅ Yes |
| Output size | 16 bytes | 32 bytes | 16 bytes |
| Security | 128 bits | 256 bits | 128 bits |
| Common use | With ChaCha20 | General | With AES |
When to Use Poly1305
✅ Use Poly1305 for:
- ChaCha20-Poly1305 AEAD - Combined encryption + authentication
- TLS 1.3 - As part of cipher suites
- WireGuard - VPN authentication
- High-performance scenarios - Where speed is critical
❌ Don’t use standalone Poly1305 for:
- Multiple messages with same key - Use HMAC instead
- Password authentication - Use Argon2
- 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
- ChaCha20-Poly1305 - Recommended AEAD
- HMAC - Reusable key MAC
- CMAC - Block cipher MAC
- AES-GCM - Alternative AEAD
- Security Concepts - Nonce management