Как использовать примитивы XSalsa20 и Poly1305 в Bouncycastle для AEAD

Я хочу использовать BouncyCastle для имитации схемы AEAD по умолчанию libsodium (X25519 ECDH, за которой следует симметричный шифр XSalsa20 с Poly1305 KDF).

Мне удалось выполнить DH с X25519 KeyAgreement, чтобы сгенерировать javax.crypto.SecretKey.

Но что касается следующего шага, я не знаю, что делать. BouncyCastle предлагает реализацию класса ChaCha20Poly1305AEADCipher, который, похоже, выполняет оба шага одновременно (генерацию MAC и шифрование). Однако я не нашел такого эквивалента для «XSalsa20Poly1305».

В BC есть Poly1305KeyGenerator, но я не вижу способа передать ему nonce, поскольку он принимает только простые KeyGenerationParameters объекты при инициализации.

Почему комбинация ChaCha20 и Poly1305 настолько особенная (по сравнению с [X]Salsa20+Poly1305), что у нее есть специальная реализация? Как я могу сделать то же самое для XSalsa20 с примитивами BouncyCastle?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
0
127
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Libsodium применяет XSalsa20-Poly1305 для шифрования с аутентификацией, которое реализовано в функции crypto_secretbox_easy() . Базовый алгоритм XSalsa20-Poly1305 можно прочитать в эталонной реализации:

  • Используя HSalsa20, ключ k и nonce n в качестве входных параметров, получается ключевой подраздел.
  • В Salsa20, используя ключевой подраздел и последние 8 байтов n в качестве nonce (далее называемых subnonce), конкатенация пустого 32-байтового массива и открытого текста m длиной mlen шифруется.
  • Первые 32 байта используются как ключ Poly1305, остальные — собственно зашифрованный текст c.
  • В Poly1305 MAC Poly1305 mac получается для c применения ключа Poly1305.

Более подробную информацию об алгоритмах, в частности HSalsa20, см., например. Расширение nonce Salsa20 и аутентификация, см., например. Поли1305.

BouncyCastle реализует XSalsa20Engine как производное от Salsa20Engine. XSalsa20Engine перезаписывает setKey() , который реализует HSalsa20 и внутренне определяет подраздел и субнонс. Когда экземпляр XSalsa20Engine инициализируется, Salsa20Engine#init() выполняется и, таким образом, setKey().
Это позволяет реализовать XSalsa20-Poly1305 с помощью Bouncycastle следующим образом:

  • Инициализация XSalsa20Engine с использованием ключа и одноразового номера. Это внутренне вычисляет подразделы и субнонсы.
  • Шифрование пустого 32-байтового массива с помощью Salsa20 с использованием субключа и субнонса для получения ключа Poly1305.
    Шифрование открытого текста с помощью Salsa20 с использованием субключа и субнонса к зашифрованному тексту.
    Для простоты оба выполняются отдельно (в качестве альтернативы можно зашифровать объединение пустого 32-байтового массива и открытого текста, как в эталонной реализации).
  • Расчет MAC Poly1305 для зашифрованного текста с использованием ключа Poly1305.

Кроме того, генерируется случайный одноразовый номер (это делается в эталонной реализации перед вызовом crypto_secretbox_easy()), а одноразовый номер, Poly1305 MAC и зашифрованный текст объединяются в этом порядке.

Что касается ключа Poly1305, обратите внимание на следующее:

  • Poly1305 используется в контексте XSalsa20Poly1305 как одноразовый аутентификатор, размер ключа которого составляет 32 байта, см., например. вот .
  • Для ключей Poly1305 ряд битов очищается (фиксируется) по определению. macKey в приведенной выше процедуре (пока) не зафиксировано, это происходит только неявно во время инициализации Poly1305 (в init() через setKey()).
    Поэтому, если вы хотите проверить macKey с помощью checkKey() , зажим() должен быть выполнен первым (последующее неявное ограничение во время инициализации Poly1305 тогда не имеет никакого эффекта).
  • Poly1305KeyGenerator или generateKey() нельзя использовать для генерации ключа Poly1305 в контексте XSalsa20-Poly1305. generateKey() предназначен для генерации случайных ключей Poly1305, при этом ключ Poly1305, сгенерированный в контексте XSalsa20-Poly1305, генерируется детерминированно (способом, описанным выше).

