Примитивы и интерфейсы

Затем мы определим (неформально, а затем более формально) две важные части языка, используемые в Tink: примитив и интерфейс .

Примитивный

Примитив — это математический объект, соответствующий всем алгоритмам, безопасно выполняющим некоторую задачу. Например, примитив AEAD состоит из всех алгоритмов шифрования, которые удовлетворяют свойствам безопасности, которые Tink требует от Aead .

Мы подчеркиваем, что примитивы не привязаны к языку программирования или конкретному способу доступа к ним. Вместо этого следует думать о примитиве как о чисто математическом объекте. Например, если мы рассмотрим AEAD, то по сути он будет состоять из пар функций: одна выполняет шифрование, а другая — дешифрование.

Интерфейсы

Интерфейс — это способ предоставления пользователям доступа к примитиву. Например, мы ожидаем, что в будущем Tink предоставит интерфейс Mac , а также интерфейс StreamingMac , который позволит вычислять Mac данных, которые не загружаются напрямую в память.

Обратите внимание, что здесь мы явно различаем интерфейсы и примитивы. Это должно прояснить, что математический объект, к которому эти два интерфейса предоставляют доступ, один и тот же.

Формальные определения

Большинству читателей, вероятно, будет достаточно приведенных выше интуитивных объяснений. Тем не менее, мы считаем, что иногда может быть важно дать формальные определения этих понятий.

Криптографические функции

Понятие криптографической функции не так важно, как понятие примитива, но нам необходимо ввести его, чтобы формально определить примитив.

Криптографическая функция

Криптографическая функция — это карта

\[ f: {\bf K} \times {\bf R} \times {\bf I} \to {\bf O}\]

из набора \({\bf K}\) (пространство ключей), набора \({\bf R} = \{0,1\}^{\infty}\)(случайность, которую мы предполагаем набором бесконечных битовых строк) и набора \({\bf I}\) (входное пространство) к набору \({\bf O}\) (выходное пространство).

Позже станет понятно, почему мы добавили конкретный параметр случайности.

В качестве примера мы покажем одну возможность того, как эти концепции могут быть сопоставлены с AES-GCM. Для каждого допустимого размера ключа \(s_k\), размера nonce \(s_n\)и размера тега\(s_t\)AES-GCM состоит из двух криптографических функций: одна для шифрования, а другая для дешифрования. Оба будут иметь одинаковое ключевое пространство \({\bf K} = \{0,1\}^{s_k}\).

Для функции шифрования \(\mathrm{Enc}\)первые биты случайности \(s_n\) будут использоваться для выбора nonce.

Пусть \({\bf B} = \{0,1\}^8\) обозначает байт. Входным пространством функции шифрования являются пары \({\bf I} = {\bf B}^{*} \times {\bf B}^{*}\) пар байтовых строк произвольной длины. Первый элемент пары предназначен для сообщения, второй элемент — для связанных данных. Стандарт AES-GCM имеет верхний предел длины входных данных, но мы предпочитаем разрешать произвольные длины и вместо этого добавляем в выходное пространство специальный символ ошибки \(\bot\) . Затем пространство вывода становится \({\bf O} = {\bf B}^* \cup \{\bot\}\), где мы произвольно определяем результат успешных вычислений как конкатенацию \((\mathrm{IV} \| \mathrm{ciphertext} \| \mathrm{tag})\) как указано в стандарте, и выводим\(\bot\)на случай, если некоторые входные данные слишком длинные. Следовательно, для фиксированного ключа функция шифрования становится типа \(\mathrm{Enc}_k : {\bf R} \times {\bf B}^* \times {\bf B}^* \rightarrow {\bf B}^* \cup \{\bot\}\).

Для функции дешифрования \(\mathrm{Dec}\) ключевое пространство такое же. Входное пространство по совпадению такое же: \({\bf I} ={\bf B}^* \times {\bf B}^*\), но теперь первый элемент предназначен для вывода функции шифрования, а второй по-прежнему является связанными данными.

Выходное пространство также оказывается тем же \({\bf O} = {\bf B}^* \cup \{\bot\}\) (опять совпадение). Интерпретация несколько иная, поскольку \(\bot\) обычно обозначает ошибку аутентификации (хотя это также будет вывод в случае, если какой-либо ввод окажется слишком длинным).

