AES-GCM или ChaCha20-Poly1305 — это выбор между двумя алгоритмами аутентифицированного шифрования, на которых держится почти весь зашифрованный трафик: TLS 1.3, QUIC, SSH, WireGuard, age и большая часть шифрования на уровне приложений. При правильном использовании оба безопасны, при повторе nonce защита обоих серьёзно нарушается, а их скорость сильно зависит от оборудования.
Коротко
| AES-GCM | ChaCha20-Poly1305 | |
|---|---|---|
| Спецификация | NIST SP 800-38D (2007) | RFC 8439 (2018, заменяет RFC 7539) |
| Длина ключа | 128, 192, 256 бит | 256 бит |
| Nonce | рекомендуется 96 бит | 96 бит (192 бита в XChaCha20) |
| Тег | 128 бит (короче допускается, но не рекомендуется) | 128 бит |
| Максимум данных в одном сообщении | около 64 ГиБ (2³² − 2 блока) | 256 ГиБ (2³² блоков по 64 байта) |
| Лимит случайных nonce на один ключ | 2³² сообщений (SP 800-38D) | Тот же порядок при 96-битных nonce; с XChaCha20 практически без ограничений |
| Повторное использование nonce | Раскрывает XOR открытых текстов и ключ GHASH, что позволяет подделывать сообщения | Раскрывает XOR открытых текстов и позволяет подделывать сообщения |
| Пропускная способность, 16 КиБ, OpenSSL 3.6.5 | 10,4 ГБ/с (AES-128), 8,8 ГБ/с (AES-256) | 2,1 ГБ/с |
| Пропускная способность, сообщения по 16 байт | 0,14 ГБ/с (AES-256) | 0,52 ГБ/с |
| Постоянное время выполнения без аппаратной поддержки | Трудно достижимо (табличная реализация AES даёт утечки через кеш) | Достигается естественным образом (сложение, циклический сдвиг, XOR) |
| Одобрен по FIPS 140-3 | Да | Нет |
| TLS 1.3 | TLS_AES_128_GCM_SHA256 (обязателен), TLS_AES_256_GCM_SHA384 (рекомендуется) | TLS_CHACHA20_POLY1305_SHA256 (рекомендуется) |
| Другие применения | JWE A128GCM и A256GCM, IPsec, SSH aes256-gcm@openssh.com, шифрование дисков и облачных данных | WireGuard, SSH chacha20-poly1305@openssh.com, age, libsodium |
Пропускная способность измерена командой openssl speed -evp на одном ядре Apple M4 Max, у которого есть инструкции AES и полиномиального умножения.
Производительность зависит от процессора
AES-GCM рассчитан на аппаратную поддержку: каждый раунд AES выполняется одной инструкцией (AES-NI на x86, криптографические расширения ARMv8), а GHASH использует умножение без переноса (carry-less multiplication). Когда доступно и то и другое, в этом замере он выдаёт около 10 ГБ/с на ядро — примерно в пять раз больше, чем ChaCha20-Poly1305. Без них реализации AES приходится выбирать между таблицами поиска, которые работают медленно и раскрывают биты ключа через время обращения к кешу, и bitslice-кодом, который работает за постоянное время, но тоже медленно. Для ChaCha20 нужны только 32-битные сложения, циклические сдвиги и XOR, поэтому он работает быстро и за постоянное время на любом процессоре, включая бюджетные телефоны и микроконтроллеры.
На маленьких сообщениях разрыв сокращается, а то и меняется на обратный: на входных данных по 16 байт ChaCha20-Poly1305 показал 0,52 ГБ/с против 0,14 ГБ/с у AES-256-GCM, потому что в GCM основное время уходит на подготовку. Для типичных TLS-записей (до 16 КиБ) на серверах быстрее AES-GCM; для множества крошечных пакетов разница в любом случае невелика.
Nonce и ограничения
Оба алгоритма — шифры в режиме счётчика с одноразовыми аутентификаторами, поэтому пара (ключ, nonce) никогда не должна повторяться. Повтор раскрывает XOR двух открытых текстов; в GCM он, кроме того, позволяет атакующему восстановить подключ аутентификации и подделывать сообщения для этого ключа, а в Poly1305 раскрывает одноразовый ключ и позволяет подделать сообщение с этим nonce. NIST SP 800-38D ограничивает использование случайно сгенерированных 96-битных IV величиной 2³² вызовов на ключ, чтобы вероятность коллизии оставалась пренебрежимо малой; TLS обходит эту проблему: nonce выводятся из порядковых номеров записей, а ключи периодически обновляются.
Если ваша схема генерирует случайные nonce для множества сообщений под одним ключом, используйте конструкцию с более широким пространством nonce или устойчивостью к неправильному использованию (misuse resistance):
- XChaCha20-Poly1305 (192-битные nonce, описан в черновике IRTF и реализован в libsodium и других библиотеках): с ним случайные nonce безопасны практически для неограниченного числа сообщений;
- AES-GCM-SIV (RFC 8452): повторное использование nonce раскрывает лишь то, были ли два сообщения одинаковыми;
- или вывод отдельного ключа для каждого сообщения или сеанса, как это делают TLS, age и HPKE.
Ни один из алгоритмов не обеспечивает привязку к ключу (key commitment): можно подобрать шифртекст, который корректно расшифровывается двумя разными ключами. Это важно для схем с несколькими получателями или на основе пароля (проблема «невидимых саламандр», invisible salamanders). Если атакующий может выбирать ключи, добавьте привязку к ключу.
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
const plaintext = Buffer.from("card ending 4242, amount 19.99");
const aad = Buffer.from("order=7781");
for (const algorithm of ["aes-256-gcm", "chacha20-poly1305"]) {
const key = randomBytes(32);
const nonce = randomBytes(12); // 96-bit nonce for both; never reuse with the same key
const cipher = createCipheriv(algorithm, key, nonce, { authTagLength: 16 });
cipher.setAAD(aad);
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
const tag = cipher.getAuthTag();
const decipher = createDecipheriv(algorithm, key, nonce, { authTagLength: 16 });
decipher.setAAD(aad);
decipher.setAuthTag(tag);
const roundTrip = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
console.log(algorithm.padEnd(18), "ciphertext", ciphertext.length, "bytes + tag", tag.length, "bytes, decrypts:", roundTrip.equals(plaintext));
}
aes-256-gcm ciphertext 30 bytes + tag 16 bytes, decrypts: true
chacha20-poly1305 ciphertext 30 bytes + tag 16 bytes, decrypts: true
Как TLS выбирает между ними
TLS 1.3 требует, чтобы каждая реализация поддерживала TLS_AES_128_GCM_SHA256, и рекомендует AES-256-GCM и ChaCha20-Poly1305. Клиенты без аппаратной поддержки AES ставят ChaCha20 первым в списке; серверы, которые учитывают это предпочтение, отдают каждому клиенту более быстрый для него шифр. В OpenSSL для этого есть параметр PrioritizeChaCha (SSL_OP_PRIORITIZE_CHACHA): он выбирает ChaCha20, только если клиент указал его первым. BoringSSL для той же цели использует группы с равным приоритетом. Генератор конфигурации TLS создаёт настройки сервера, в которых сохранены оба набора шифров.
Что когда выбирать
- Серверы TLS и QUIC: включите оба; отдавайте предпочтение AES-GCM, но позвольте клиентам, которые ставят ChaCha20 первым, получать его.
- Среды FIPS 140-3: AES-GCM; ChaCha20-Poly1305 не входит в число одобренных алгоритмов.
- Мобильные устройства, IoT и микроконтроллеры без аппаратного ускорения AES: ChaCha20-Poly1305.
- Шифрование на уровне приложений со случайными nonce и долгоживущими ключами: XChaCha20-Poly1305 или AES-GCM-SIV.
- JWE:
A256GCM(илиA128GCM); ChaCha20 нет в основном реестре JWA. - Очень большие отдельные сообщения (больше 64 ГиБ): разбивайте их на фрагменты с отдельным nonce для каждого (так age шифрует данные фрагментами ChaCha20-Poly1305 по 64 КиБ); не полагайтесь на предельный объём одного сообщения.
Заметки о миграции
В хорошо спроектированной системе смена AEAD меняет формат шифртекста и больше ничего.
- Версионируйте формат: храните с каждым шифртекстом идентификатор алгоритма и идентификатор ключа, чтобы старые данные оставались читаемыми.
- Используйте для каждого алгоритма свой ключ: выводите разные ключи (например, с помощью HKDF с разными строками info), а не применяйте один ключ для обоих.
- Перепроверьте генерацию nonce: счётчики должны сохраняться между перезапусками; случайные nonce должны укладываться в указанные выше ограничения, иначе переходите на XChaCha20.
- Перешифровывайте по мере обращения к данным: когда запись затрагивается, расшифровывайте её старым алгоритмом и записывайте новым, если только нормативный срок не требует массовой миграции.
- Измеряйте производительность на реальном оборудовании: на серверных процессорах и на маленьких устройствах лидеры по скорости разные.