Пример реализации шифрования с помощью XSalsa20-Poly1305:

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.HexFormat;
import org.bouncycastle.crypto.engines.XSalsa20Engine;
import org.bouncycastle.crypto.macs.Poly1305;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.util.Arrays;
...
static final int NONCEBYTES = 24;
static final int KEYBYTES = 32;
...
public static byte[] encrypt(byte[] key, byte[] plaintext) {
    
    // generate random nonce
    byte[] nonce = new byte[NONCEBYTES];
    SecureRandom secureRandom = new SecureRandom();
    secureRandom.nextBytes(nonce);

    XSalsa20Engine xSalsa20Engine = new XSalsa20Engine();
    xSalsa20Engine.init(true, new ParametersWithIV(new KeyParameter(key), nonce));
    
    // generate mac key
    byte[] macKey = new byte[KEYBYTES];
    xSalsa20Engine.processBytes(macKey, 0, macKey.length, macKey, 0);

    // encrypt plaintext
    byte[] ciphertext = new byte[plaintext.length];
    xSalsa20Engine.processBytes(plaintext, 0, plaintext.length, ciphertext, 0);
    
    // generate mac
    Poly1305 poly1305 = new Poly1305();
    poly1305.init(new KeyParameter(macKey));
    byte[] mac = new byte[poly1305.getMacSize()];
    poly1305.update(ciphertext, 0, plaintext.length); // ciphertext size = plaintext size
    poly1305.doFinal(mac, 0);

    // concatenate, e.g. nonce|mac|ciphertext
    return Arrays.concatenate(nonce, mac, ciphertext);
}

Пример расшифровки:

public static byte[] decrypt(byte[] key, byte[] nonceMacCiphertext) {
    
    // separate nonce, mac and ciphertext
    Poly1305 poly1305 = new Poly1305();
    byte[] nonce = Arrays.copyOfRange(nonceMacCiphertext, 0, NONCEBYTES);
    byte[] mac  = Arrays.copyOfRange(nonceMacCiphertext, NONCEBYTES, NONCEBYTES + poly1305.getMacSize());
    byte[] ciphertext  = Arrays.copyOfRange(nonceMacCiphertext, NONCEBYTES + poly1305.getMacSize(), nonceMacCiphertext.length);
    
    XSalsa20Engine xSalsa20Engine = new XSalsa20Engine();
    xSalsa20Engine.init(false, new ParametersWithIV(new KeyParameter(key), nonce));
    
    // generate mac key
    byte[] macKey = new byte[KEYBYTES];
    xSalsa20Engine.processBytes(macKey, 0, macKey.length, macKey, 0);

    // calculate Mac
    byte[] macCalculated = new byte[poly1305.getMacSize()];
    poly1305.init(new KeyParameter(macKey));
    poly1305.update(ciphertext, 0, ciphertext.length);
    poly1305.doFinal(macCalculated, 0);

    // decrypt on successful authentication
    byte[] decrypted = null;
    if (MessageDigest.isEqual(macCalculated, mac)) {
        decrypted = new byte[ciphertext.length];
        xSalsa20Engine.processBytes(ciphertext, 0, ciphertext.length, decrypted, 0);
    }
    return decrypted;
}

Тест:

Самый удобный способ проверить совместимость с XSalsa20-Poly1305 — сравнить зашифрованный текст, сгенерированный с помощью библиотеки Libsodium, например. Lazysodium и идентичные входные параметры:

