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

Затем мы определим (неформально, а затем более формально) две важные части языка, используемые в 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}\) примитив в Тинк. Он имеет несколько алгоритмов, среди которых 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 генерируется случайным образом для каждой операции шифрования.