Подчеркнем, что описанная выше формализация не является единственным вариантом формализации стандарта. Например, можно рассматривать nonce как часть входных данных, а не считывать его по случайности (что приводит к совершенно другому примитиву). В качестве альтернативы можно определить выходные данные как тройку, содержащую одноразовый номер, зашифрованный текст и тег (вместо конкатенации). Или можно ограничить пространство ключей (несколько произвольно) до\({\bf K} = \{0,1\}^{128} \cup \{0,1\}^{256}\).

Криптографический алгоритм:

(Симметричный) криптографический алгоритм представляет собой кортеж

\[(f_1, ... f_k)\]

криптографических функций, где все функции имеют одно и то же ключевое пространство. Тип криптографического алгоритма — кортеж \((({\bf I}_1, {\bf O}_1), \ldots, ({\bf I}_k, {\bf O}_k))\).

Например, для каждой допустимой тройки \((s_k, s_n, s_t)\) размера ключа, одноразового номера и тега AES-GCM\({}_{s_k, s_n, s_t}\) представляет собой криптографический алгоритм с двумя функциями \(\mathrm{Enc}\) и \(\mathrm{Dec}\) описанными выше.

Примитивы и интерфейсы

Далее мы определим криптографический примитив.

Примитивный
Примитив — это набор криптографических алгоритмов, где все алгоритмы имеют один и тот же тип \((({\bf I}_1, {\bf O}_1), \ldots, ({\bf I}_k, {\bf O}_k))\), а ключевые пространства алгоритмов попарно не пересекаются.

В качестве примера рассмотрим примитив \(\mathrm{AEAD}\) в Tink. Он имеет несколько алгоритмов, среди которых AES-GCM для ключей размером 128 и 256 бит с размером nonce 96 бит, AES-EAX с некоторыми размерами ключей и XChaCha20Poly1305. У них непересекающиеся ключевые пространства, но все они предоставляют одни и те же криптографические функции\(\mathrm{Enc}\) и \(\mathrm{Dec}\). (Мы не видим смысла в том, чтобы как-то сжимать разные размеры ключей AES-GCM в этом формальном обсуждении, но, конечно, можно было бы это сделать).

Определение примитивов

Обычный способ рассмотрения примитивов состоит в том, чтобы сначала определить свойства криптографических функций, а затем просто рассматривать примитивы как все такие алгоритмы.

Например, для AEAD мы бы сказали, что \(\mathrm{Dec}_k(\mathrm{Enc}_k(m, a), a) = m\) выполняется «всегда» (за исключением случаев, когда открытый текст \(m\) слишком длинный). Кроме того, у нас есть охранные свойства; например, для случайного ключа шифрование является семантически безопасным.

Тогда примитив AEAD представляет собой просто набор всех криптографических алгоритмов, удовлетворяющих этим свойствам. Другими словами, на практике, когда мы определяем конкретный примитив, мы определяем его на основе свойств. Мы не приводим список алгоритмов, как следует из определения.

Интерфейсы

Интерфейс в Tink предоставляет доступ к примитиву в том смысле, что он позволяет вычислить элемент выходного пространства из входного пространства. Например, рассмотрим интерфейс AEAD в Java:

public interface Aead {
  byte[] encrypt(byte[] plaintext, byte[] associated_data) throws GeneralSecurityException;
  byte[] decrypt(byte[] ciphertext, byte[] associated_data) throws GeneralSecurityException;
}

Обратите внимание, что мы не даем доступ к случайности. Вместо этого мы разрешаем пользователю предоставлять элементы пространства ввода. Запретить доступ к случайности, конечно, намеренно. 1

Tink иногда предлагает несколько интерфейсов для одного примитива. Это может быть очень полезно, поскольку требования иногда различаются. Тем не менее, за это приходится платить: как правило, чем больше интерфейсов предлагается, тем ниже совместимость. Например, представьте, что кто-то пишет библиотеку на основе Tink, которая требует от пользователя передачи объекта Aead (чтобы что-то зашифровать внутри). Если Tink предлагает слишком много различных интерфейсов для примитива \(\mathrm{AEAD}\) , высока вероятность того, что у пользователя нет готового экземпляра, который работает одновременно с выбранным пользователем ключом и библиотекой. Следовательно, добавление большего количества интерфейсов является компромиссом.


  1. Шифры AEAD обладают тем свойством, что они защищены от атак с использованием выбранного зашифрованного текста, что гарантируется только в том случае, если одноразовый номер не используется повторно. Интерфейс Aead в Tink спроектирован таким образом, чтобы предотвратить повторное использование nonce: пользователь не может предоставить nonce в качестве входных данных для шифрования, вместо этого новый nonce генерируется случайным образом для каждой операции шифрования.