import com.goterl.lazysodium.LazySodiumJava;
import com.goterl.lazysodium.SodiumJava;
import com.goterl.lazysodium.utils.Key;
...
public static void main(String[] args) throws Exception {

    // Check XSalsa20-Poly1305 implementation
    byte[] key = HexFormat.of().parseHex("a7e845b0854294da9aa743b807cb67b19647c1195ea8120369f3d12c70468f29");
    byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(StandardCharsets.UTF_8);        

    byte[] nonceMacCiphertext = encrypt(key, plaintext);
    System.out.println("CT, BouncyCastle: " + HexFormat.of().formatHex(nonceMacCiphertext)); 

    byte[] decrypted = decrypt(key, nonceMacCiphertext);
    System.out.println("Decrypted data:   " + new String(decrypted, StandardCharsets.UTF_8)); 

    // Compare nonce|mac|ciphertext with Lazysodium
    byte[] nonce = Arrays.copyOfRange(nonceMacCiphertext, 0, NONCEBYTES);
    SodiumJava sodium = new SodiumJava();
    LazySodiumJava lazySodium = new LazySodiumJava(sodium, StandardCharsets.UTF_8);
    String macCiphertext = lazySodium.cryptoSecretBoxEasy("The quick brown fox jumps over the lazy dog", nonce , Key.fromBytes(key));
    System.out.println("CT, Lazysodium:   " + HexFormat.of().formatHex(nonce) + macCiphertext.toLowerCase()); 
}

Оба зашифрованных текста совпадают для идентичных входных данных.

Я не понимаю этот код... Почему вы передаете пустой массив движку salsa для создания ключа Mac? Разве не для этого нужен Poly1305KeyGenerator? Это какой-то трюк по оптимизации производительности?

Leprechaun 15.07.2024 12:52

тогда почему проверка «детерминированного» ключа не проходит проверку Poly1305KeyGenerator#checkKey() BouncyCastle? Похоже, что ключ не является валидным ключом Poly1305 — выдает IAE: Invalid format for r portion of Poly1305 key. Я также просмотрел связанные технические документы [X]Salsa20, но там не упоминаются какие-либо ключи Poly1305, так почему же он должен вести себя по-другому при использовании с другими шифрами?

Leprechaun 15.07.2024 19:26

Кроме того, я нашел различные ссылки, в которых говорится: «Poly1305 принимает на вход 16-байтовый ключ (с некоторыми конкретными битами, установленными в ноль) и сообщение произвольной длины и выводит 16-байтовый дайджест сообщения. Выходные данные Poly1305 — это усечение (до 16 байт) полинома, вычисленного в F2130−5 по ключу r (который сначала «прижимает» определенные биты к нулю » eprint.iacr.org/2014/613.pdf Это не подходит); просто каждый раз просто устанавливая нулевой вход.

Leprechaun 15.07.2024 19:58

@Leprechaun - я отредактировал свой пост (см. часть перед реализацией примера) и ответил (среди прочего) на ваши вопросы из комментариев (особенно на вопросы о происхождении пустого 32-байтового массива, об ограничении и о размере ключа Poly1305). ). Это должно сделать реализацию BouncyCastle более прозрачной.

Topaco 16.07.2024 11:06

Ох, мне потребовалось много времени, чтобы переварить это, мне пришлось прочитать статьи Бернштейна, RFC ChaCha, код libsodium Rubberduck и перекрестные ссылки BC с вашим пошаговым обзором. Я думаю (?), что вы правы во всех своих утверждениях, поэтому я принимаю ответ, спасибо. Было бы гораздо проще, если бы кто-нибудь где-нибудь написал большую оговорку о том, что XSalsa20-Poly1305 из libsodium — это не то же самое, что XSalsa20 + Poly1305. Действительно, рецепт, что делать, надо расшифровать из статьи Бернштейна Cryptography in NaCl. ИМХО, она должна даже получить отдельное имя как схема AEAD.

Leprechaun 16.07.2024 19:40

Другие вопросы по теме