Runtime: Общий низкоуровневый примитив для шифров (первым является AES-GCM)

Созданный на 29 авг. 2017  ·  143Комментарии  ·  Источник: dotnet/runtime

Обоснование

Существует общая потребность в ряде шифров для шифрования. Сегодняшняя смесь интерфейсов и классов стала немного разрозненной. Также нет поддержки шифров в стиле AEAD, поскольку им нужна возможность предоставлять дополнительную информацию для аутентификации. Текущие проекты также склонны к распределению, и этого трудно избежать из-за возвращаемых массивов.

Предлагаемый API

Абстрактный базовый класс общего назначения, который будет реализован конкретными классами. Это позволит расширяться, а также, имея класс, а не статические методы, у нас есть возможность создавать методы расширения, а также сохранять состояние между вызовами. API должен позволять перерабатывать класс, чтобы обеспечить меньшие выделения (не нужно каждый раз создавать новый экземпляр и перехватывать, скажем, неуправляемые ключи). Из-за часто неуправляемого характера отслеживаемых ресурсов класс должен реализовывать IDisposable.

public abstract class Cipher : IDisposable
{
    public virtual int TagSize { get; }
    public virtual int IVSize { get; }
    public virtual int BlockSize { get; }
    public virtual bool SupportsAssociatedData { get; }

    public abstract void Init(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv);
    public abstract void Init(ReadOnlySpan<byte> iv);
    public abstract int Update(ReadOnlySpan<byte> input, Span<byte> output);
    public abstract int Finish(ReadOnlySpan<byte> input, Span<byte> output);
    public abstract void AddAssociatedData(ReadOnlySpan<byte> associatedData);
    public abstract int GetTag(Span<byte> span);
    public abstract void SetTag(ReadOnlySpan<byte> tagSpan);
}

Пример использования

(источник ввода/вывода представляет собой мифический поток на основе диапазона, такой как источник ввода-вывода)

using (var cipher = new AesGcmCipher(bitsize: 256))
{
    cipher.Init(myKey, nonce);
    while (!inputSource.EOF)
    {
        var inputSpan = inputSource.ReadSpan(cipher.BlockSize);
        cipher.Update(inputSpan);
        outputSource.Write(inputSpan);
    }
    cipher.AddAssociatedData(extraInformation);
    cipher.Finish(finalBlockData);
    cipher.GetTag(tagData);
}

Поведение API

  1. Если тег get вызывается до завершения, должно быть выдано [тип исключения?], а внутреннее состояние должно быть установлено как недопустимое.
  2. Если тег недействителен при завершении расшифровки, это должно быть выдано исключение.
  3. После завершения вызова вызов чего-либо, кроме одного из методов Init, вызовет
  4. После вызова Init второй вызов без «завершения» вызовет
  5. Если тип ожидает предоставленный ключ (прямой «новый» экземпляр up), если начальный вызов «Init» имеет только IV, он выдаст
  6. Если тип был сгенерирован, скажем, из ключа на основе хранилища, и вы пытаетесь изменить ключ через Init, а не только IV, он выдаст
  7. Если тег get не вызывается перед dispose или Init, должно ли быть выдано исключение? Чтобы пользователь случайно не собрал тег?

Ссылка dotnet/corefx#7023

Обновления

  1. Изменен одноразовый номер на IV.
  2. Добавлен раздел поведения
  3. Удалены случаи с одним входом/выходом из окончаний и обновлений, они могут быть просто методами расширения.
  4. Изменено количество диапазонов на readonlyspan, как было предложено @bartonjs.
  5. Удален Reset, вместо него следует использовать Init с IV
api-suggestion area-System.Security

Самый полезный комментарий

@bartonjs , вы буквально игнорируете весь технический и логический анализ и вместо этого используете даты проектов Go и libsodium в качестве слабого прокси для реального анализа?

Нет, я пользуюсь советом профессиональных криптографов, которые говорят, что это чрезвычайно опасно и что нам следует избегать потоковой передачи AEAD. Затем я использую информацию от команды CNG о том, что «многие люди говорят, что хотят этого в теории, но на практике почти никто этого не делает» (я не знаю, насколько это телеметрия по сравнению с анекдотичными данными из запросов на помощь). Тот факт, что другие библиотеки пошли по однократному пути, просто подкрепляет принятое решение.

Почему спрос, демонстрируемый на GitHub, недостаточен?

Было упомянуто несколько сценариев. Обработка фрагментированных буферов, вероятно, может быть решена путем принятия ReadOnlySequence , если кажется, что сценария достаточно, чтобы оправдать усложнение API вместо того, чтобы вызывающая сторона выполняла повторную сборку данных.

Большие файлы — это проблема, но большие файлы — это уже проблема, так как у GCM ограничение составляет чуть меньше 64 ГБ, что «не так уж и много» (хорошо, это довольно много, но это не то «ух ты, это много», что это было). Файлы, отображаемые в памяти, позволят использовать диапазоны (до 2 ^ 31-1), не требуя 2 ГБ ОЗУ. Таким образом, мы сократили максимум на пару битов... это, вероятно, произойдет со временем.

Вы понимаете, что выбор непотокового интерфейса для AEAD исключает все подобные реализации в будущем, верно?

Я все больше и больше убеждаюсь, что @GrabYourPitchforks был прав (https://github.com/dotnet/corefx/issues/23629#issuecomment-334638891), что, вероятно, нет разумного унифицирующего интерфейса. GCM _требует_ одноразового номера/IV, а SIV _запрещает_ это означает, что инициализация режима/алгоритма AEAD уже требует знания о том, что должно произойти... на самом деле в AEAD нет понятия "абстрагироваться". SIV диктует, куда идет «метка». ГКМ/СКК нет. SIV является тэг-первым, по спецификации.

SIV не может начать шифрование, пока не получит все данные. Таким образом, его потоковое шифрование будет либо генерировать (что означает, что вы должны знать, чтобы не вызывать его), либо буферизировать (что может привести к времени работы n ^ 2). CCM не может начать работу, пока не будет известна длина; но CNG не позволяет предварительно зашифровать подсказку длины, так что это в той же лодке.

Мы не должны разрабатывать новый компонент, в котором проще сделать что-то неправильно, чем правильно по умолчанию. Потоковое дешифрование делает очень простым и заманчивым подключение класса Stream (как ваше предложение сделать это с CryptoStream), что позволяет очень легко получить ошибку проверки данных до проверки тега, что почти полностью сводит на нет преимущества AE . ( IGcmDecryptor => CryptoStream => StreamReader => XmlReader => «подождите, это незаконный XML...» => адаптивный оракул зашифрованного текста) .

Дело доходит до... покупательского спроса.

Как я, к сожалению, слишком много раз в своей жизни слышал: извините, но вы не тот клиент, которого мы имеем в виду. Я допускаю, что, возможно, вы знаете, как безопасно выполнять GCM. Вы знаете, что следует выполнять потоковую передачу только в изменчивый файл/буфер/и т. д. до проверки тега. Вы знаете, что означает управление одноразовыми номерами, и знаете, как ошибиться. Вы знаете, что нужно обращать внимание на размеры потоков и переходить на новый сегмент GCM после 2 ^ 36-64 байт. Вы знаете, что после того, как все сказано и сделано, это ваша ошибка, если вы все сделаете неправильно.

С другой стороны, клиент, которого я имею в виду, это тот, кто знает: «Я должен зашифровать это», потому что их босс сказал им. И они знают, что при поиске того, как сделать шифрование, в каком-то учебнике говорилось «всегда использовать AE» и упоминается GCM. Затем они находят учебник по шифрованию в .NET, в котором используется CryptoStream. Затем они подключают конвейер, не подозревая, что они только что сделали то же самое, что выбрали SSLv2... поставили галочку в теории, но не на практике. И когда они делают это, _эта_ ошибка принадлежит всем, кто знает лучше, но пусть неправильная вещь будет слишком легко сделать.

Все 143 Комментарий

Краткий обзор предложенного API (стараюсь быть полезным):

  • Span<T> отсутствует в NetStandard2 .
  • "Nonce" очень зависит от реализации, т.е. пахнет ГКМ. Однако даже документы GCM (например, NIST SP800-38D) называют его «IV», что в случае GCM оказывается одноразовым номером. Однако в случае других реализаций AEAD IV не обязательно должен быть одноразовым номером (например, повторение IV в CBC+HMAC не является катастрофическим).
  • Потоковое вещание AEAD должен либо беспрепятственно работать с CryptoStream , либо предоставлять свой собственный «AEADCryptoStream», который так же легко вводить и выводить, как CryptoStream.
  • Реализации AEAD API должны быть разрешены для получения внутренних ключей на основе AAD (ассоциированных данных). Использование AAD исключительно для расчета/проверки тегов является слишком ограничивающим и препятствует созданию более надежной модели AEAD.
  • Методы "Get*" должны что-то возвращать (GetTag). Если они недействительны, они должны что-то устанавливать/менять состояние.
  • Попытка получить тег перед «завершением», вероятно, плохая идея, поэтому «IsFinished» может быть полезным.
  • Разработчики ICryptoTransform думали о повторном использовании, поддержке нескольких блоков и размерах блоков ввода/вывода разного размера. Эти опасения не учитываются.

В качестве доказательства работоспособности API AEAD первой реализацией такого предлагаемого API должен быть не AES-GCM, а классический/стандартный AES-CBC с тегом HMAC. Простая причина этого заключается в том, что сегодня любой может создать реализацию AES-CBC+HMAC AEAD с помощью простых, существующих, хорошо известных классов .NET. Давайте сначала заставим старый скучный [AES-CBC+HMAC] работать над новыми API AEAD, так как каждый может легко получить MVP и протестировать его.

Проблема именования nonce/IV была чем-то, о чем я не знал, доволен переходом на IV, так что изменится.

Что касается методов Get, возвращающих что-то, это позволяет избежать каких-либо аллокаций. Может быть перегрузка Get(), которая что-то возвращает. Может быть, это требует изменения имени, но я довольно женат на идее, что весь API должен быть в основном свободным от распределения.

Что касается потоков и т. д., меня это не слишком беспокоит, поскольку они представляют собой API более высокого уровня, которые можно легко создать из примитивов более низкого уровня.

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

Что касается того, какой шифр, я не думаю, что какой-то конкретный шифр должен быть единственной целью, чтобы доказать новый API общего назначения, он должен соответствовать числу. Должны быть включены AES GCM и CBC.

(Все отзывы по теме, хорошие или плохие, всегда полезны!)

  • Класс или интерфейс?
  • Как взаимодействуют с этим текущие классы SymmetricAlgorithm, если вообще взаимодействуют?
  • Как это можно использовать для постоянных ключей, например TripleDESCng и AesCng?
  • Кажется, что многие из этих диапазонов могут быть ReadOnlySpan.

@Drawaes спасибо за начало работы над этим API. Несколько мыслей:

  1. Генерация и проверка тегов — очень важная часть этого API, поскольку неправильное использование тегов может свести на нет всю цель. Если возможно, я бы хотел, чтобы теги были встроены в операции инициализации и завершения, чтобы их нельзя было случайно проигнорировать. Вероятно, это означает, что шифрование и дешифрование не должны использовать одни и те же методы инициализации и финализации.
  2. У меня смешанные чувства по поводу вывода блоков во время расшифровки, прежде чем дойти до конца, поскольку данные не заслуживают доверия, пока тег не будет проверен (что невозможно сделать, пока все данные не будут обработаны). Нам нужно очень тщательно оценить этот компромисс.
  3. Нужен ли сброс? Должен закончить только сброс? Мы делаем это на инкрементных хэшах (но им не нужны новые IV)

@bartonjs

  1. Класс, как часто можно увидеть в BCL с интерфейсом, который вы не сможете расширить позже, не сломав все. Интерфейс похож на щенка на всю жизнь... если методы интерфейса по умолчанию не могут рассматриваться как решение этой проблемы.
    Кроме того, запечатанный класс из абстрактного типа на самом деле быстрее (на данный момент), потому что jitt теперь может девиртуализировать методы... Так что это в основном бесплатно. диспетчеризация интерфейса не так хороша (все еще хороша, но не так хороша)
  2. Я не знаю, как бы вы хотели, чтобы это работало? Меня мало интересуют текущие вещи, так как они настолько запутаны, что я бы просто пропатчил все разумные современные алгоритмы прямо (оставьте 3DES в других классах :) Но у меня нет всех ответов, так что у вас есть какие-либо дополнительные мысли по этому поводу?
  3. Сохраняемые ключи должны быть простыми. Создайте метод расширения для ключевого метода или хранилища сохраняемости.
MyKeyStore.GetCipher();

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

Да на пролеты только для чтения буду настраивать когда не на трубке на телефоне.

@morganbr нет проблем ... я просто хочу, чтобы это произошло больше всего на свете ;)

  1. Можете ли вы дать фрагмент кода о том, как вы видите, что это работает? Не уверен, что это так, но код всегда вносит ясность
  2. К сожалению, вы действительно должны выплюнуть блоки рано. С hmac и хешированием у вас нет, но у вас нет промежуточных данных, только состояние. Так что в этом случае вам придется буферизовать неизвестное количество данных. Давайте посмотрим на пример конвейеров и TLS. Мы можем записать 16 КБ открытого текста, но буферы конвейера сегодня имеют размер страницы 4 КБ. Таким образом, мы в лучшем случае хотели бы зашифровать/расшифровать 4 * 4k. Вы не даете мне ответ до конца, вам нужно выделить внутреннюю память для хранения всего этого, а затем, я полагаю, выбросить ее, когда я получу результат? Или будешь вытирать. Что, если я расшифрую 10 МБ, а вы сохраните эту память после того, как мне придется беспокоиться об использовании скрытой памяти.
  3. Не на 100% в отношении инициализации/сброса (не ваши идеи, моя текущая форма API), это меня не устраивает, поэтому я открыт для нового предложения!

Меня мало интересуют текущие вещи, так как они настолько запутаны, что я бы просто исправил все разумные современные алгоритмы прямо (оставьте 3DES в других классах :)

Проблема может заключаться в том, что форматы контейнеров, такие как EnvelopedCms (или EncryptedXml), возможно, должны работать с 3DES-CBC, AES-CBC и т. д. Кто-то хочет расшифровать что-то, зашифрованное с помощью ECIES/AES-256-CBC-PKCS7/HMAC-SHA-2. -256, вероятно, не почувствовали бы, что занимаются старыми и грубыми вещами.

Если это должно быть только для AE, то это должно быть отражено где-то в названии. Прямо сейчас это общий «шифр» (который я собирался / собирался в какой-то момент сесть со словарем / глоссарием и выяснить, есть ли слово для «алгоритма шифрования в режиме работы», поскольку я думаю, что "шифр" == "алгоритм", поэтому "Айс").

:) Я просто указал, что это не моя предметная область или не представляет для меня большого интереса, поэтому я готов уступить вам и сообществу в этой теме. Я не думал о последствиях этого.


После быстрого сканирования их один из вариантов — позволить им взять экземпляр класса «Шифр» или как там он называется. Это может быть не сделано в первой волне, но может быстро последовать за ней. Если API суперэффективен, то я не вижу причин, по которым они должны заниматься своими делами внутри, и это именно тот случай использования этого API.

В качестве дополнения к названию... Должен признать, что это сложно.
Openssl = шифр
Рубин = шифр
Go = пакет шифров с интерфейсами типа «утка» для AEAD и т. д.
Java = шифр

Сейчас я за то, чтобы быть другим, но... тенденция есть. Если возможно что-то лучше, это круто.

Возможно "BlockModeCipher"...?

Я внес несколько изменений, я изменю название, если будет выбрано лучшее имя.

Когда я начал пытаться отвечать на вопросы, я понял, что в API уже отсутствует дифференциация шифрования/дешифрования, поэтому в вашем примере он не знает, шифровать или дешифровать данные. Включение этого может внести некоторую ясность.

Я мог бы представить пару способов, которыми API может обеспечить правильное использование тегов (исходя из предположения, что это API AEAD, а не просто симметричное шифрование, поскольку у нас уже есть SymmetricAlgorithm/ICryptoTransform/CryptoStream). Не принимайте их как предписывающие, просто как пример принудительного применения тегов.
По методу:

class Cipher
{
   void InitializeEncryption(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv);
   // Ensures decryptors get a tag
   void InitializeDecryption(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv, ReadOnlySpan<byte> tag);
   // Ensure encryptors produce a tag
    void FinishEncryption(ReadOnlySpan<byte> input, Span<byte> output, Span<byte> tag);
   // Throws if tag didn't verify
   void FinishDecryption(ReadOnlySpan<byte> input, Span<byte> output);
   // Update and properties are unchanged, but GetTag and SetTag are gone
}

По классам:

class Cipher
{
    // Has properties and update, but Initialize and Finish aren't present
}
class Encryptor : Cipher
{
    void Initialize(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv);
    void Finish(ReadOnlySpan<byte> input, Span<byte> output, Span<byte> tag);
}
class Decryptor : Cipher
{
   void Initialize(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv, ReadOnlySpan<byte> tag);
   // Throws if tag didn't verify
   void Finish(ReadOnlySpan<byte> input, Span<byte> output);
}
class AesGCMEncryptor : Encryptor {}
class AesGCMDecryptor : Decryptor {}
}

Тем не менее, если он не буферизируется при расшифровке, практично ли гарантировать, что расшифровка действительно завершена и тег проверен? Может Update как-то сообразит, что пора проверить? Это то, что должен делать Dispose? (Утилизация немного опасна, так как вы, возможно, уже доверяете данным к тому времени, когда вы удаляете объект)

Что касается именования, наш прецедент — SymmetricAlgorithm и AsymmetricAlgorithm. Если это предназначено для AEAD, некоторые идеи могут быть AuthenticatedSymmetricAlgorithm или AuthenticatedEncryptionAlgorithm.

Некоторые мысли и идеи API:

public interface IAEADConfig
{
    // size of the input block (plaintext)
    int BlockSize { get; }

    // size of the output per input-block;
    // typically a multiple of BlockSize or equal to BlockSize.
    int FeedbackSize { get; }

    // IV size; CAESAR completition uses a fixed-length IV
    int IVSize { get; }

    // CAESAR competition uses a fixed-length key
    int KeySize { get; }

    // CAESAR competition states that typical AEAD ciphers have a constant gap between plaintext length
    // and ciphertext length, but the requirement is to have a constant *limit* on the gap.
    int MaxTagSize { get; }

    // allows for AE-only algorithms
    bool IsAdditionalDataSupported { get; }
}

public interface ICryptoAEADTransform : ICryptoTransform
{
    // new AEAD-specific ICryptoTransform interface will allow CryptoStream implementation
    // to distinguish AEAD transforms.
    // AEAD decryptor transforms should throw on auth failure, but current CryptoStream
    // logic swallows exceptions.
    // Alternatively, we can create a new AEAD_Auth_Failed exception class, and
    // CryptoTransform is modified to catch that specific exception.
}

public interface IAEADAlgorithm : IDisposable, IAEADConfig
{
    // separates object creation from initialization/keying; allows for unkeyed factories
    void Initialize(ArraySegment<byte> key);

    void Encrypt(
        ArraySegment<byte> iv, // readonly; covered by authentication
        ArraySegment<byte> plaintext, // readonly; covered by authentication
        ref ArraySegment<byte> ciphertext, // must be of at least [plaintext_length + MaxTagSize] length. iv is not part of ciphertext.
        ArraySegment<byte> additionalData = default(ArraySegment<byte>) // readonly; optional; covered by authentication
        ); // no failures expected under normal operation - abnormal failures will throw

    bool Decrypt(
        ArraySegment<byte> iv, // readonly
        ArraySegment<byte> ciphertext, // readonly
        ref ArraySegment<byte> plaintext, // must be of at least [ciphertext_length - MaxTagSize] length.
        ArraySegment<byte> additionalData = default(ArraySegment<byte>), // readonly; optional
        bool isAuthenticateOnly = false // supports Authentication-only mode
        );// auth failures expected under normal operation - return false on auth failure; throw on abnormal failure; true on success

    /*  Notes:
        * Array.LongLength should be used instead of Array.Length to accomodate byte arrays longer than 2^32.
        * Ciphertext/Plaintext produced by Encrypt()/Decrypt() must be determined *only* by method inputs (combined with a key).
          - (ie. if randomness or other hidden inputs are needed, they must be a part of iv)
        * Encrypt()/Decrypt() are allowed to write to ciphertext/plaintext segments under all conditions (failure/success/abnormal)
          - some implementations might be more stringent than others, and ex. not leak decrypted plaintext on auth failures
    */

    ICryptoAEADTransform CreateEncryptor(
        ArraySegment<byte> key,
        ArraySegment<byte> iv,
        ArraySegment<byte> additionalData = default(ArraySegment<byte>)
        );

    ICryptoAEADTransform CreateDecryptor(
        ArraySegment<byte> key,
        ArraySegment<byte> iv,
        ArraySegment<byte> additionalData = default(ArraySegment<byte>)
        );

    // Streaming AEAD can be done with good-old CryptoStream
    // (possibly modified to be AEAD-aware).
}

@sdrapkin , спасибо за мысли. Где должны располагаться теги в вашем API? Являются ли они неявно частью зашифрованного текста? Если это так, это подразумевает протокол, которого на самом деле нет в некоторых алгоритмах. Я хотел бы понять, какие протоколы могут интересовать людей, чтобы увидеть, допустимо ли неявное размещение тегов или его нужно переносить отдельно.

@morganbr Я тоже заметил проблему с шифрованием / дешифрованием, но сегодня вечером у меня не было времени исправить, так что рад за ваш дизайн. Я предпочитаю методы классу, так как он обеспечивает более агрессивную переработку (буферы для ключей и IV могут складываться).

Что касается проверки перед утилизацией. К сожалению, невозможно сказать об окончании операции.

Я бы сказал, что интерфейсы @sdrapkin не подходят из-за проблемы с версией, упомянутой ранее. Если только мы не будем полагаться на имплантацию по умолчанию в будущем. Кроме того, диспетчеризация интерфейса медленнее. Сегменты массива также являются нашими, поскольку span является более универсальным нижним примитивом. Однако можно было бы добавить методы расширения для сегмента массива, если позже возникнет потребность.

Некоторые из ваших свойств представляют интерес, поэтому они будут обновляться, когда я буду за компьютером, а не за телефоном.

Хорошая обратная связь со всех сторон!

Теги @morganbr являются частью зашифрованного текста. Это смоделировано на основе CAESAR API (который включает AES-GCM).

@Drawaes Я использовал интерфейсы только для иллюстрации мыслей - со статическими методами/классами у меня все в порядке. Охватыватьне существует. Меня не волнует, что может быть, а может и не быть - этого нет в NetStandard2, и его нет в обычном .NET, который на самом деле используют серьезные проекты (да-да, я знаю, что это в Core, но Core - это игрушка сейчас). Я был бы рад лично рассмотреть Spanкогда увижу - до тех пор ArraySegmentявляется ближайшим NetStandard API, который фактически поставляется.

Я более подробно рассмотрю полезный API CAESAR.

Что касается Span, я полагаю, что он будет поставляться примерно в период времени 2.1, который, надеюсь, будет таким же, как и первая реализация этого API (или, по крайней мере, в самое раннее возможное время).

Если вы взглянете на текущий предварительный пакет nuget, он поддерживает стандарт .net 1.0, и нет никаких планов изменить его при выпуске.

Возможно , @stephentoub может подтвердить это, поскольку он работает над добавлением API на основе Span в структуру, как мы говорим.

(Nuget для Span) [https://www.nuget.org/packages/System.Memory/4.4.0-preview2-25405-01]

Так что я бы сказал, что это единственный реальный выбор для совершенно нового API. Затем можно добавить методы расширения и т. д., чтобы взять ArraySegment, если вы так решили, и если он достаточно полезен, его можно добавить в структуру, но превратить ArraySegment в Span тривиально, но другой способ требует копирования данных.

Проблема, которую я вижу с этим API выше, заключается в том, что это будет катастрофой для производительности любых «фрагментированных» данных. Скажем, например, сетевой трафик, если один аутентифицированный блок разделен на несколько операций чтения из существующего потока, мне нужно буферизовать все это в одну [вставить структуру данных] и зашифровать/расшифровать все сразу. Поражение всех попыток сделать любую нулевую копию этих данных.

Сетевые фреймворки, такие как те, которые предоставляет Pipelines, умудряются избегать почти всех копий, но затем, если они наткнутся на какую-либо криптографию в этом API, все это пропадет.

Отдельный объект конфигурации (или сумка) фактически участвовал в недавнем обсуждении другого API, которое у меня было. Я не против этого в принципе, так как если он разрастется в будущем, он может превратиться в беспорядок, чтобы иметь большое количество свойств на основном объекте.

Со мной произошло несколько вещей.

  • Текущее предложение называет TagSize выходным значением (ну, свойством только для получения). Но как для GCM, так и для CCM это входные данные для шифрования (и полученные из расшифровки, поскольку вы предоставили фактический тег).
  • Предложение предполагает, что ввод и вывод могут происходить одновременно и по частям.

    • IIRC CCM не может выполнять потоковое шифрование (длина открытого текста является входом на первом этапе алгоритма).

    • Заполненные режимы отстают от расшифровки (по крайней мере, на один) блок, поскольку до тех пор, пока не будет вызван Final, они не знают, поступают ли дополнительные данные / требуется ли текущему блоку удаление заполнения.

  • Некоторые алгоритмы могут считать, что элемент AD требуется в начале операции, что делает его больше похожим на параметр Init/ctor, чем на связь с поздней привязкой.

Я не знаю, должны ли форматы контейнеров (EnvelopedCms, EncryptedXml) извлекать ключ, или они просто должны будут сгенерировать его и запомнить (до тех пор, пока им нужно, чтобы записать его).

(Очевидно, вчера я не нажал кнопку «комментировать», поэтому после «Я внес несколько изменений» в 1910Z ничего не будет подтверждено)

Истинный размер тега должен быть переменным. Согласованный.

Если мы пока просто посмотрим на шифрование, чтобы упростить вариант использования. Вы правы, что некоторые шифры ничего не вернут, меньше или больше. Существует общий вопрос о том, что произойдет, если вы не предоставите достаточно большой буфер.

В новых интерфейсах TextEncoding, использующих span, было предложено иметь возвращаемый тип enum, чтобы определить, достаточно ли места для вывода или нет, и вместо этого размер фактически записывался в параметре «out». Это возможность.

В случае с CCM я бы просто сказал, что он ничего не возвращает и должен будет выполнять внутреннюю буферизацию до тех пор, пока вы не вызовете finish, после чего он захочет сбросить всю партию. Ничто не мешает вам просто вызвать финиш в качестве первого вызова, если у вас есть все данные в одном блоке (в этом случае может быть лучшее имя). Или можно кинуть, если попробуете апдейт на тех шифрах. CNG возвращает ошибку недопустимого размера, если вы, например, пытаетесь выполнить продолжение на CCM.

Что касается того, когда тег установлен при расшифровке, вы часто не знаете об этом, пока не прочитаете весь пакет, если мы возьмем TLS в качестве примера, нам может потребоваться прочитать 8 * 2k сетевых пакетов, чтобы добраться до тега в конце блока 16к. Таким образом, теперь мы должны буферизовать все 16 КБ, прежде чем мы сможем начать расшифровку, и нет возможности перекрытия (я не говорю, что это будет использоваться для TLS, просто процесс, связанный с вводом-выводом, является обычным для этих типов шифров, будь то дисковый или сеть).

@Drawaes re. фрагментированные потоки и ограничения буферизации:
Вы должны выбрать свои сражения. Вы не сможете создать единый API, соответствующий каждой единственной приятной цели в мире AE — а таких целей много. Бывший. в Inferno есть приличная потоковая передача AEAD по частям, но это никоим образом не стандарт, и такого стандарта не существует. На более высоком уровне целью являются «безопасные каналы» (см. это , это и это ).

Однако сейчас нам нужно думать о меньшем. Разделение на фрагменты/ограничение буфера даже не рассматривается для усилий по стандартизации ( раздел AEADs с большими открытыми текстами ).

Операции шифрования/дешифрования в основном связаны с преобразованиями. Для этих преобразований требуются буферы, и они не используются на месте (выходные буферы должны быть больше, чем входные буферы — по крайней мере, для преобразования шифрования).

RFC 5116 также может представлять интерес.

@Drawaes , интересно, что вы упомянули TLS. Я бы сказал, что SSLStream (если бы он использовал этот API) не должен возвращать какие-либо неаутентифицированные результаты приложению, поскольку у приложения не будет никакого способа защитить себя.

Конечно, но это проблема SSLStreams. Я создал именно этот прототип (управляемый TLS на уровне протокола, обращающийся к CNG и OpenSSL для битов шифрования) поверх конвейеров. Логика была довольно проста, приходят зашифрованные данные, расшифровываете буфер на месте, прикрепляете к исходящей границе и повторяете, пока не дойдете до тега. На завершении вызова тега...

Если он бросает закрыть конвейер. Если он не бросает, сбросьте, чтобы следующий этап работал либо в том же потоке, либо через диспетчеризацию.

Мое доказательство концепции не было готово к прайм-тайму, но при использовании этого и избегании большого количества копий и т. д. оно показало очень приличное увеличение производительности;)

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

Крипта OpenSsl и CNG имеют один и тот же метод Update,Update,Finish. Finish может вывести тег, как обсуждалось. Обновления должны иметь размер блока (для CNG), а для OpenSsl требуется минимальная буферизация, чтобы получить размер блока.

Поскольку они примитивны, я не уверен, что мы можем ожидать от них функциональности более высокого уровня. Если бы мы разрабатывали API «пользовательского» уровня, а не примитивы для их создания, я бы сказал, что генерация ключа, построение IV и целые аутентифицированные фрагменты должны быть реализованы, поэтому я думаю, это зависит от того, каков на самом деле целевой уровень этого API.

Неверная кнопка

@blowdart , у которого есть интересные идеи по управлению nonce.

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

Итак ... сделайте это требованием. Вы должны подключить управление одноразовыми номерами... и не предоставлять реализацию по умолчанию или вообще какие-либо реализации. Это, а не простой

cipher.Init(myKey, nonce);

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

Идея @blowdart может помочь как с проблемами управления nonce, так и с различиями между алгоритмами. Я согласен с тем, что, вероятно, важно не иметь встроенной реализации, чтобы пользователи понимали, что управление одноразовыми номерами — это проблема, которую им необходимо решить. Как это выглядит?

interface INonceProvider
{
    public void GetNextNonce(Span<byte> writeNonceHere);
}

class AesGcmCipher : Cipher
{
    public AesGcmCipher(ReadOnlySpan<byte> key, INonceProvider nonceProvider);
}

// Enables platform-specific hardware keys
class AesGcmCng : Cipher
{
    public AesGcmCng(CngKey key, INonceProvider nonceProvider);
}

// Example of AEAD that might not need a nonce
class AesCBCHmac : Cipher
{
    public AesCBC(ReadOnlySpan<byte> key)
}

class Cipher
{
    // As above, but doesn't take keys, IVs, or nonces
}

Но в чем смысл INonceProvider? Это просто дополнительный интерфейс/тип, если Init просто принимает значение none и его необходимо вызвать перед запуском любого блока, разве это не то же самое без дополнительного/интерфейса?

Кроме того, я не эксперт по криптографии, но AES не требует IV (который не является одноразовым номером, но должен быть предоставлен пользователем?)

Это просто дополнительный интерфейс/тип

В этом-то и дело. По сути, это говорит о том, что _nonce management_ является проблемой, а не просто передачей нулевого или даже случайного массива байтов. Это также может помочь предотвратить непреднамеренное повторное использование, если люди интерпретируют GetNextNonce как означающее «вернуть что-то другое, чем вы сделали в прошлый раз».

Также полезно не использовать его для алгоритмов, у которых нет проблем с управлением одноразовыми номерами (например, AES SIV или, возможно, AES+CBC+HMAC).

Точные требования к IV/nonce зависят от режима. Например:

  • AES ECB не требует одноразового номера или IV
  • AES GCM требует 96-битного одноразового номера, который нельзя использовать повторно, иначе безопасность ключа будет нарушена. Низкая энтропия хороша, если одноразовый номер не используется повторно.
  • AES CBC требует 128-битного IV, который должен быть случайным. Если IV повторяется, он только показывает, было ли отправлено то же самое сообщение ранее.
  • AES SIV не нуждается в явном IV, поскольку он получает его из других входных данных.

AES CBC нуждается в IV, верно? Итак, у вас будет InitializationVectorProvider? Это не одноразовый номер, а
одноразовый лайк и повторное использование последнего блока привели к атаке tls, потому что iv можно предсказать. Вы явно не можете использовать, скажем, последовательный одноразовый номер для CBC.

Да, но IV не одноразовый номер, поэтому вы не можете использовать термин nomce provider.

Я не имел в виду, что AES CBC не нуждается в капельнице — она нужна. Я просто хотел порассуждать о некоторых схемах, которые получают IV из других данных.

Конечно, я думаю, моя точка зрения в том, что мне это нравится в целом ... Я могу объединить провайдера;), но либо назовите его iv-провайдером, либо используйте 2 интерфейса, чтобы было ясно намерение.

@morganbr INonceProvider фабрики, переданные в конструкторы шифров, - плохой дизайн. Он полностью упускает из виду тот факт, что _nonce_ не существует сам по себе: ограничение "_...используется один раз_" имеет _context_. В случае режимов CTR и GCM (который использует CTR) _context_ _nonce_ является _key_. Т.е. _nonce provider_ должен возвращать одноразовый номер, который используется только один раз в контексте определенного _key_.

Поскольку INonceProvider в предлагаемом вами API не поддерживает ключи, он не может генерировать правильные одноразовые номера (кроме как с помощью случайности, которая не является одноразовым номером, даже если битовое пространство было достаточно большим для статистической случайности). безопасно).

Я не совсем уверен, к чему стремится эта дискуссионная ветка. Обсуждаются различные идеи дизайна Authenticated-Encryption... хорошо. Как насчет интерфейсов Authenticated Encryption, уже встроенных в .NET Core, особенно в его ASP.NET API? Есть IAuthenticatedEncryptor и т. д. Все эти возможности уже реализованы, расширяемы и поставляются сегодня как часть .NET Core. Я не говорю, что криптография DataProtection идеальна, но планируется ли игнорировать их? Изменить их? Ассимилировать или рефакторить?

Криптовалюта DataProtection была создана @GrabYourPitchforks (Леви Бродерик). Он знает предмет, и его мнение/вклад/отзывы будут наиболее ценны для этого сообщества. Я, как и все остальные, наслаждаюсь крипто-тематическими развлечениями, но если кто-то хочет серьезно заняться дизайном крипто API, то следует привлечь настоящих экспертов, которые уже работают в команде MS.

@sdrapkin , поставщики одноразовых номеров должны быть осведомлены о ключах, это хороший момент. Интересно, есть ли разумный способ изменить эти API, чтобы обеспечить это.

DataProtection — прекрасный API, но это конструкция более высокого уровня. Он инкапсулирует генерацию ключей и управление ими, IV и выходной протокол. Если кому-то нужно использовать (скажем) GCM в реализации другого протокола, DataProtection не сделает это возможным.

В команду криптографии .NET входят @bartonjs , @blowdart и я. Конечно, если @GrabYourPitchforks захочет присоединиться, мы будем рады.

Я согласен с @morganbr в том, что это должен быть низкоуровневый примитив (на самом деле это сказано в заголовке). В то время как защита данных и т. д. предназначены для использования непосредственно в пользовательском коде и уменьшают возможность выстрелить себе в ногу, я вижу, как этот примитив позволяет фреймворку и библиотекам создавать конструкции более высокого уровня на общей основе.

Имея это в виду, провайдер в порядке, если он должен предоставляться каждый раз, когда предоставляется ключ. Это делает его немного запутанным, позвольте мне объяснить, используя TLS (это хорошо известное использование блочных режимов AES для сетевого трафика).

Я получаю «кадр» (возможно, более 2 + TU с MTU ~ 1500 в Интернете). Он содержит одноразовый номер (или часть одноразового номера с 4 байтами, оставленными «скрытыми»), затем я должен установить это значение в «поставщике» оболочки, а затем вызвать расшифровку и пройти цикл расшифровки буферов, чтобы получить один простой текст .

Если тебя это устраивает, я могу с этим жить. Я очень хочу, чтобы это продвигалось вперед, поэтому я хочу обновить дизайн выше до чего-то, с чем мы можем согласиться.

Спасибо, что разветвили дискуссию, появилось немного свободного времени, чтобы перейти к этому. @Drawaes , можете ли вы подтвердить/обновить верхний пост в качестве золотого стандарта/цели этого развивающегося разговора? Если нет, можете ли вы обновить его?

Я вижу, что текущее предложение имеет фатальную проблему, а затем другие проблемы, связанные с излишней болтливостью.

// current proposed usage
using (var cipher = new AesGcmCipher(bitsize: 256))
{
    cipher.Init(myKey, nonce);
    while (!inputSource.EOF)
    {
        var inputSpan = inputSource.ReadSpan(cipher.BlockSize);
        cipher.Update(inputSpan);
        outputSource.Write(inputSpan);
    }
    cipher.AddAssociatedData(extraInformation); // <= fatal, one can't just do this
    cipher.Finish(finalBlockData);
    cipher.GetTag(tagData);
}

Если вы посмотрите на настоящий примитив AEAD, данные о конфиденциальности и аутентифицированные данные перемешаны с блокировкой. См. это для Auth Data 1 и CipherText1. Это, конечно, продолжается для нескольких блоков, а не только для 1.highlighted

Так как весь мир мем, не могу удержаться, извините :)
Can't resist

Кроме того, API кажется болтливым с новыми, инициализирующими, обновлениями и т. д. Я бы предложил эту модель программиста

// proposed, see detailed comments below
using (var cipher = new AesGcmCipher(myKey, iv, aad)) // 1
{
    // 2
    while (!inputSource.EOF) 
    {
        var inputSpan = inputSource.ReadSpan(16411); // 3
        var outSpan = cipher.Encrypt(inputSpan); // 4
        outputSource.Write(outSpan); 
    }    
    var tag = cipher.Finish(finalBlockData); // 5
}
  1. Обычно AAD << открытый текст, поэтому я видел cipher.Init(mykey, nonce, aad); , где весь AAD передается в виде буфера, а затем шифр перебирает остальную часть потенциально гигабайтного потока. (например , параметр CipherModeInfo BCryptEncrypts ). Кроме того, размер myKey уже устанавливает AES128, 192, 256, нет необходимости в другом параметре.
  2. Init становится необязательным API в случае, если вызывающая сторона хочет повторно использовать существующий класс, существующие константы AES и пропустить генерацию подключа AES, если ключ AES тот же.
  3. API шифра должен защищать вызывающую сторону от внутренних механизмов управления размером блока, как и большинство других криптографических API или даже существующих API .NET. Вызывающий абонент заинтересован в том, чтобы размер буфера был оптимизирован для его варианта использования, например сетевой ввод-вывод через 16K+ буферов). Демонстрация с простым числом> 16 КБ для проверки предположений о реализации
  4. inputSpan доступен только для чтения. И ввод. Так что нужен outSpan
  5. Update() шифрует или расшифровывает? Просто используйте интерфейсы Encrypt и Decrypt, соответствующие ментальной модели разработчика. tag также являются наиболее важными требуемыми данными в данный момент, верните их.

На самом деле, идем дальше, почему бы просто не

using (var cipher = new AesGcmCipher(myKey, iv, aad))
{
    var tag = cipher.EncryptFinal(inputSpan, outputSpan);
}

Кроме того, пожалуйста, держитесь подальше от INonceProvider и тому подобного. Крипто-примитивам это не нужно, просто придерживайтесь byte[] iv (мой любимый вариант для небольших данных) или Span (предполагаемый новый крутой, но слишком много абстракций, ИМХО). Поставщик одноразовых номеров работает на уровне выше, и результатом этого может быть просто iv , показанный здесь.

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

Мы говорим о AEAD в целом, к которому относится GCM. Итак, во-первых, общий случай ( iv ) должен определять дизайн, а не конкретный случай ( nonce ).

Во-вторых, как простой переход от byte[] iv к GetNextNonce(Span<byte> writeNonceHere) на самом деле решает проблему одноразового номера? Вы только изменили имя/метку проблемы, одновременно сделав ее более сложной, чем она должна быть.

В-третьих, поскольку мы переходим к политикам защиты iv , должны ли мы также переходить к политикам защиты ключей? Как насчет политики распределения ключей? Это, очевидно, проблемы более высокого уровня.

Наконец, nonce крайне ситуативен при использовании на более высоких уровнях. Вы не хотите иметь хрупкую архитектуру, в которой межуровневые задачи связаны друг с другом.

Честно говоря, если бы мы могли скрывать примитивы, пока кто-нибудь не сделает жест, чтобы сказать, что я знаю, что делаю, я бы настаивал на этом. Но мы не можем. Существует слишком много плохих криптографических реализаций, потому что люди думают: «О, это доступно, я буду использовать это». Черт возьми, посмотрите на сам AES, я просто буду использовать его без HMAC.

Я хочу, чтобы API были безопасными по умолчанию, и если это означает немного больше боли, то, честно говоря, я полностью за это. 99% разработчиков не знают, что они делают, когда дело доходит до криптографии, и упрощение работы для 1%, которые знают, должно иметь меньший приоритет.

Спан не существует. Меня не волнует, что может быть, а что нет - этого нет в NetStandard2

@sdrapkin , как указывает @Drawaes , Span<T> — это .NET Standard 1.0 , поэтому его можно использовать в любой среде. Это также безопаснее, чем ArraySegment<T> , поскольку позволяет получить доступ только к фактическому окну, на которое ссылается; а не весь массив.

Также ReadOnlySpan<T> предотвращает изменение этого окна; опять же, в отличие от сегмента массива, где все переданное может изменить и/или сохранить ссылку на переданный массив.

Span должен быть общим для синхронизации API (тот факт, что API, использующий Span, может дополнительно справляться с stackalloc'd, собственной памятью, а также массивами, является обледенением)

то есть
С ArraySegment только для чтения предлагается через документы; и никакие чтения/модификации за пределами не предотвращаются

void Encrypt(
    ArraySegment<byte> iv, // readonly; covered by authentication
    ArraySegment<byte> plaintext, // readonly; covered by authentication
    ref ArraySegment<byte> ciphertext, // must be of at least [plaintext_length + MaxTagSize] length. iv is not part of ciphertext.
    ArraySegment<byte> additionalData = default(ArraySegment<byte>) // readonly; optional; covered by authentication
    );

Однако с Span только для чтения обеспечивается API; а также предотвращение чтения за пределами массивов

void Encrypt(
    ReadOnlySpan<byte> iv, // covered by authentication
    ReadOnlySpan<byte> plaintext, // covered by authentication
    Span<byte> ciphertext, // must be of at least [plaintext_length + MaxTagSize] length. iv is not part of ciphertext.
    ReadOnlySpan<byte> additionalData = ReadOnlySpan<byte>.Empty) // optional; covered by authentication
    );

Это гораздо лучше передает намерение с параметрами; и менее подвержен ошибкам в отношении чтения/записи за пределами границ.

@benaadams @Drawaes никогда не говорил, что Span<T> находится в NetStandard ( любом поставленном NetStandard ). Он сказал следующее: (1) согласен с тем, что Span<T> не входит ни в один поставляемый NetStandard; (2) что Span<T> будет _"доставляться в период времени 2.1"_.

Тем не менее, для этой конкретной проблемы Github (только для чтения) обсуждение Span<T> прямо сейчас зашло в тупик - нет ясности в отношении объема или цели разрабатываемого API.

Либо мы используем необработанный низкоуровневый примитивный API AEAD (например, аналогичный CAESAR):

  • Плюсы: хорошо подходит для AES-GCM/CCM, существующие тестовые векторы из хороших источников (NIST, RFC). @sidshetye будет счастлив. @blowdart будет размышлять о том, как сделать примитивы настолько примитивными, но в конце концов увидит Инь и Ян, потому что примитивы примитивны и нет способа защитить их от детей.
  • Минусы: опытные пользователи (пресловутый 1%) будут ответственно использовать низкоуровневые API-интерфейсы, в то время как другие неопытные пользователи (99%) будут использовать их неправильно для написания сломанного программного обеспечения .NET, которое будет отвечать за подавляющее большинство .NET. CVE, которые будут в значительной степени способствовать восприятию .NET как небезопасной платформы.

Или мы используем высокоуровневый API AEAD с защитой от неправильного использования:

  • Плюсы: 99% неопытных пользователей будут продолжать делать ошибки, но, по крайней мере, не в коде AEAD. Слова @blowdart _ «Я хочу, чтобы API были безопасными по умолчанию» _ глубоко резонируют в экосистеме, и безопасность, процветание и хорошая карма постигают всех. Многие хорошие проекты и реализации API уже доступны.
  • Недостатки: Нет стандартов. Нет тестовых векторов. Нет единого мнения о том, является ли AEAD даже правильной целью для высокоуровневого API потоковой передачи онлайн (спойлер: это не так — см . статью Rogaway ).

Или делаем и то, и другое. Или мы входим в паралич анализа и можем закрыть этот вопрос прямо сейчас.

Я твердо убежден, что, будучи частью основного языка, крипто должен иметь надежный низкоуровневый базовый API. Как только вы это сделаете, создание высокоуровневых API или API «тренировочных колес» может быть быстро объединено ядром или сообществом. Но я призываю любого сделать обратное элегантно. Плюс тема " Общий низкоуровневый примитив для шифров "!

@Drawaes есть ли график, чтобы сойтись и решить эту проблему? Есть ли планы по привлечению людей, не связанных с Microsoft, помимо таких предупреждений GitHub? Например, 30-минутная конференц-связь? Я пытаюсь держаться подальше от кроличьей норы, но мы держим пари, что криптосистема ядра .NET будет на определенном уровне зрелости и стабильности ... так что я могу проводить сортировку для таких дискуссий.

Мы все еще обращаем на это внимание и работаем над этим. Мы встретились с Советом по криптографии Microsoft (группа исследователей и других экспертов, которые консультируют Microsoft по использованию криптографии), и @bartonjs скоро поделится дополнительной информацией.

Основываясь на небольшом наброске потока данных и совете Crypto Board, мы пришли к следующему. Нашей моделью были GCM, CCM, SIV и CBC+HMAC (обратите внимание, что мы сейчас не говорим о выполнении SIV или CBC+HMAC, просто мы хотели проверить форму).

```С#
открытый интерфейс INonceProvider
{
ReadOnlySpanGetNextNonce(int nonceSize);
}

открытый абстрактный класс AuthenticatedEncryptor: IDisposable
{
публичный интервал NonceOrIVSizeInBits { получить; }
публичный интервал TagSizeInBits { получить; }
общественное логическое значение SupportsAssociatedData { get; }
публичный ReadOnlySpanLastNonceOrIV { получить; }
публичный ReadOnlySpanLastTag {получить; }

protected AuthenticatedEncryptor(
    int tagSizeInBits,
    bool supportsAssociatedData,
    int nonceOrIVSizeInBits) => throw null;

protected abstract bool TryEncrypt(
    ReadOnlySpan<byte> data,
    ReadOnlySpan<byte> associatedData,
    Span<byte> encryptedData,
    out int bytesWritten,
    Span<byte> tag,
    Span<byte> nonceOrIVUsed);

public abstract void GetEncryptedSizeRange(
    int dataLength,
    out int minEncryptedLength,
    out int maxEncryptedLength);

public bool TryEncrypt(
    ReadOnlySpan<byte> data,
    ReadOnlySpan<byte> associatedData,
    Span<byte> encryptedData,
    out int bytesWritten) => throw null;

public byte[] Encrypt(
    ReadOnlySpan<byte> data,
    ReadOnlySpan<byte> associatedData) => throw null;

// some variant of the Dispose pattern here.

}

общедоступный закрытый класс AesGcmEncryptor: AuthenticatedEncryptor
{
общедоступный AesGcmEncryptor (ReadOnlySpankeySize, INonceProvider, nonceProvider)
: основание(128, правда, 96)
{
}
}

общедоступный закрытый класс AesCcmEncryptor: AuthenticatedEncryptor
{
публичный AesCcmEncryptor(
ReadOnlySpanключ,
интервал nonceSizeInBits,
нонсепровидер INonceProvider,
int тегSizeInBits)
: base(tagSizeInBits, true, nonceSizeInBits)
{
проверить nonceSize и tagSize на соответствие спецификации алгоритма;
}
}

открытый абстрактный класс AuthenticatedDecryptor: IDisposable
{
общественное абстрактное логическое значение TryDecrypt(
ReadOnlySpanтег,
ReadOnlySpannonceOrIV,
ReadOnlySpanзашифрованные данные,
ReadOnlySpanсвязанные данные,
Охватыватьданные,
out int bytesWritten);

public abstract void GetEncryptedSizeRange(
    int encryptedDataLength,
    out int minDecryptedLength,
    out int maxDecryptedLength);

public byte[] Decrypt(
    ReadOnlySpan<byte> tag,
    ReadOnlySpan<byte> nonceOrIV,
    ReadOnlySpan<byte> encryptedData,
    ReadOnlySpan<byte> associatedData) => throw null;

// some variant of the Dispose pattern here.

}

общедоступный закрытый класс AesGcmDecryptor: AuthenticatedDecryptor
{
публичный AesGcmDecryptor (ReadOnlySpanключ) => выбросить ноль;
}

общедоступный закрытый класс AesCcmDecryptor: AuthenticatedDecryptor
{
общедоступный AesCcmDecryptor (ReadOnlySpanключ) => выбросить ноль;
}
```

Это предложение исключает потоковую передачу данных. У нас действительно нет большой гибкости в этом вопросе. Реальная необходимость (низкая) в сочетании с соответствующими рисками (чрезвычайно высокая для GCM) или их невозможность (CCM) означает, что это просто исчезло.

В этом предложении для шифрования используется внешний источник одноразового номера. У нас не будет публичных реализаций этого интерфейса. Каждое приложение/протокол должно сделать свою собственную привязку ключа к контексту, чтобы оно могло правильно вводить данные. Хотя каждый вызов TryEncrypt будет выполнять только один вызов GetNextNonce, нет никакой гарантии, что этот конкретный TryEncrypt будет успешным, поэтому приложение все еще должно понять, означает ли это, что оно должно повторить попытку одноразового номера. Для CBC+HMAC мы создадим новый интерфейс IIVProvider, чтобы избежать путаницы в терминологии. Для SIV строится IV, поэтому приемлемого параметра нет; и на основе спецификации одноразовый номер (при использовании), кажется, просто рассматривается как часть ассоциированных данных. Таким образом, SIV, по крайней мере, предполагает, что использование nonceOrIV в качестве параметра для TryEncrypt не является общеприменимым.

TryDecrypt определенно выдает неверный тег. Он возвращает false только в том случае, если пункт назначения слишком мал (в соответствии с правилами Try-методов).

Вещи, которые определенно открыты для обратной связи:

  • Должны ли размеры быть в битах (например, в значимых частях спецификаций) или в байтах (поскольку в любом случае допустимы только значения %8, и мы всегда будем делить, а некоторые части спецификаций говорят о таких вещах, как размер одноразового номера в байтах). )?
  • Имена параметров и их порядок.
  • Свойства LastTag/LastNonceOrIV. (Сделать их (доступными для записи) Spans в общедоступном TryEncrypt просто означает, что есть три буфера, которые могут быть слишком маленькими, если разместить их на стороне, где «Try» более понятен; базовый класс может дать обещание, что он будет никогда не предлагайте слишком короткий буфер.).
  • Предлагая алгоритм AE, для которого это не работает.
  • Следует ли переместить associatedData в конец со значением по умолчанию ReadOnlySpan<byte>.Empty ?

    • Или сделаны перегрузки, которые его опускают?

  • Хочет ли кто-нибудь утверждать, что любит или ненавидит методы возврата byte[] ? (Низкое распределение может быть достигнуто с помощью метода Span, это просто для удобства)
  • Методы диапазонов размеров были как бы прикручены в конце.

    • Их цель в том, что

    • Если диапазон назначения меньше min, немедленно верните false.

    • Методы, возвращающие byte[] , будут выделять максимальный буфер, а затем Array.Resize при необходимости.

    • Да, для GCM и CCM min=max=input.Length , но это неверно для CBC+HMAC или SIV.

    • Существует ли алгоритм, который должен учитывать длину ассоциированных данных?

Определенно байты - не биты.
Поставщик одноразовых номеров, который не знает ключей, является большой ошибкой.

Поставщик одноразовых номеров, который не знает ключей, является большой ошибкой.

Вы можете написать поставщика nonce, как хотите. Мы ничего не предоставляем.

Как насчет детерминированной очистки/ IDisposable ?

Как насчет детерминированной очистки/IDisposable?

Хороший звонок. Добавил его в AuthenticatedEncryptor/AuthenticatedDecryptor. Я не думаю, что они должны проверять одноразовость поставщика одноразовых номеров, вызывающий абонент может просто складывать операторы использования.

Концепция/цель INonceProvider не имеют для меня никакого смысла (вторя другим). Пусть примитивы будут примитивными - передавайте одноразовый номер так же, как вы передаете ключ (т. Е. Как байты - как бы они ни были объявлены). Никакая спецификация AE/AEAD не навязывает алгоритм генерации/получения одноразовых номеров — это ответственность более высокого уровня (по крайней мере, в модели «пусть-примитивы-быть-примитивами»).

Нет стрима? Действительно? Каково оправдание принудительного удаления потоковой передачи из потокового шифра, такого как AES-GCM, на базовом уровне ядра?

Например, что ваш совет по криптографии рекомендует для этих двух недавних сценариев, которые мы рассмотрели?

  1. У клиента есть большие медицинские файлы размером от 10 до 30 ГБ. Ядро видит только поток данных между двумя машинами, так что это один проходной поток. Очевидно, что для каждого файла размером 10 ГБ выдается новый ключ, но вы только что сделали каждый такой рабочий процесс бесполезным. Теперь вы хотите, чтобы мы а) буферизовали эти данные (память, без конвейерной обработки) б) выполнили шифрование (все машины в конвейере теперь простаивают!) в) записали данные (первый байт, записанный после a и b, равен 100% Выполнено) ? Пожалуйста, скажи мне, что ты шутишь. Вы, ребята, сознательно возвращаете в игру фразу «шифрование — это бремя».

  2. Блок физической безопасности имеет несколько потоков 4K, которые также зашифрованы для сценариев в состоянии покоя. Выдача свежего ключа происходит на границе 15 ГБ. Вы предлагаете буферизовать весь клип?

Я не вижу никакого вклада от сообщества, людей, которые действительно создают реальное программное обеспечение, с просьбой удалить поддержку потоковой передачи. Но затем команда исчезает из диалога сообщества, собирается внутри, а затем возвращается с чем-то, о чем никто не просил, что-то, что убивает реальные приложения и подтверждает, что «шифрование медленное и дорогое, пропустите его?»

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

Элегантный дизайн устраняет сложность, а не контроль.

Каково оправдание принудительного удаления потоковой передачи из потокового шифра, такого как AES-GCM, на базовом уровне ядра?

Я думаю, это было что-то вроде

Это предложение исключает потоковую передачу данных. У нас действительно нет большой гибкости в этом вопросе. Реальная необходимость (низкая) в сочетании с соответствующими рисками (чрезвычайно высокая для GCM) или их невозможность (CCM) означает, что это просто исчезло.

В GCM слишком много неудачных моментов, когда можно восстановить ключ. Если злоумышленник может выполнить выбранный зашифрованный текст и просмотреть потоковый вывод до проверки тега, он может восстановить ключ. (По крайней мере, так мне говорит один из криптоаналитиков). По сути, если какие-либо данные, обработанные GCM, можно наблюдать в любой момент до проверки тега, ключ скомпрометирован.

Я почти уверен, что Crypto Board рекомендовал бы НЕ использовать GCM для первого сценария, а скорее CBC + HMAC.

Если ваш второй сценарий — кадрирование 4k, и вы шифруете каждый кадр 4k, то это работает с этой моделью. Каждый кадр 4k + nonce + tag расшифровывается и проверяется до того, как вы вернете байты, поэтому вы никогда не утечете ключевой поток / ключ.

Для сравнения: в настоящее время я разрабатываю этот криптографический API «пусть примитивы будут примитивами». Вот мой класс для аутентифицированного шифрования.

Для меня оказалась полезной возможность говорить о криптопримитиве независимо от ключа. Например, я часто хочу подключить конкретный примитив к методу, который работает с любым алгоритмом AEAD, и оставить генерацию ключей и т. д. этому методу. Поэтому есть класс AeadAlgorithm и отдельный класс Key .

Еще одна очень полезная вещь, которая уже предотвратила несколько ошибок, — это использование разных типов для представления данных разных форм, например, Key и Nonce , вместо использования для всего простого byte[] или Span<byte> .


AeadAlgorithm API (нажмите, чтобы развернуть)

public abstract class AeadAlgorithm : Algorithm
{
    public int KeySize { get; }

    public int NonceSize { get; }

    public int TagSize { get; }

    public byte[] Decrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> ciphertext)

    public void Decrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> ciphertext,
        Span<byte> plaintext)

    public byte[] Encrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> plaintext)

    public void Encrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> plaintext,
        Span<byte> ciphertext)

    public bool TryDecrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> ciphertext,
        out byte[] plaintext)

    public bool TryDecrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> ciphertext,
        Span<byte> plaintext)
}

@bartonjs он / она прав, вам нужно полагаться на то, что программа не выводит данные до аутентификации. Так, например, если вы не аутентифицируетесь (или просто еще нет), вы можете контролировать ввод для блока и, следовательно, знать вывод и работать в обратном направлении оттуда...

Например, атака «человек посередине» может вводить известные блоки в поток cbc и выполнять классическую атаку с переворачиванием битов.

Не уверен, как решить проблему с большими блоками данных, иначе, благодаря разделению их на части с помощью последовательных одноразовых номеров или подобных... аля TLS.

Что ж, позвольте мне перефразировать, что я делаю, но только в случае небольших размеров сети, чего недостаточно для библиотеки общего назначения.

В духе открытости, возможно ли раскрыть, кто входит в Совет по обзору криптографии Microsoft (и, в идеале, комментарии/мнения конкретных членов, которые рассмотрели эту тему)? Брайан Ламаккиа и кто еще?

_используя обратную психологию:_

Я рад , что потоковое AEAD отсутствует. Это означает, что Inferno продолжает оставаться единственным практичным потоковым AEAD на основе CryptoStream для среднего человека. Спасибо Совету по обзору MS Crypto!

Основываясь на комментарии @ektrah , его (ее?) подход основан на RFC 5116 , на который я ссылался ранее. В RFC 5116 есть много примечательных цитат:

3.1. Требования к генерации одноразового номера
Для безопасности важно, чтобы одноразовые номера были сконструированы таким образом, чтобы соблюдать требование, чтобы каждое значение одноразового номера было различным для каждого вызова аутентифицированной операции шифрования для любого фиксированного значения ключа.
...

  1. Требования к спецификациям алгоритма AEAD
    Каждый алгоритм AEAD ДОЛЖЕН принимать любой одноразовый номер длиной от N_MIN до N_MAX октетов включительно, где значения N_MIN и N_MAX специфичны для этого алгоритма. Значения N_MAX и N_MIN МОГУТ совпадать. Каждому алгоритму СЛЕДУЕТ принимать одноразовый номер длиной двенадцать (12) октетов. Рандомизированные алгоритмы или алгоритмы с отслеживанием состояния, описанные ниже, МОГУТ иметь нулевое значение N_MAX.
    ...
    Алгоритм аутентифицированного шифрования МОЖЕТ включать или использовать случайный источник, например, для генерации внутреннего вектора инициализации, который включается в вывод зашифрованного текста. Алгоритм AEAD такого типа называется рандомизированным; хотя обратите внимание, что только шифрование является случайным, а дешифрование всегда детерминировано. Рандомизированный алгоритм МОЖЕТ иметь значение N_MAX, равное нулю.

Алгоритм аутентифицированного шифрования МОЖЕТ включать внутреннюю информацию о состоянии, которая поддерживается между вызовами операции шифрования, например, для обеспечения возможности создания различных значений, которые используются алгоритмом в качестве внутренних одноразовых номеров. Алгоритм AEAD такого типа называется stateful. Этот метод может использоваться алгоритмом для обеспечения хорошей безопасности, даже когда приложение вводит одноразовые номера нулевой длины. Алгоритм с отслеживанием состояния МОЖЕТ иметь значение N_MAX, равное нулю.

Одной из идей, потенциально заслуживающих изучения, является передача Nonce нулевой длины/null, которая может даже использоваться по умолчанию. Передача такого «специального» значения Nonce рандомизирует фактическое значение Nonce, которое будет доступно в качестве выходных данных Encrypt.

Если INonceProvider остается по «причинам», другая идея состоит в том, чтобы добавить вызов Reset() , который будет запускаться каждый раз при изменении ключа AuthenticatedEncryptor . Если, с другой стороны, план состоит в том, чтобы никогда не менять ключ экземпляров AuthenticatedEncryptor , это приведет к поломке GC, если мы хотим создать потоковый API шифрования фрагментов (например, чанк = сетевой пакет), и каждый фрагмент должен быть зашифрованы другим ключом (например, протокол Netflix MSL , Inferno и другие). Особенно для параллельных операций enc/dec, когда мы хотели бы поддерживать пул механизмов AEAD и заимствовать экземпляры из этого пула для выполнения enc/dec. Давайте подарим GC немного любви :)

С моей точки зрения, единственной целью криптопримитивов является реализация хорошо разработанных протоколов безопасности более высокого уровня. Каждый такой протокол настаивает на генерации одноразовых номеров по-своему. Например:

  • TLS 1.2 следует рекомендациям RFC 5116 и объединяет 4-байтовый IV с 8-байтовым счетчиком,
  • TLS 1.3 xor — это 8-байтовый счетчик, дополненный 12 байтами с 12-байтовым IV,
  • Noise использует 8-байтовый счетчик, дополненный до 12 байтов в обратном порядке байтов для AES-GCM, и 8-байтовый счетчик, дополненный до 12 байтов в обратном порядке байтов для ChaCha/Poly.

GCM слишком хрупок для рандомизированных одноразовых номеров при типичных размерах одноразовых номеров (96 бит). И я не знаю ни одного протокола безопасности, который действительно поддерживает рандомизированные одноразовые номера.

Нет большого спроса на дополнительные API, предоставляющие криптопримитивы. 99,9% разработчиков нуждаются в высокоуровневых рецептах для сценариев, связанных с безопасностью: хранение пароля в базе данных, шифрование файла в состоянии покоя, безопасная передача обновления программного обеспечения и т. д.

Однако API для таких высокоуровневых рецептов встречаются редко. Единственными доступными API часто являются только HTTPS и криптопримитивы, что вынуждает разработчиков внедрять собственные протоколы безопасности. IMO решение состоит в том, чтобы не прилагать много усилий для разработки API для работы с примитивами. Это API для высокоуровневых рецептов.

Спасибо за отзыв, всем! Пара вопросов:

  1. В то время как потоковое дешифрование может привести к катастрофическим сбоям, потоковое шифрование может быть выполнимо. Потоковое шифрование (наряду с непотоковым вариантом), но только непотоковое дешифрование звучит более полезно? Если да, то есть пара проблем, которые нужно решить:
    а. Некоторые алгоритмы (CCM, SIV) на самом деле не поддерживают потоковую передачу. Должны ли мы поместить потоковое шифрование в базовый класс и буферизовать потоковые входные данные или выбрасывать из производных классов?
    б. Потоковая передача AAD, скорее всего, невозможна из-за ограничений реализации, но она нужна разным алгоритмам в разное время (некоторым она нужна в начале, некоторым не нужна до конца). Должны ли мы требовать его заранее или иметь метод его добавления, который работает, когда позволяют отдельные алгоритмы?
  1. Мы открыты для улучшений INonceProvider, если дело в том, что пользователям необходимо писать код, генерирующий новый одноразовый номер. У кого-нибудь есть другая предложенная форма для этого?

1 . a = Я думаю, что это может быть проблемой, если не предупредить пользователя заранее. Представьте себе сценарий от кого-то выше, файл размером 10 ГБ. Они думают, что получают потоковую передачу, затем через некоторое время другой разработчик меняет шифр, а затем код буферизует 10 ГБ (или пытается), прежде чем вернуть значение.

  1. b = Опять же, с «потоковой» или сетевой идеей, например, AES GCM и т. Д., Вы не получите информацию AAD до конца для расшифровки. Что касается шифрования, я еще не видел случая, когда у вас нет данных заранее. Поэтому я бы сказал, что по крайней мере для шифрования вы должны требовать его в начале, дешифрование более сложное.
  1. Я думаю, что это действительно не проблема, предоставление «байтов» для одноразового номера через интерфейс или просто напрямую не является ни здесь, ни там. Вы можете добиться одного и того же в обоих случаях, я просто нахожу это более уродливым для примитива, но не категорически против, если это помогает людям лучше спать по ночам. Я бы просто вычеркнул это как решенное дело и перешел к другим вопросам.

Что касается процесса обсуждения

@bartonjs : Мы могли бы спорить весь день, если решения за закрытыми дверями, лишенные участия сообщества, являются эффективным оправданием, но мы отойдем от темы, так что я позволю этому быть. Кроме того, без более богатого личного общения или общения в реальном времени я не хочу никого там расстраивать.

О потоковой передаче

1. аргумент «потоковая передача не подразумевает безопасности AES-GCM»

В частности, обработка паром => возврат расшифрованных данных вызывающей стороне до проверки тега => отсутствие безопасности. Это не звук. @bartonjs утверждает, что «выбранный зашифрованный текст => просмотр вывода => восстановление ключа», в то время как @drawaes утверждает, что «управляет вводом для блока => поэтому знает вывод => «работает оттуда» '

Что ж, в AES-GCM единственное , что делает тег, — это проверка целостности (защита от несанкционированного доступа). Это не влияет на конфиденциальность. Фактически, если вы удалите обработку тегов GCM/GHASH из AES-GCM, вы просто получите режим AES-CTR. Именно эта конструкция отвечает за конфиденциальность. И CTR податлив к переворачиванию битов, но не «сломан» ни одним из способов, которые вы двое утверждаете (восстановление ключа или открытого текста), потому что это означало бы, что основной примитив AES скомпрометирован. Если ваш криптоаналитик (кто это?) знает что-то, чего не знают остальные, он должен опубликовать это. Единственное, что возможно, это то, что атакуемый может перевернуть бит N и знать, что бит N открытого текста был перевернут, но они никогда не узнают, что такое фактический открытый текст.

Так

1) конфиденциальность открытого текста всегда соблюдается
2) проверка целостности просто откладывается (до конца потока) и
3) ни один ключ никогда не скомпрометирован.

Для продуктов и систем, в которых потоковая передача является основополагающей, теперь вы можете, по крайней мере, найти компромисс, при котором можно на мгновение перейти от AEAD к обычному шифрованию AES, а затем вернуться к AEAD после проверки тега. Это открывает доступ к нескольким инновационным концепциям обеспечения безопасности вместо того, чтобы говорить: «Вы хотите буферизовать все это — вы с ума сошли? Мы не можем использовать шифрование!».

Все потому, что вы хотите реализовать только EncryptFinal , а не Encrypt и EncryptFinal (или их эквиваленты).

2. Не относится к GCM!

Теперь AES-GCM не какой-то волшебный зверь, который изобилует «упс-моментами». Это просто AES-CTR + GHASH (что-то вроде хеша, если можно). Соображения одноразового номера, связанные с конфиденциальностью, унаследованы от режима CTR, а соображения тегов, связанные с целостностью, исходят из переменных размеров тегов, разрешенных в спецификации. Тем не менее, AES-CTR + GHASH очень похож на что-то вроде AES-CBC + HMAC-SHA256 в том смысле, что первый алгоритм обеспечивает конфиденциальность, а второй — целостность. В AES-CBC + HMAC-SHA256 перестановка битов в зашифрованном тексте приведет к повреждению соответствующего блока в расшифрованном тексте (в отличие от CTR), а также детерминированному перевороту битов в следующем блоке расшифрованного открытого текста (например, CTR). Опять же, злоумышленник не будет знать, каким будет результирующий открытый текст — только то, что биты были перевернуты (например, CTR). Наконец, проверка целостности (HMAC-SHA256) обнаружит его. Но обрабатывается только последний байт (например, GHASH).

Поэтому, если ваш аргумент о сдерживании ВСЕХ расшифрованных данных до тех пор, пока целостность не будет в порядке, действительно хорош - его следует применять последовательно. Таким образом, ВСЕ данные, выходящие из пути AES-CBC, также должны буферизоваться (внутри библиотеки) до прохождения HMAC-SHA256. В основном это означает, что в .NET никакие потоковые данные не могут даже выиграть от достижений AEAD. .NET принудительно откатывает потоковые данные. Выбор между отсутствием шифрования или обычным шифрованием. Нет АЭАД. Там, где буферизация технически нецелесообразна, архитекторы должны, по крайней мере, иметь возможность предупреждать конечных пользователей о том, что «видеозаписи с дрона могут быть повреждены», а не о том, что «вас никто не видит».

3. Это лучшее, что у нас есть

Объем данных увеличивается, и безопасность должна быть усилена. Потоковая передача также является реальностью, которую должны принять дизайнеры. До тех пор, пока мир не создаст по-настоящему интегрированный алгоритм AEAD, который может изначально обнаруживать коррупцию и фальсификацию в середине потока, мы застряли с шифрованием + аутентификацией в качестве приятелей. Истинный примитив AEAD исследуется, но пока у нас есть только шифрование + аутентификация.

Меня меньше интересует «AES-GCM», чем быстрый, популярный алгоритм AEAD, который может поддерживать потоковые рабочие нагрузки — очень распространенный в мире, богатом данными, с гиперсвязью.

4. Используйте AES-CBC-HMAC, используйте (вставьте обходной путь)

Совет по криптографии рекомендует НЕ использовать GCM для первого сценария, а использовать CBC+HMAC.

Если оставить в стороне все упомянутое выше или даже специфику сценария, предложение AES-CBC-HMAC не является бесплатным. Это примерно в 3 раза медленнее, чем AES-GCM, поскольку шифрование AES-CBC не распараллеливается и поскольку GHASH можно ускорить с помощью инструкции PCLMULQDQ. Таким образом, если у вас 1 ГБ/сек с AES-GCM, теперь вы достигнете ~ 300 МБ/сек с AES-CBC-HMAC. Это снова приводит к мысли «Криптовалюта замедляет вас, пропустите ее», с которой специалисты по безопасности изо всех сил пытаются бороться.

шифрование каждого кадра 4k

видеокодеки должны вдруг делать шифрование? Или уровень шифрования теперь должен понимать видеокодеки? Это просто битовый поток на уровне безопасности данных. Тот факт, что это видео/геномные данные/изображения/проприетарные форматы и т. д., не должен беспокоить уровень безопасности. Общее решение не должно смешивать основные обязанности.

одноразовый номер

NIST допускает рандомизированные IV для длины, превышающей 96 бит. См. раздел 8.2.2 в NIST 800-38D . Здесь нет ничего нового, требования к nonce исходят из режима CTR. Что также довольно стандартно для большинства потоковых шифров . Я не понимаю внезапного страха перед одноразовыми номерами - это всегда было number used once . Тем не менее, несмотря на то, что дебаты о INonce создают неуклюжий интерфейс, по крайней мере, они не устраняют инновации, такие как навязывание «нет потока для вас». Я уступлю INonce любой день, если мы сможем получить инновации AEAD в области безопасности и потоковой передачи данных. Я ненавижу называть что-то простое, например, потоковое вещание, инновацией, но я боюсь, что это приведет к регрессу.

Я хотел бы оказаться неправым

Я просто парень, который после долгого рабочего дня отказался от вечера кино с моими детьми, чтобы напечатать это. Я устал и могу ошибаться. Но, по крайней мере, имейте открытый диалог сообщества, основанный на фактах, а не анекдоты или «комитетские причины» или какое-то другое вуду. Я занимаюсь продвижением безопасных инноваций .NET и Azure. Думаю, у нас совпали цели.

Кстати, о диалоге с сообществом...

Можем ли мы, пожалуйста, устроить звонок сообщества по скайпу? Выражение такой сложной темы превращается в гигантскую стену текста. Довольно, пожалуйста?

Пожалуйста, не звоните по скайпу — это и есть определение «встречи за закрытыми дверями», без каких-либо записей, доступных для сообщества. Проблемы Github — это правильный способ для всех сторон вести гражданский задокументированный дискурс (игнорируя прецеденты MS-comment-removal).

MS Crypto Review Board, вероятно, тоже звонил по Skype. Это не вина людей из MS, участвующих в этой теме - у них, вероятно, очень ограниченный доступ и сила убеждения над башнями из слоновой кости MS Crypto Review Board (что бы это ни было).

Что касается потоковой передачи AEAD:

Потоковое _шифрование_ возможно для режимов MAC-last, таких как GCM, CTR+HMAC, но невозможно для режимов MAC-first, таких как CCM. Потоковое _расшифрование_ размером в байт является фундаментальной утечкой и поэтому никем не рассматривается. Потоковое _шифрование_ размера блока также возможно для CBC+HMAC, но это ничего не меняет. Т.е. Подходы размера байта или размера блока к потоковой передаче AEAD ошибочны.

Потоковое _шифрование_ и _дешифрование_ размера фрагмента работает отлично, но у них есть 2 ограничения:

  • они требуют буферизации (за пределами размера блока). Это может быть сделано библиотекой/API, если буферизация контролируется/ограничена (например, Inferno) или оставлена ​​на верхний уровень (вызывающий уровень). В любом случае работает.

  • Разделенная потоковая передача AEAD не стандартизирована. Бывший. nacl-stream , Inferno, MS-собственная защита данных, сделай сам.

Это всего лишь краткое изложение того, что все участники этого обсуждения уже знают.

@sdrapkin , чтобы убедиться, что я правильно понял, у вас все в порядке с этим API, обеспечивающим потоковое шифрование, но не потоковое дешифрование?

@sdrapkin хорошо, мозговой штурм людей в режиме реального времени, безусловно, полезен, проблемы ведения учета могут быть решены с помощью протокола собрания. Вернемся к технической стороне: хотя разбиение на фрагменты работает для потокового дешифрования, это не низкоуровневый примитив безопасности. Это пользовательский протокол. И нестандартный, как вы заметили.

@morganbr

_Вы согласны с тем, что этот API обеспечивает потоковое шифрование, но не потоковую расшифровку?_

Нет. Если бы такой API был доступен, было бы легко создать шифротекст с потоковым шифрованием такого размера, который не сможет расшифровать никакое буферное дешифрование (из-за нехватки памяти).

^^^^ До сих пор не было особого согласия, но я думаю, что мы все можем согласиться с тем, что асимметричный API будет катастрофой. Как из «эй, это методы расшифровки потока, на которые я думал, что буду полагаться, потому что были методы шифрования», так и из-за комментариев @sdrapkin выше.

@Drawaes Согласен. Асимметричный API enc/dec был бы ужасным.

Любые обновления, ребята?

Видимо, я совместил несколько атак.

Врожденные недостатки потоковых шифров (которыми являются AES-CTR и AES-GCM) позволяют атакам с выбранным зашифрованным текстом обеспечить произвольное восстановление открытого текста. Защита от атак с выбранным зашифрованным текстом — это аутентификация; поэтому AES-GCM невосприимчив ... если вы не выполняете потоковую расшифровку и не можете определить из наблюдений по побочному каналу, каким был бы открытый текст. Например, если расшифрованные данные обрабатываются как XML, очень быстро произойдет сбой, если символы, отличные от пробела или <, находятся в начале расшифрованных данных. Таким образом, «потоковое дешифрование вновь вызывает проблемы с проектированием потокового шифра» (которых, как вы могли заметить, в .NET нет).

Пока ищу откуда взялось восстановление ключа есть бумажки типа Аутентификацияслабости в GCM (Ferguson/Microsoft) , но это восстановление ключа аутентификации на основе коротких размеров тегов (что является частью того, почему реализация Windows допускает только 96-битные теги). Вероятно, мне сообщили о других векторах восстановления ключа аутентификации, почему потоковая передача GCM опасна.

В более раннем комментарии @sdrapkin отметил: «Потоковое дешифрование размера байта в корне протекает и поэтому никем не рассматривается… Подходы размера байта или размера блока к потоковой передаче AEAD ошибочны». Это, в сочетании с тем, что CCM (и SIV) не может выполнять потоковое шифрование, и комментарием о том, что было бы странно иметь одну потоковую передачу, а не другую, предполагает, что мы вернулись к предложению просто использовать одноразовое шифрование и расшифровать.

Итак, похоже, мы вернулись к моему последнему предложению по API (https://github.com/dotnet/corefx/issues/23629#issuecomment-329202845). Если только нет других нерешенных вопросов, о которых мне удалось забыть во время перерыва.

С возвращением @bartonjs

Я засну ненадолго, но ненадолго:

  1. Ранее в этой ветке мы уже путали дизайн протокола с примитивным дизайном. Я просто скажу, что выбранные атаки зашифрованного текста - это проблема дизайна протокола, а не примитивная проблема.

  2. Потоковая расшифровка AEAD, по крайней мере, позволяет вам иметь конфиденциальность, а затем немедленно обновляется до конфиденциальности + подлинности в последнем байте. Без поддержки потоковой передачи в AEAD (то есть только традиционной конфиденциальности) вы постоянно ограничиваете людей более низкой гарантией конфиденциальности.

Если технические достоинства недостаточны или вы (правомерно) скептически относитесь к авторитетности моих аргументов, я попробую использовать внешний авторитет. Вы должны знать, что ваша фактическая базовая реализация поддерживает AEAD (включая AES GCM) в потоковом режиме. Основная ОС Windows ( bcrypt ) позволяет выполнять потоковую передачу GCM с помощью функций BCryptEncrypt или BCryptDecrypt . См. там dwFlags . Или пример пользовательского кода . Или созданная Microsoft оболочка CLR . Или что реализация была сертифицирована NIST FIP-140-2 совсем недавно, в начале этого года. Или что и Microsoft, и NIST потратили значительные ресурсы на реализацию AES и сертифицировали ее здесь и здесь . И, несмотря на все это, первобытных людей никто не обвинял. Для .NET Core нет никакого смысла внезапно появляться и навязывать свой собственный крипто-тезис, чтобы ослабить мощную базовую реализацию. Особенно, когда ОБА потоковая и одноразовая могут поддерживаться одновременно, очень тривиально.

Более? Что ж, сказанное выше верно для OpenSSL, даже с их «более новыми» API-интерфейсами evp.

И это верно для BouncyCastle.

И это правда с криптографической архитектурой Java.

ваше здоровье!
Сид

@sidshetye ++ 10, если криптосовет так обеспокоен, почему они позволяют Windows CNG делать это?

Если вы проверите проверку Microsoft NIST FIPS-140-2 AES (например, # 4064 ), вы заметите следующее:

AES-GCM:

  • Длина обычного текста: 0, 8, 1016, 1024
  • Длина AAD: 0, 8, 1016, 1024

АЕС-СКМ:

  • Длина обычного текста: 0-32
  • Длина AAD: 0-65536

Нет проверки для потоковой передачи. Я даже не уверен, проверяет ли NIST этот упр. Реализация AES-GCM не должна позволять шифровать открытый текст объемом более 64 ГБ (еще одно нелепое ограничение GCM).

Я не слишком привязан к потоковой передаче, так как мое использование не должно превышать 16 КБ, однако фрагментированные буферы были бы хороши и не должны представлять никакого риска (на самом деле я подозреваю, что cng сделал свой интерфейс таким, какой он есть, именно для этой цели) ... например, я хочу иметь возможность передать несколько диапазонов или что-то подобное (например, связанный список) и расшифровать его за один раз. Если он расшифровывает непрерывный буфер, все в порядке.

Так что я думаю, что перевод теневой крипто-платы на API «потокового стиля» пока невозможен, поэтому давайте двигаться вперед, создав одноразовый API. Всегда есть возможности для расширения API, ЕСЛИ позже достаточное количество людей выявит потребность

@sdrapkin дело в том, что это потоковый API, который прошел тщательную проверку NIST Labs и MSFT. Каждая проверяемая сборка стоит от 80 000 до 50 000 долларов, и MSFT (а также OpenSSL, Oracle и другие крипто-тяжеловесы) вложили БОЛЬШИЕ средства в проверку этих API и реализаций на протяжении более 10 лет. Давайте не будем отвлекаться на конкретные размеры простого текста в плане тестирования, потому что я уверен, что .NET будет поддерживать размеры, отличные от 0, 8, 1016, 1024, независимо от потоковой передачи или однократной обработки. Дело в том, что все эти проверенные в бою API (буквально; в системах поддержки оружия), на всех этих платформах поддерживают потоковую передачу AEAD на уровне крипто-примитивного API. К сожалению, до сих пор каждый аргумент против этого был проблемой на уровне приложения или протокола, которую называли псевдопроблемой на уровне крипто-примитивов.

Я полностью за «пусть победит лучшая идея», но если команда .net core crypto (MSFT или сообщество) не сделает какое-то новаторское открытие, я просто не понимаю, почему все, кто занимается криптографией до сих пор, из всех разных организаций ошибаются. и они правы.

PS: я знаю, что у нас здесь разногласия, но мы все хотим лучшего для платформы и ее клиентов.

@Drawaes, если только интерфейс AEAD (не обязательно реализация), определяемый сегодня, не поддерживает поверхность потокового API, я не понимаю, как люди могут расширить его, не имея двух интерфейсов или пользовательских интерфейсов. Это было бы катастрофой. Я надеюсь, что это обсуждение приведет к интерфейсу, ориентированному на будущее (или, по крайней мере, отражающему другие интерфейсы AEAD, которые существуют уже много лет!).

Я склонен согласиться. Но эта проблема никуда не денется быстро, и когда это произойдет, мы, вероятно, столкнемся с критической точкой, либо она не будет реализована в версии 2.1, либо ее придется решать без остатка времени, чтобы сгладить проблемы. Я буду честен, я вернулся к своим старым оберткам и просто обновляю их для 2.0;)

У нас есть несколько эталонных API для Java , OpenSSL или C# Bouncy Castle или CLR Security . Честно говоря, подойдет любой из них, и в долгосрочной перспективе я бы хотел, чтобы в C# было что-то вроде «Архитектуры криптографии Java» в Java, где все криптографические реализации противоречат хорошо зарекомендовавшему себя интерфейсу, позволяющему менять криптографические библиотеки, не влияя на пользовательский код.

Вернувшись сюда, я думаю, что лучше всего расширить интерфейс .NET Core ICryptoTransform как

public interface IAuthenticatedCryptoTransform : ICryptoTransform 
{
    bool CanChainBlocks { get; }
    byte[] GetTag();
    void SetExpectedTag(byte[] tag);
}

Если мы Span идентифицируем все byte[] s, это должно пронизывать весь API в пространстве имен System.Security.Cryptography для общей согласованности.

Изменить: Исправлены ссылки JCA

Если мы разделяем все byte[], это должно пронизывать весь API в пространстве имен System.Security.Cryptography для общей согласованности.

Мы это уже сделали. Все, кроме ICryptoTransform, потому что мы не можем менять интерфейсы.

Я думаю, что лучше всего расширить ICryptoTransform .NET Core...

Проблема с этим заключается в том, что шаблон вызова очень неудобен с получением тега в конце (особенно если задействован CryptoStream). Я написал это изначально, и это было уродливо. Также существует проблема, как получить один из них, поскольку параметры GCM отличаются от параметров CBC/ECB.

Итак, вот мои мысли.

  • Потоковое дешифрование опасно для AE.
  • В общем, я сторонник "дайте мне примитив, и пусть я управляю своим риском"
  • Я также поклонник «.NET не должен (легко) допускать совершенно небезопасные вещи, потому что это часть его ценностного предложения»
  • Если бы, как я изначально неправильно понял, риски плохого расшифровки GCM заключались в восстановлении входного ключа, тогда я бы все еще был в «это слишком небезопасно». (Разница между .NET и всем остальным будет заключаться в том, что «поскольку на это ушло больше времени, мир узнал больше»)
  • Но, поскольку это не так, если вы действительно хотите, чтобы тренировочные колеса оторвались, то, думаю, я приму это во внимание.

Мои довольно сырые мысли на этот счет (добавляя к существующим предложениям, так что один выстрел остается, хотя я предполагаю, что это виртуальная реализация по умолчанию вместо абстрактной):

```С#
частичный класс AuthenticatedEncryptor
{
// бросает, если операция уже выполняется
public abstract void Initialize (ReadOnlySpanсвязанные данные);
// true в случае успеха, false в случае "назначение слишком маленькое", исключение в любом другом случае.
общественное абстрактное логическое значение TryEncrypt (ReadOnlySpanданные, диапазонзашифрованные данные, исходящие байты прочитаны, исходящие байты записаны);
// false, если оставшиеся EncryptedData слишком малы, сбрасывается, если другие входные данные слишком малы, см. свойства NonceOrIVSizeInBits и TagSizeInBits.
// NonceOrIvUsed может перейти в Initialize, но тогда это может быть интерпретировано как ввод.
общественное абстрактное логическое значение TryFinish (ReadOnlySpanоставшиеся данные, диапазоноставшиеся зашифрованные данные, вне int bytesWritten, Spanтег, диапазонбесцеоривусед);
}

частичный класс AuthenticatedDecryptor
{
// бросает, если операция уже выполняется
public abstract void Initialize (ReadOnlySpanтег, ReadOnlySpannonceOrIv, ReadOnlySpanсвязанные данные);
// true в случае успеха, false в случае "назначение слишком маленькое", исключение в любом другом случае.
общественное абстрактное логическое значение TryDecrypt (ReadOnlySpanданные, диапазонdecryptedData, out int bytesRead, out int bytesWritten);
// выбрасывает неверный тег, но все равно может привести к утечке данных.
// (remainingDecryptedData требуется для CBC+HMAC, так что можно добавить и оставшиеся данные, я думаю?)
общественное абстрактное логическое значение TryFinish (ReadOnlySpanоставшиеся данные, диапазоноставшиеся DecryptedData, out int bytesWritten);
}
```

AssociatedData поступает в Initialize, потому что алгоритмы, которым они нужны последними, могут удерживать их, а алгоритмы, которым они нужны первыми, не могут получить их каким-либо другим способом.

Как только будет принято решение о том, как будет выглядеть потоковая передача (и думают ли люди, что CCM должен выполнять внутреннюю буферизацию или должен выбрасывать в режиме потокового шифрования), я вернусь к доске.

@bartonjs Я знаю, что вы имеете в виду под извлечением и программированием тега в конце потока для симметрии при шифровании/дешифровании. Это сложно, но еще хуже, если решение остается за каждым пользователем. У меня есть реализация, которой я могу поделиться в Массачусетском технологическом институте; нужно будет посмотреть вместе с моей командой (не за моим рабочим столом/мобильным телефоном)

Золотая середина может быть похожа на OpenSSL или bcrypt в NT, где вам нужно подключить тег прямо перед окончательным вызовом расшифровки, поскольку именно тогда происходит сравнение тегов. т.е. SetExpectedTag (до окончательного расшифрования) и GetTag (после окончательного шифрования) будут работать, но перекладывают управление тегами на пользователя. Большинство просто добавит тег к шифропотоку, поскольку это естественный временной порядок.

Я действительно думаю, что ожидание тега в самом Initialize (при расшифровке) нарушает симметрию в пространстве (поток байтов) и во времени (проверка тега в конце, а не в начале), что ограничивает его полезность. Но вышеупомянутые API-интерфейсы тегов решают эту проблему.

Также для шифрования Initialize требуется IV перед любыми криптопреобразованиями.

Наконец, для шифрования и дешифрования Initialize требуются ключи шифрования AES перед любыми преобразованиями. (Я упустил что-то очевидное, или вы забыли ввести этот бит?)

Я действительно думаю, что ожидание тега в Initialize (при расшифровке) нарушает симметрию

В CBC+HMAC обычной рекомендацией является проверка HMAC перед началом любого дешифрования, так что это алгоритм дешифрования с первым тегом. Точно так же может быть «чистый алгоритм AE», который выполняет деструктивные операции над тегом во время вычислений и просто проверяет, что окончательный ответ равен 0. Таким образом, как и связанное значение данных, поскольку могут быть алгоритмы, которым это нужно в первую очередь, он стать первым в полностью обобщенном API.

Вывод их в SetAssociatedData и SetTag приводит к проблеме, заключающейся в том, что хотя базовый класс не зависит от алгоритма, его использование становится зависимым от алгоритма. Изменение AesGcm на AesCbcHmacSha256 или SomeTagDesctructiveAlgorithm теперь приведет к броску TryDecrypt, поскольку тег еще не предоставлен. Для меня это хуже, чем полное отсутствие полиморфности, поэтому предоставление гибкости предполагает разбиение модели на части для полной изоляции по алгоритму. (Да, им можно было бы управлять с помощью дополнительных свойств идентификации алгоритма, таких как NeedsTagFirst , но на самом деле это просто приводит к тому, что его сложнее использовать)

Кроме того, для шифрования Initialize требуется IV перед любыми криптопреобразованиями.

Наконец, для шифрования и дешифрования Initialize нужны ключи шифрования AES перед любыми преобразованиями.

Ключ был параметром ctor класса. IV/nonce поступает от поставщика IV/nonce в параметре ctor.

Модель провайдера решает SIV, где во время шифрования IV не задается, он создается от имени данных. В противном случае SIV имеет параметр и требует, чтобы было предоставлено пустое значение.

или вы забыли ввести этот бит?

Методы потоковой передачи добавлялись к моему существующему предложению, в котором уже были ключ и провайдер IV/nonce в качестве параметров ctor.

@bartonjs : Хорошо, что некоторые алгоритмы могут хотеть пометить тег сначала, а другие - в конце, и спасибо за напоминание о том, что это дополнение к исходной спецификации. Я обнаружил, что рассмотрение варианта использования упрощает задачу, поэтому вот пример с облачными технологиями:

Мы собираемся выполнить анализ одного или нескольких зашифрованных файлов AES-GCM объемом 10 ГБ (т. е. тегов после зашифрованного текста), хранящихся в хранилище. Работник аналитики одновременно расшифровывает несколько входящих потоков в отдельные машины/кластеры и после проверки последних байтов и тегов запускает каждую рабочую нагрузку анализа. Все виртуальные машины хранения, рабочие и аналитические виртуальные машины находятся в Azure US-West.

Здесь нет способа получить тег в конце каждого потока и предоставить его методу Initialize AuthenticatedDecryptor. Таким образом, даже если пользователь добровольно изменит код для использования GCM, он даже не сможет начать использовать API.

Если подумать, единственный способ, которым мы могли бы иметь API, который поддерживает различные AEAD и не имеет изменений пользовательского кода, — это если поставщики криптографии для различных алгоритмов AEAD автоматически обрабатывают теги. Java делает это, помещая теги в конец зашифрованного текста для GCM и извлекая их во время расшифровки без вмешательства пользователя. Помимо этого, каждый раз, когда кто-то существенно меняет алгоритм (например, CBC-HMAC => GCM), ему придется модифицировать свой код из-за взаимоисключающего характера обработки тегов первым и тегом последним.

ИМХО, надо сначала решить

Вариант 1) Поставщики алгоритмов внутренне обрабатывают управление тегами (например, Java)

или

Вариант 2) Предоставьте достаточно API, чтобы пользователи могли сделать это сами (например, WinNT bcrypt или openssl)

Вариант 1 действительно упростил бы общее взаимодействие с пользователями библиотек, поскольку управление буфером может стать сложным. Решите ее хорошо в библиотеке, и теперь каждому пользователю не придется решать ее каждый раз. Кроме того, все AEAD имеют одинаковый интерфейс (сначала теги, в конце теги, без тегов), а замена алгоритмов также упрощается.

Мой голос будет за вариант 1.

Наконец, мы смогли раскопать нашу реализацию, позволяющую ICryptoTransform потоковым операциям через GCM автоматически извлекать тег в потоке источника . Это было значительное обновление собственной оболочки CLR Security, и, несмотря на дополнительные копии буфера, оно по-прежнему очень быстрое (~ 4 ГБ/с на нашем тестовом macbook pro в буткемпе Windows 10). В основном мы использовали CLR Security, чтобы создать вариант 1 для себя, поэтому нам не нужно делать это где-либо еще. Это изображение действительно помогает объяснить, что происходит внутри TransformBlock и TransformFinalBlock ICryptoTransform .

@sidshetye Я не понимаю, почему ваш облачный пример заблокирован. Если вы читаете из хранилища, вы можете сначала загрузить последние несколько байтов тега и предоставить их дешифратору. При использовании API хранилища Azure это можно сделать с помощью CloudBlockBlob.DownloadRangeXxx .

@GrabYourPitchforks Не хочу слишком отвлекаться на этот пример, но это особая возможность хранилища BLOB-объектов Azure. Как правило, хранилище на основе виртуальных машин (IaaS) или рабочие нагрузки, отличные от хранилища Azure, обычно получают сетевой поток, который не доступен для поиска.

Лично я очень рад видеть @GrabYourPitchforks — ура!

Мы собираемся выполнить анализ одного или нескольких зашифрованных файлов AES-GCM объемом 10 ГБ (т. е. тегов после зашифрованного текста), хранящихся в хранилище. Работник аналитики одновременно расшифровывает несколько входящих потоков в отдельные машины/кластеры и после проверки последних байтов и тегов запускает каждую рабочую нагрузку анализа. Все виртуальные машины хранения, рабочие и аналитические виртуальные машины находятся в Azure US-West.

@sidshetye , ты был так непреклонен в том, чтобы разделять тупые и опасные примитивы и умные и привлекательные протоколы! Мне приснился сон - и я в него поверил. А потом вы бросаете это на нас. Это протокол - дизайн системы. Кто бы ни разработал тот протокол, который вы описали, он напортачил. Нет смысла сейчас плакать из-за невозможности вставить квадратный колышек в круглое отверстие.

Кто бы ни зашифровал 10-гигабитные GCM-файлы, он не только живет в опасной близости от примитивного края (GCM никуда не годится после 64-гигабитных), но также было неявное утверждение, что весь зашифрованный текст нужно будет буферизовать.

Тот, кто GCM-шифрует 10-гигабайтные файлы, с огромной вероятностью совершает ошибку в протоколе. Решение: фрагментированное шифрование. TLS имеет разбиение на фрагменты переменной длины с ограничением в 16 КБ, а также есть другие, более простые разновидности, не использующие PKI. Сексуальная привлекательность этого гипотетического примера, «сначала облачная», не умаляет ошибок дизайна.

(Мне нужно многое наверстать в этой теме.)

@sdrapkin поднял вопрос о повторном использовании интерфейса IAuthenticatedEncryptor из уровня защиты данных. Честно говоря, я не думаю, что это правильная абстракция для примитива, поскольку уровень защиты данных довольно самоуверен в том, как он выполняет криптографию. Например, он запрещает самостоятельный выбор IV или одноразового номера, требует, чтобы соответствующая реализация понимала концепцию AAD, и дает результат, который является в некоторой степени проприетарным. В случае AES-GCM возвращаемое значение из IAuthenticatedEncryptor.Encrypt представляет собой конкатенацию странного почти одноразового значения, используемого для получения подключа, зашифрованного текста, полученного в результате запуска AES-GCM по предоставленному открытому тексту (но не AAD!), и тег AES-GCM. Таким образом, хотя каждый шаг, связанный с созданием защищенной полезной нагрузки, является безопасным, сама полезная нагрузка не следует каким-либо принятым соглашениям, и вы не найдете никого, кроме библиотеки защиты данных, который может успешно расшифровать полученный зашифрованный текст. Это делает его хорошим кандидатом на роль библиотеки для разработчиков приложений, но ужасным кандидатом на роль интерфейса, реализуемого примитивами.

Я также должен сказать, что не вижу большой ценности в наличии единого истинного интерфейса(tm) IAuthenticatedEncryptionAlgorithm , который должны реализовать все аутентифицированные алгоритмы шифрования. Эти примитивы являются «сложными», в отличие от простых примитивов блочного шифра или примитивов хеширования. В этих сложных примитивах просто слишком много переменных. Только примитивный AE или это AEAD? Алгоритм вообще принимает IV/nonce? (Я видел некоторые, которые этого не делают.) Есть ли какие-либо опасения по поводу того, как должны быть структурированы ввод IV/одноразовый номер или данные? IMO, сложные примитивы должны быть просто автономными API, а библиотеки более высокого уровня будут поддерживать конкретные сложные примитивы, о которых они заботятся. Затем библиотека более высокого уровня предоставляет любой унифицированный API, который, по ее мнению, подходит для ее сценариев.

@sdrapkin Мы снова уходим от темы. Скажу лишь, что система строится на примитивах. Крипто-примитивы здесь голые и мощные. В то время как уровень системы/протокола обрабатывал буферизацию; это также на уровне кластера, конечно, не в основной системной памяти, которую заставят однократные примитивы. Граница «фрагментирования» равна X (здесь X = 10 ГБ), потому что < 64 ГБ, потому что буферная емкость кластера была почти безграничной, и ничего не могло / не могло начаться, пока в кластер не будет загружен последний байт. Это именно то разделение задач, оптимизация каждого слоя с учетом его сильных сторон, о которых я говорил. И это может произойти только в том случае, если базовые примитивы не препятствуют дизайну/ограничениям более высокого уровня (обратите внимание, что более реальные приложения имеют свои собственные унаследованные недостатки).

NIST 800-38d sec9.1 гласит:

Чтобы помешать неуполномоченной стороне контролировать или влиять на создание IV,
GCM должен быть реализован только в составе криптографического модуля, отвечающего требованиям
Паб ФИПС. 140-2. В частности, криптографическая граница модуля должна содержать
«агрегат генерации», производящий ИВ по одной из конструкций гл. 8.2 выше.
Документация модуля для его проверки на соответствие требованиям FIPS 140-2 должна
опишите, как модуль соответствует требованию уникальности IV.

Для меня это означает, что GCM IV должны автоматически генерироваться внутри (а не передаваться извне).

@sdrapkin Хорошая мысль, но если вы прочитаете еще внимательнее, вы увидите, что для IV длины 96 бит и выше раздел 8.2.2 позволяет генерировать IV с помощью генератора случайных битов (RBG), где не менее 96 бит случайны (вы может просто 0 других битов). Я упоминал об этом в прошлом месяце в самой этой теме ( здесь под одноразовым номером).

LT;DR: INonce — это ловушка, ведущая к несоблюдению рекомендаций NIST и FIPS.

В разделе 9.1 просто говорится, что для FIPS 140-2 блок генерации IV (полностью случайный, т. е. раздел 8.2.2, или детерминированная реализация, т. е. раздел 8.2.1) должен находиться в пределах границ модуля, проходящего проверку FIPS. С ...

  1. RBG уже прошли проверку FIPS
  2. Рекомендуется линза IV >= 96
  3. разработать блок поколения IV, который сохраняет перезагрузку, бессрочную потерю мощности на крипто-примитивном уровне, сложно
  4. получить 3 выше, реализованных в криптобиблиотеке, И получить его сертификацию сложно и дорого (50 000 долларов за все, что приводит к неточному побитовому образу сборки)
  5. Ни один пользовательский код никогда не реализует 3 и не получит сертификат из-за 4 выше. (давайте оставим в стороне некоторые экзотические военные/государственные объекты).

... большинство криптографических библиотек (см. Oracle Java, WinNT bcryptprimitives, OpenSSL и т. д.), проходящих сертификацию FIPS, используют маршрут RBG для IV и просто берут массив байтов для ввода. Обратите внимание, что наличие интерфейса INonce на самом деле является ловушкой с точки зрения NIST и FIPS, поскольку неявно предполагает, что пользователь должен передать реализацию этого интерфейса функции шифрования. Но любая пользовательская реализация INonce почти гарантированно НЕ прошла процесс сертификации NIST за 9 месяцев + и $50K+. Тем не менее, если бы они только что отправили массив байтов с использованием конструкции RGB (уже в криптобиблиотеке), они бы полностью соответствовали рекомендациям.

Я уже говорил ранее, что эти существующие криптобиблиотеки расширили свою поверхность API и прошли боевые испытания в нескольких сценариях. Больше того, что мы затронули в этой длинной ветке. Я снова голосую за то, чтобы использовать эти знания и опыт во всех этих библиотеках, во всех этих проверках и во всех этих установках, а не пытаться изобретать велосипед. Не изобретайте велосипед. Используйте его, чтобы изобрести ракету :)

Привет народ,

Есть новости по этому поводу? Не видел никаких обновлений в ветке криптографической дорожной карты @karelz или в ветке AES GCM .

Спасибо
Сид

Итак, последнее конкретное предложение взято с https://github.com/dotnet/corefx/issues/23629#issuecomment -334328439:

partial class AuthenticatedEncryptor
{
    // throws if an operation is already in progress
    public abstract void Initialize(ReadOnlySpan<byte> associatedData);
    // true on success, false on “destination too small”, exception on anything else.
    public abstract bool TryEncrypt(ReadOnlySpan<byte> data, Span<byte> encryptedData, out int bytesRead, out int bytesWritten);
    // false if remainingEncryptedData is too small, throws if other inputs are too small, see NonceOrIVSizeInBits and TagSizeInBits properties.
    // NonceOrIvUsed could move to Initialize, but then it might be interpreted as an input.
    public abstract bool TryFinish(ReadOnlySpan<byte> remainingData, Span<byte> remainingEncryptedData, out int bytesWritten, Span<byte> tag, Span<byte> nonceOrIvUsed);
}

partial class AuthenticatedDecryptor 
{
    // throws if an operation is already in progress
    public abstract void Initialize(ReadOnlySpan<byte> tag, ReadOnlySpan<byte> nonceOrIv, ReadOnlySpan<byte> associatedData);
    // true on success, false on “destination too small”, exception on anything else.
    public abstract bool TryDecrypt(ReadOnlySpan<byte> data, Span<byte> decryptedData, out int bytesRead, out int bytesWritten);
    // throws on bad tag, but might leak the data anyways.
    // (remainingDecryptedData is required for CBC+HMAC, and so may as well add remainingData, I guess?)
    public abstract bool TryFinish(ReadOnlySpan<byte> remainingData, Span<byte> remainingDecryptedData, out int bytesWritten);
}

С тех пор было поднято лишь несколько потенциальных проблем:

  • Тег требуется заранее, что мешает некоторым сценариям. Либо API должен стать значительно более сложным для обеспечения большей гибкости, либо эту проблему следует рассматривать как проблему протокола (т.е. проблему высокого уровня).
  • INonceProvider может быть излишне сложным и/или привести к несоответствию рекомендациям NIST и FIPS.
  • Предполагаемая абстракция аутентифицированных примитивов шифрования может быть несбыточной мечтой, поскольку различия могут быть слишком велики. Дальнейшее обсуждение этого предложения не проводилось.

Я хотел бы предложить следующее:

  1. Дополнительная сложность, заключающаяся в том, что тег не требуется заранее, кажется серьезной, соответствующий сценарий проблемы кажется необычным, и проблема действительно очень похожа на вопрос протокола. Хороший дизайн может вместить многое, но не все. Лично я чувствую себя комфортно, оставив это протоколу. (Приветствуются сильные контрпримеры.)
  2. Обсуждение последовательно двигалось в сторону гибкой низкоуровневой реализации, не защищающей от неправильного использования, за исключением IV поколения . Давайте будем последовательны. Общее мнение, похоже, заключается в том, что высокоуровневый API является важным следующим шагом, жизненно важным для правильного использования большинством разработчиков — именно так мы избегаем защиты от неправильного использования в низкоуровневом API. Но кажется, что дополнительная доза страха поддержала идею предотвращения неправильного использования _в области IV поколения_. В контексте низкоуровневого API и, чтобы быть последовательным, я склоняюсь к эквиваленту byte[] . Но замена реализации более плавна с введенным INonceProvider . Является ли комментарий @sidshetye неопровержимым, или может ли простая имплантация INonceProvider , которая просто вызывает RNG, по-прежнему считаться совместимой?
  3. Абстракции кажутся полезными, и столько усилий было вложено в их разработку, что теперь я убежден, что они принесут больше пользы, чем вреда. Кроме того, высокоуровневые API по-прежнему могут реализовывать низкоуровневые API, которые не соответствуют низкоуровневым абстракциям.
  4. IV — это общий термин, а одноразовый номер — это конкретный вид IV, верно? Это требует переименования с INonceProvider на IIVProvider и с nonceOrIv* на iv* . В конце концов, мы всегда имеем дело с IV, но не обязательно с одноразовым номером.

Предварительный тег не подходит для моего сценария, поэтому я, вероятно, просто оставлю свою собственную реализацию. Что хорошо, я не уверен, что всем нравится писать высокопроизводительный код в этой области.

Проблема в том, что это вызовет ненужную задержку. Вы должны предварительно буферизовать все сообщение, чтобы получить тег в конце, чтобы начать декодирование кадра. Это означает, что вы в принципе не можете перекрывать ввод-вывод и расшифровку.

Я не уверен, почему так трудно позволить это в конце. Но я не собираюсь блокировать этот API, он просто не будет представлять никакого интереса в моем сценарии.

IV — это общий термин, а одноразовый номер — это конкретный вид IV, верно?

Нет. nonce — это число, используемое один раз . Алгоритм, который указывает одноразовый номер, указывает, что повторное использование нарушает гарантии алгоритма. В случае GCM использование одного и того же одноразового номера с тем же ключом и другим сообщением может привести к компрометации ключа GHASH, что приведет к снижению GCM до CTR.

Из http://nvlpubs.nist.gov/nistpubs/ir/2013/NIST.IR.7298r2.pdf :

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

У «IV» нет таких строгих требований. Например, повторение IV с CBC приводит к утечке только в том случае, если зашифрованное сообщение такое же, как и предыдущее сообщение с тем же IV, или отличается от него. Это не ослабляет алгоритм.

Nonce — это число, используемое один раз.
У «IV» нет таких строгих требований.

@bartonjs Да. Я бы сказал, что, поскольку одноразовый номер используется для инициализации крипто-примитива, это его вектор инициализации. Он идеально соответствует любому определению IV, которое я могу найти. Да, у него более строгие требования, точно так же, как у коровы более строгие требования, чем у животного. Текущая формулировка, кажется, требует параметра "cowOrAnimal". Тот факт, что разные режимы предъявляют разные требования к IV, не меняет того факта, что все они требуют той или иной формы IV. Если я что-то упускаю, обязательно сохраните текущую формулировку, но, насколько я могу судить, просто "iv" или "IIVProvider" и просты, и правильны.

Чтобы побаловать себя nonceOrIv велопрогулкой:

96-битный GCM IV иногда определяется как 4-байтовый salt и 8-байтовый nonce (например, RFC 5288). RFC 4106 определяет GCM nonce как 4-байтовый salt и 8-байтовый iv . RFC 5084 (GCM в CMS) говорит, что CCM принимает nonce , GCM принимает iv , но _"... чтобы иметь общий набор терминов для AES-CCM и AES-GCM. , в остальной части этого документа AES-GCM IV упоминается как одноразовый номер»._ RFC 5647 (GCM для SSH) говорит _ «примечание: в [RFC5116] IV называется одноразовым номером»._ RFC 4543 ( GCM в IPSec) говорится: «мы ссылаемся на ввод AES-GMAC IV как на одноразовый номер, чтобы отличить его от полей IV в пакетах»._ RFC 7714 (GCM для SRTP) говорит о 12-байтовом IV и почти сохраняет свою согласованность, но затем говорит: «_минимальная и максимальная длина одноразового номера (IV): ДОЛЖНО быть 12 октетов»._

Учитывая полное отсутствие согласованности в большинстве спецификаций GCM, nonceOrIv имеет смысл. 0,02 доллара США

Тег заранее не является стартовым

Как и другие клиенты, озвучивающие себя здесь, требование предварительного тега для нас тоже не является стартовым. .NET никак не может обрабатывать параллельные потоки с этим искусственно введенным ограничением. Полностью убивает масштабируемость.

Можете ли вы подтвердить утверждение, что это добавляет сложности? Потому что на самом деле это должно быть тривиально. Кроме того, ни одна из криптографических реализаций для конкретных платформ (которые вы будете обертывать) не имеет этого ограничения. В частности, причина в том, что входной тег должен просто сравниваться с вычисляемым тегом в постоянное время. И вычисленный тег доступен только после расшифровки финального блока в течение TryFinish . По сути, когда вы начнете свою реализацию, вы обнаружите, что просто храните tag внутри своего экземпляра до TryFinish . Вы могли бы очень хорошо иметь его в качестве дополнительного ввода

public abstract bool TryFinish(ReadOnlySpan<byte> remainingData, 
                             Span<byte> remainingDecryptedData, 
                             out int bytesWritten, 
                             ReadOnlySpan<byte> tag = null); // <==

Я также думаю, что мы слишком усердно пытаемся нормализовать единый интерфейс, который будет охватывать все криптографические сценарии. Я тоже предпочитаю обобщенные интерфейсы, но никогда не в ущерб функциональности или масштабируемости — особенно на таком фундаментальном уровне, как стандартная крито-библиотека самого языка. ИМХО, если кто-то это делает, это обычно означает, что абстракция ошибочна.

Если нужен простой непротиворечивый интерфейс, я предпочитаю подход Javaтакже упоминавшийся ранее здесь как вариант 1 . Это также обходит вышеупомянутую проблему тега первым / тегом последним, сохраняя их в реализациях алгоритма (ИМХО, как я думаю, так и должно быть). Моя команда не реализует это, так что это не наше решение, НО если бы нам нужно было принять решение и начать реализацию - мы бы точно пошли по этому пути.

Пожалуйста, избегайте интерфейса INonce , простого интерфейса byte[] или span<> должно быть достаточно для совместимого низкоуровневого интерфейса.

IV vs Nonce - Обобщенный случай действительно IV. Для GCM IV должен быть одноразовым номером (например, Car vs RedOrCar). И когда я копирую это, я только что заметил, что @timovzl использовал очень похожий пример :)

@sidshetye Можете ли вы сделать точное предложение о том, что оба (1) поддерживают алгоритмы, которым тег нужен заранее, и (2) тег требуется только до TryFinish во всех других ситуациях?

Я полагаю, вы думаете что-то в следующем направлении?

  • Тег в Initialize может быть нулевым. Только те алгоритмы, которые нуждаются в этом заранее, будут выдавать null.
  • Тег в TryFinish является обязательным или (в качестве альтернативы) может быть пустым для тех алгоритмов, которые уже потребовали его заранее.

Я предполагаю, что вышеизложенное только добавляет сложности в виде документации и ноу-хау. Для низкоуровневого API это можно считать небольшой жертвой, поскольку в любом случае требуется адекватная документация и ноу-хау.

Я начинаю убеждаться, что это должно быть возможно для совместимости с другими реализациями и потоковой передачей.

@timovzl Конечно, я надеюсь выделить на это время завтра.

@Timovzl , у меня было время только сегодня, и это оказалось настоящей кроличьей норой! Это длинно, но я думаю, что оно охватывает большинство вариантов использования, фиксирует сильные стороны криптографии .NET ( ICryptoTransform ), охватывая направление .NET Core/Standard ( Span<> ). Я перечитал, но надеюсь, что ниже нет опечаток. Я также думаю, что некоторое общение в реальном времени (чат, телефонная конференция и т. д.) жизненно важно для быстрого мозгового штурма; Я надеюсь, что вы можете рассмотреть это.

Модель программирования

Сначала я расскажу о получившейся модели программирования для пользователей API.

Потоковое шифрование

var aesGcm = new AesGcm();
using (var encryptor = aesGcm.CreateAuthenticatedEncryptor(Key, IV, AAD))
{
  using (var cryptoOutStream = new CryptoStream(cipherOutStream, encryptor, CryptoStreamMode.Write))
  {
    clearInStream.CopyTo(cryptoOutStream);
  }
}

Потоковая расшифровка

var aesGcm = new AesGcm();
using (var decryptor = aesGcm.CreateAuthenticatedDecryptor(Key, IV, AAD))
{
  using (var decryptStream = new CryptoStream(cipherInStream, encryptor, CryptoStreamMode.Write))
  {
    decryptStream.CopyTo(clearOutStream);
  }
}

Не потоковое

Поскольку непотоковая передача — это особый случай потоковой передачи, мы можем обернуть приведенный выше пользовательский код во вспомогательные методы на AuthenticatedSymmetricAlgorithm (определенные ниже), чтобы предоставить более простой API. то есть

public class AesGcm : AuthenticatedSymmetricAlgorithm
{
  ...
  // These return only after consuming entire input buffer

  // Code like Streaming Encrypt from above within here
​  public abstract bool TryEncrypt(ReadOnlySpan<byte> clearData, Span<byte> encryptedData);

  // Code like Streaming Decrypt from above within here
  public abstract bool TryDecrypt(ReadOnlySpan<byte> encryptedData, Span<byte> clearData); 
  ...
}

Это может удвоиться как представление более простого API, такого как

Непотоковое шифрование

var aesGcm = new AesGcm(Key, IV, AAD);
aesGcm.TryEncrypt(clearData, encryptedData);
var tag = aesGcm.Tag;

Расшифровка без потоковой передачи

var aesGcm = new AesGcm(Key, IV, AAD);
aesGcm.Tag = tag;
aesGcm.TryDecrypt(encryptedData, clearData);

Под капотом

Глядя на исходный код corefx, Span<> есть везде. Это включает в себя System.Security.Cryptography.* — за исключением симметричных шифров, так что давайте исправим это первое и наложим аутентифицированное шифрование поверх.

1. Создайте ICipherTransform для Span I/O

Это похоже на версию ICryptoTransform с поддержкой Span . Я бы просто изменил сам интерфейс как часть обновления фреймворка, но, поскольку люди могут обидеться на это, я называю его ICipherTransform .

public partial interface ICipherTransform : System.IDisposable
{
  bool CanReuseTransform { get; }
  bool CanTransformMultipleBlocks { get; } // multiple blocks in single call?
  bool CanChainBlocks { get; }             // multiple blocks across Transform/TransformFinal
  int InputBlockSize { get; }
  int OutputBlockSize { get; }
  int TransformBlock(ReadOnlySpan<byte> inputBuffer, int inputOffset, int inputCount, Span<byte> outputBuffer, int outputOffset);
  Span<byte> TransformFinalBlock(ReadOnlySpan<byte> inputBuffer, int inputOffset, int inputCount);
}

Также отметьте ICryptoTransform как [Obsolete]

Быть вежливым с людьми, уже знакомыми с криптографией .NET.

[Obsolete("See ICipherTransform")]
public partial interface ICryptoTransform : System.IDisposable { ... }

2. Расширьте существующий класс SymmetricAlgorithm для Span I/O.

public abstract class SymmetricAlgorithm : IDisposable
{
  ...
  public abstract ICipherTransform CreateDecryptor(ReadOnlySpan<byte> Key, ReadOnlySpan<byte> IV);
  public abstract ICipherTransform CreateEncryptor(ReadOnlySpan<byte> Key, ReadOnlySpan<byte> IV);
  public virtual ReadOnlySpan<byte> KeySpan {...}
  public virtual ReadOnlySpan<byte> IVSpan {...}
  public virtual ReadOnlySpan<byte> KeySpan {...}
  ...
}

3. Расширьте существующие CryptoStream для Span I/O.

Это похоже Stream в System.Runtime. Кроме того, мы добавим c'tor для нашего случая AEAD.

ВАЖНО: CryptoStream потребуется обязательное обновление в FlushFinalBlock , чтобы добавить тег в конец потока во время шифрования и автоматически извлечь тег (TagSize bytes) во время дешифрования . Это похоже на другие протестированные API, такие как криптографическая архитектура Java или C # BouncyCastle. Это неизбежно, но это лучшее место для этого, поскольку при потоковой передаче тег создается в конце, но не требуется до тех пор, пока финальный блок не будет преобразован во время дешифрования. Плюс в том, что это значительно упрощает модель программирования.

Примечание. 1) При использовании CBC-HMAC вы можете сначала проверить тег. Это более безопасный вариант, но если это так, он фактически делает его двухпроходным алгоритмом. 1-й проход вычисляет тег HMAC, а 2-й проход фактически выполняет расшифровку. Таким образом, поток памяти или сетевой поток всегда должен буферизоваться в памяти, сводя его к однократной модели; не потоковое. Настоящие алгоритмы AEAD, такие как GCM или CCM, могут эффективно выполнять потоковую передачу.

public class CryptoStream : Stream, IDisposable
{
  ...
  public CryptoStream(Stream stream, IAuthenticatedCipherTransform transform, CryptoStreamMode mode);
  public override int Read(Span<byte> buffer, int offset, int count);
  public override Task<int> ReadAsync(Span<byte> buffer, int offset, int count, CancellationToken cancellationToken);
  public override void Write(ReadOnlySpan<byte> buffer, int offset, int count);
  public override Task WriteAsync(ReadOnlySpan<byte> buffer, int offset, int count, CancellationToken cancellationToken);

  public void FlushFinalBlock()
  {
    ...
    // If IAuthenticatedCipherTransform
    //    If encrypting, `TransformFinalBlock` -> `GetTag` -> append to out stream
    //    If decryption, extract last `TagSize` bytes -> `SetExpectedTag` -> `TransformFinalBlock`
    ...
  }

  ...
}

Слой в аутентифицированном шифровании

С учетом вышеизложенного мы можем добавить недостающие биты, чтобы разрешить аутентифицированное шифрование со связанными данными (AEAD).

Расширение нового ICipherTransform для AEAD

Это позволяет CryptoStream правильно выполнять свою работу. Мы также можем использовать интерфейс IAuthenticatedCipherTransform для реализации нашего собственного пользовательского класса/использования потоковой передачи, но работа с CryptoStream обеспечивает сверхсвязный и согласованный криптографический API .net.

  public interface IAuthenticatedCipherTransform : ICipherTransform
  {
    Span<byte> GetTag();
    void SetExpectedTag(Span<byte> tag);
  }

Базовый класс аутентифицированного шифрования

Просто расширяет SymmetricAlgorithm

public abstract class AuthenticatedSymmetricAlgorithm : SymmetricAlgorithm
{
  ...
  // Program Key/IV/AAD via class properties OR CreateAuthenticatedEn/Decryptor() params
  public abstract IAuthenticatedCipherTransform CreateAuthenticatedDecryptor(ReadOnlySpan<byte> Key = default, ReadOnlySpan<byte> IV = default, ReadOnlySpan<byte> AuthenticatedData = default);
  public abstract IAuthenticatedCipherTransform CreateAuthenticatedEncryptor(ReadOnlySpan<byte> Key = default, ReadOnlySpan<byte> IV = default, ReadOnlySpan<byte> AuthenticatedData = default);
  public virtual Span<byte> AuthenticatedData {...}
  public virtual Span<byte> Tag {...}
  public virtual int TagSize {...}
  ...
}

Класс AES GCM

public class AesGcm : AuthenticatedSymmetricAlgorithm
{
  public AesGcm(ReadOnlySpan<byte> Key = default, ReadOnlySpan<byte> IV = default, ReadOnlySpan<byte> AuthenticatedData = default)

  /* other stuff like valid key sizes etc similar to `System.Security.Cryptography.Aes` */
}

@sidshetye Я приветствую усилия.

Streaming-Encrypt через GCM выполним. Streaming-Decrypt через GCM

  • не допускается в NIST 800-38d. В разделе 5.2.2 «Аутентифицированная функция дешифрования» совершенно ясно, что возврат расшифрованного открытого текста P должен подразумевать правильную аутентификацию через тег T.
  • не безопасно. Существует понятие безопасности алгоритмов, защищенных в настройке «Выпуск непроверенного открытого текста» (RUP). Безопасность RUP формализована в статье Андреевой и др., опубликованной в 2014 году. GCM не защищен в настройках RUP. В конкурсе CAESAR, где каждая запись сравнивается с GCM, безопасность RUP указывается в качестве желательного свойства. Непроверенный открытый текст, выпущенный из GCM, тривиально подвержен атакам с переворачиванием битов.

Ранее в этой ветке обсуждалась возможность асимметричных API-интерфейсов шифрования/дешифрования (концептуально), и я думаю, что все пришли к единому мнению, что это очень плохая идея.

Таким образом, у вас не может быть высокоуровневого байтового потокового API для расшифровки GCM. Я говорил это много раз прежде, и я говорю это снова. Единственный способ иметь потоковый API — это шифрование по частям. Я избавлю всех от карусели с фрагментированным шифрованием.

Что бы MS ни решила сделать для GCM API, RUP не может быть разрешен.

@sdrapkin RUP подробно обсуждался здесь , и мы уже перешли этот мост. Вкратце, RUP подразумевает, что расшифрованные данные не нужно использовать до тех пор, пока тег не будет проверен, но, как и в Java JCE, WinNT bcrypt, OpenSSL и т. д., его не нужно применять на границе метода. Как и в случае с большинством криптопримитивов, особенно с низкоуровневыми, используйте их с осторожностью.

^^^ столько всего. Я согласен с потоковым API более высокого уровня и т. д., а затем прекрасно применяю его. Но когда я хочу использовать примитив низкого уровня, мне нужно иметь возможность использовать такие вещи, как разделенные буферы и т. Д., И я должен убедиться, что данные не используются. Создайте исключение в точке расчета/проверки тега, но не ограничивайте низкоуровневые примитивы.

я должен убедиться, что данные не используются

Неправильный. Это не зависит от вас. AES-GCM имеет очень _специфическое_ определение, и это определение гарантирует, что оно не зависит от вас. Вам нужен отдельный примитив AES-CTR и отдельный примитив GHASH, которые затем можно комбинировать и применять по своему усмотрению. Но мы не обсуждаем отдельные примитивы AES-CTR и GHASH, не так ли? Мы обсуждаем AES-GCM. А AES-GCM требует , чтобы RUP не был разрешен.

Я также предлагаю просмотреть ответ Илмари Каронена от crypto.stackexchange .

@sdrapkin Вы делаете хорошее замечание. Однако было бы желательно, чтобы в конечном счете имелся алгоритм, безопасный для RUP, и чтобы этот алгоритм соответствовал API, выбранному здесь. Итак, нам предстоит выбрать:

  1. API не поддерживает потоковую передачу. Простой, но не хватает низкоуровневого API. Мы можем пожалеть об этом однажды.
  2. Реализация AES-GCM предотвращает потоковую передачу, придерживаясь спецификации, не ограничивая API.

Можем ли мы определить сценарий потоковой передачи и создать исключение, объясняющее, почему это использование неверно? Или нам нужно прибегнуть к тому, чтобы его потоковая реализация потребляла весь буфер? Последнее было бы неудачно: вы можете думать, что транслируете, но это не так.

Мы могли бы добавить метод SupportsStreaming(out string whyNot) , который проверяется реализацией потоковой передачи.

Есть ли у нас веский аргумент против потоковой передачи/меток в конце в целом ? Если нет, то я считаю, что мы должны стремиться не исключать этого с помощью API.

@sdrapkin : давайте посмотрим на RUP шире, поскольку это библиотека, а не приложение. Так что это скорее проблема буферизации и проектирования слоев, чем фактическая публикация/использование непроверенных данных. Глядя на специальную публикацию NIST 800-38D для AES GCM , мы видим, что

  1. Спецификация GCM определяет максимальную длину открытого текста как 2 ^ 39-256 бит ~ 64 ГБ. Буферизация, близкая к системной памяти, нецелесообразна.

  2. Спецификация GCM определяет вывод как FAIL, если тег терпит неудачу. Но это не предписывает, какой уровень в реализации должен буферизоваться до проверки тега. Давайте посмотрим на стек вызовов, например:

A => AESGCM_Decrypt_App(key, iv, ciphertext, aad, tag)
B =>  +- AESGCM_Decrypt_DotNet(key, iv, ciphertext, aad, tag)
C =>    +- AESGCM_Decrypt_OpenSSL(key, iv, ciphertext, aad, tag)

Где
A — это AES GCM на прикладном уровне.
B — это AES-GCM на языковом уровне.
C — это AES-GCM на уровне платформы.

Открытый текст высвобождается в точке (A), если тег проверяется, но в противном случае возвращается FAIL. Однако абсолютно нигде в спецификации не предполагается, что основная память является единственным местом для буферизации незавершенного открытого текста, и что буферизация должна происходить в (B) или (C) или где-либо еще. На самом деле OpenSSL, Windows NT Bcrypt в примере (C), где потоковая передача разрешает буферизацию на более высоком уровне. И Java JCA, Microsoft CLR Security и мое предложение выше являются примерами (B), где потоковая передача разрешает буферизацию на прикладном уровне. Самонадеянно предполагать, что у разработчиков A нет лучших возможностей буферизации перед выпуском открытого текста. Этот буфер в теории и на практике может быть памятью, твердотельными накопителями или кластером хранения в сети. Или перфокарты ;) !

Даже если оставить в стороне буферизацию, в документе обсуждаются другие практические проблемы (см. Раздел 9.1, Вопросы проектирования и 9.2, Вопросы эксплуатации), такие как актуальность ключей или неповторение IV при сбоях питания на неопределенный срок. Мы, очевидно, не будем запекать это в слое B, т.е. здесь.

@timovzl недавнее предложение , приведенное выше, касается обоих сценариев: одноразового (архитектор не заботится о буферизации), а также потокового (архитектор имеет лучшие возможности буферизации). Пока в документации по потоковому API низкого уровня четко указано, что потребитель теперь несет ответственность за его буферизацию, доказательство безопасности не снижается и отклонение от спецификации отсутствует.

РЕДАКТИРОВАТЬ: грамматика, опечатки и попытка заставить работать уценку

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

Дискуссия возвращается к тому, являются ли эти API уровня «потребителя» или они настоящие примитивы. Если они являются настоящими примитивами, то они должны раскрывать функциональность, чтобы можно было построить «более безопасные» API более высокого уровня. Выше уже было решено при обсуждении Nonce, что это должны быть настоящие примитивы, что означает, что вы можете выстрелить себе в ногу, я думаю, то же самое относится к потоковому/частичному декодированию зашифрованного текста.

Говоря это, будет необходимо быстро предоставить «более высокий» уровень и более безопасный API, чтобы люди не «накатывали» свои собственные поверх них.

Мой интерес связан с сетью/конвейерами, и если вы не можете делать частичные буферы и должны делать «один выстрел», тогда не будет никакой пользы от недостатка этих API, поэтому я бы продолжил напрямую обращаться к BCrypt/OpenSsl и т. д.

Мой интерес связан с сетью/конвейерами, и если вы не можете делать частичные буферы и должны делать «один выстрел», тогда не будет никакой пользы от недостатка этих API, поэтому я бы продолжил напрямую обращаться к BCrypt/OpenSsl и т. д.

Точно. Потребность не исчезнет, ​​поэтому люди будут использовать другие реализации или создавать свои собственные. Это не обязательно более безопасный результат, чем разрешение потоковой передачи с хорошей предупреждающей документацией.

@Timovzl , я думаю, мы получили много технических отзывов и требований к дизайну в отношении последнего предложения . Мысли о реализации и выпуске?

@Sidshetye сделал подробное предложение , которое, я считаю, отвечает всем требованиям. Единственная критика в отношении RUP была рассмотрена без дальнейших возражений. (В частности, RUP может быть предотвращен на одном из нескольких уровней, и низкоуровневый API не должен определять, какой; и ожидается, что предложение _no_ потоковой передачи будет иметь худшие последствия.)

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

Я в восторге от этого предложения и от того, что API обретает форму.

@Sidshetye , у меня есть несколько вопросов и предложений:

  1. Желательно ли наследовать от существующего SymmetricAlgorithm ? Есть ли какие-либо существующие компоненты, с которыми мы хотим интегрироваться? Если я не упускаю какое-то преимущество этого подхода, я бы предпочел видеть AuthenticatedEncryptionAlgorithm без базового класса. По крайней мере, он позволяет избежать раскрытия нежелательных методов CreateEncryptor / CreateDecryptor (неаутентифицированных!)
  2. Ни один из задействованных компонентов нельзя использовать с асимметричной криптографией, да? Почти все компоненты опускают «Симметричный» в своих именах, с чем я согласен. Если мы не продолжим наследовать SymmetricAlgorithm , AuthenticatedSymmetricAlgorithm можно переименовать в AuthenticatedEncryptionAlgorithm , придерживаясь общепринятого термина Authenticated Encryption.
  3. Измените TryEncrypt / TryDecrypt для записи/получения тега вместо того, чтобы иметь настраиваемое свойство Tag в алгоритме.
  4. Какова цель установки key , iv и authenticatedAdditionalData через общедоступные сеттеры? Я бы избегал нескольких допустимых подходов и изменчивых свойств, насколько это возможно. Не могли бы вы создать обновленное предложение без них?
  5. Хотим ли мы вообще какое-либо состояние в AesGcm ? Мой инстинкт состоит в том, чтобы определенно не использовать iv и authenticatedAdditionalData , так как они указаны для каждого сообщения. key может быть полезно иметь в качестве состояния, поскольку мы обычно хотим выполнять несколько операций с одним ключом. Тем не менее, можно также получить ключ для каждого вызова. Те же вопросы относятся к CreateAuthenticatorEncryptor . В любом случае мы должны остановиться на _одном_ способе_ передачи параметров. Спешу обсудить плюсы и минусы. Я склоняюсь к ключевому состоянию в AesGcm , а остальные в CreateAuthenticatedEncryptor или TryEncrypt соответственно. Если мы уже пришли к соглашению, покажите нам обновленное предложение. :-)
  6. ICipherTransform , вероятно, должен быть абстрактным классом, CipherTransform , чтобы можно было добавлять методы, не нарушая существующие реализации.
  7. Все параметры функции должны быть в camelCase, т.е. начинаться с нижнего регистра. Кроме того, мы должны сказать authenticatedData или authenticatedAdditionalData ? Кроме того, я думаю, что мы должны выбрать имена параметров plaintext и ciphertext .
  8. Везде, где передается IV, я хотел бы видеть его как необязательный параметр, упрощающий получение правильно сгенерированного (крипторандомного) IV, чем предоставление собственного. По крайней мере, усложняет неправильное использование низкоуровневого API, и мы получаем это бесплатно.
  9. Я все еще пытаюсь понять, как клиентский код TryEncrypt может узнать требуемую длину промежутка для предоставления ciphertext ! То же самое для TryDecrypt и длины plaintext . Конечно, мы не должны пробовать их в цикле до тех пор, пока не добьемся успеха, удваивая длину после каждой неудачной итерации?

Наконец, если подумать наперед, как может выглядеть высокоуровневый API, построенный поверх этого? Глядя исключительно на использование API, кажется, что есть мало возможностей для улучшения, поскольку как потоковый, так и непотоковой API уже настолько просты! Основными отличиями, которые я представляю, являются автоматический IV, автоматические размеры вывода и, возможно, ограничение на объем зашифрованных данных.

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

Go нет, и libsodium нет.

Вроде первая волна позволяла, а последующие нет. Поскольку мы, бесспорно, находимся в более поздней волне, я думаю, что мы собираемся не допускать этого. Если спрос на потоковую передачу увеличился после введения одноразовой модели (для шифрования/дешифрования ключ может сохраняться между вызовами), мы можем провести повторную оценку. Таким образом, предложение API, которое придерживается этого шаблона, кажется выгодным. Хотя ни SIV, ни CCM не поддерживают потоковое шифрование, поэтому потоковый API для них потенциально сильно буферизируется. Сохранять ясность кажется лучше.

Предложения также не должны включать тег в полезную нагрузку (GCM и CCM называют его отдельным данным), если только сам алгоритм (SIV) не включает его в выходные данные шифрования. ( E(...) => (c, t) против E(...) => c || t или E(...) => t || c ). Пользователи API, безусловно, могут использовать его как concat (просто откройте соответствующим образом диапазоны).

Спецификация GCM не позволяет выдавать ничего, кроме FAIL , при несоответствии тегов. NIST совершенно ясно говорит об этом. В оригинальной статье GCM McGrew & Viega также говорится:

Операция расшифровки вернет FAIL , а не открытый текст, и декапсуляция остановится, а открытый текст будет отброшен, а не отправлен или обработан дальше.

Ни один из предыдущих комментариев не касался RUP - они просто отмахивались от него ("об этом позаботится более высокий уровень" - да, верно).

Все просто: шифрование GCM может передаваться в потоковом режиме. Расшифровка GCM не может выполняться в потоковом режиме. Все остальное уже не GCM.

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

@bartonjs , вы буквально игнорируете весь технический и логический анализ и вместо этого используете даты проектов Go и libsodium в качестве слабого прокси для реального анализа? Представьте, если я сделаю аналогичный аргумент, основываясь на названиях проектов. Плюс мы определяемся с интерфейсом И реализацией. Вы понимаете, что выбор непотокового интерфейса для AEAD исключает все подобные реализации в будущем, верно?

Если спрос на потоковую передачу увеличился после введения одноразовой модели (для шифрования/дешифрования ключ может сохраняться между вызовами), мы можем провести повторную оценку.

Почему спрос, демонстрируемый на GitHub, недостаточен? Доходит до того, что кажется совершенно причудливым поддерживать необходимость выполнять меньше работы, чем по каким-либо техническим или потребительским требованиям.

@bartonjs , вы буквально игнорируете весь технический и логический анализ и вместо этого используете даты проектов Go и libsodium в качестве слабого прокси для реального анализа?

Нет, я пользуюсь советом профессиональных криптографов, которые говорят, что это чрезвычайно опасно и что нам следует избегать потоковой передачи AEAD. Затем я использую информацию от команды CNG о том, что «многие люди говорят, что хотят этого в теории, но на практике почти никто этого не делает» (я не знаю, насколько это телеметрия по сравнению с анекдотичными данными из запросов на помощь). Тот факт, что другие библиотеки пошли по однократному пути, просто подкрепляет принятое решение.

Почему спрос, демонстрируемый на GitHub, недостаточен?

Было упомянуто несколько сценариев. Обработка фрагментированных буферов, вероятно, может быть решена путем принятия ReadOnlySequence , если кажется, что сценария достаточно, чтобы оправдать усложнение API вместо того, чтобы вызывающая сторона выполняла повторную сборку данных.

Большие файлы — это проблема, но большие файлы — это уже проблема, так как у GCM ограничение составляет чуть меньше 64 ГБ, что «не так уж и много» (хорошо, это довольно много, но это не то «ух ты, это много», что это было). Файлы, отображаемые в памяти, позволят использовать диапазоны (до 2 ^ 31-1), не требуя 2 ГБ ОЗУ. Таким образом, мы сократили максимум на пару битов... это, вероятно, произойдет со временем.

Вы понимаете, что выбор непотокового интерфейса для AEAD исключает все подобные реализации в будущем, верно?

Я все больше и больше убеждаюсь, что @GrabYourPitchforks был прав (https://github.com/dotnet/corefx/issues/23629#issuecomment-334638891), что, вероятно, нет разумного унифицирующего интерфейса. GCM _требует_ одноразового номера/IV, а SIV _запрещает_ это означает, что инициализация режима/алгоритма AEAD уже требует знания о том, что должно произойти... на самом деле в AEAD нет понятия "абстрагироваться". SIV диктует, куда идет «метка». ГКМ/СКК нет. SIV является тэг-первым, по спецификации.

SIV не может начать шифрование, пока не получит все данные. Таким образом, его потоковое шифрование будет либо генерировать (что означает, что вы должны знать, чтобы не вызывать его), либо буферизировать (что может привести к времени работы n ^ 2). CCM не может начать работу, пока не будет известна длина; но CNG не позволяет предварительно зашифровать подсказку длины, так что это в той же лодке.

Мы не должны разрабатывать новый компонент, в котором проще сделать что-то неправильно, чем правильно по умолчанию. Потоковое дешифрование делает очень простым и заманчивым подключение класса Stream (как ваше предложение сделать это с CryptoStream), что позволяет очень легко получить ошибку проверки данных до проверки тега, что почти полностью сводит на нет преимущества AE . ( IGcmDecryptor => CryptoStream => StreamReader => XmlReader => «подождите, это незаконный XML...» => адаптивный оракул зашифрованного текста) .

Дело доходит до... покупательского спроса.

Как я, к сожалению, слишком много раз в своей жизни слышал: извините, но вы не тот клиент, которого мы имеем в виду. Я допускаю, что, возможно, вы знаете, как безопасно выполнять GCM. Вы знаете, что следует выполнять потоковую передачу только в изменчивый файл/буфер/и т. д. до проверки тега. Вы знаете, что означает управление одноразовыми номерами, и знаете, как ошибиться. Вы знаете, что нужно обращать внимание на размеры потоков и переходить на новый сегмент GCM после 2 ^ 36-64 байт. Вы знаете, что после того, как все сказано и сделано, это ваша ошибка, если вы все сделаете неправильно.

С другой стороны, клиент, которого я имею в виду, это тот, кто знает: «Я должен зашифровать это», потому что их босс сказал им. И они знают, что при поиске того, как сделать шифрование, в каком-то учебнике говорилось «всегда использовать AE» и упоминается GCM. Затем они находят учебник по шифрованию в .NET, в котором используется CryptoStream. Затем они подключают конвейер, не подозревая, что они только что сделали то же самое, что выбрали SSLv2... поставили галочку в теории, но не на практике. И когда они делают это, _эта_ ошибка принадлежит всем, кто знает лучше, но пусть неправильная вещь будет слишком легко сделать.

вы не клиент, которого мы имеем в виду [...] Клиент, которого я имею в виду, с другой стороны, это тот, кто знает: «Я должен зашифровать это», потому что их босс сказал им [...]

@bartonjs несколько месяцев назад мы уже решили, что цель состоит в том, чтобы нацелиться на два профиля клиентов, используя низкоуровневый API (мощный, но небезопасный при определенных условиях) и высокоуровневый API (защищенный от дурака). Это даже в названии. Это, безусловно, свободная страна, но было бы неискренним сейчас двигать штангу ворот, заявляя об обратном.

С другой стороны, клиент, которого я имею в виду, это тот, кто знает: «Я должен зашифровать это», потому что их босс сказал им. И они знают, что при поиске того, как сделать шифрование, в каком-то учебнике говорилось «всегда использовать AE» и упоминается GCM. Затем они находят учебник по шифрованию в .NET, в котором используется CryptoStream. Затем они подключают конвейер, не подозревая, что они только что сделали то же самое, что выбрали SSLv2... поставили галочку в теории, но не на практике. И когда они это делают, эта ошибка принадлежит всем, кто знает лучше, но пусть неправильная вещь будет слишком легко сделать.

@bartonjs Подождите, что случилось с низкоуровневым примитивом? Я думал, что целью этого конкретного выпуска была гибкость, а не присмотр за детьми. Обязательно дайте нам знать, если план изменился, чтобы мы все говорили об одном и том же.

Кроме того, рассматриваются ли поблочные методы или только одноразовые методы?

Я все больше и больше убеждаюсь, что @GrabYourPitchforks был прав (#23629 (комментарий)), что, вероятно, нет разумного унифицирующего интерфейса.

Глядя на все примеры, это начинает выглядеть все более и более бесполезным, особенно для низкоуровневого API, где реализации имеют такие разные ограничения. Возможно, нам следует просто подать хороший пример с помощью AES-GCM, а не объединяющего интерфейса. В качестве примечания, последний может быть интересен для будущего высокоуровневого API. Его свойство быть более ограничительным, вероятно, значительно облегчит создание унифицирующего интерфейса.

поблочные методы все еще находятся в стадии рассмотрения или только одноразовые методы?

Как упоминалось в https://github.com/dotnet/corefx/issues/23629#issuecomment -378605071, мы считаем, что риск против вознаграждения против выраженных вариантов использования говорит о том, что мы должны разрешать только одноразовые версии AE.

Всю дискуссию не читал, только отрывки. Я не знаю, в каком направлении вы идете. Извините, если то, что я пишу, не имеет смысла в данном контексте. Мои 2 цента:

  • Потоки важны. Если вы не можете поддерживать их напрямую, потому что это будет означать уязвимость системы безопасности, то, если возможно, предоставьте оболочку более высокого уровня, построенную поверх вашего низкоуровневого API, которая будет раскрывать потоки (неэффективным, но безопасным способом).
  • Если AES-GCM абсолютно не может использовать потоки ни в каком сценарии, предоставьте законную реализацию AES-CBC-HMAC, основанную на потоках. Или какой-то другой алгоритм АЭ.
  • Чем выше уровень, тем лучше. Чем меньше областей, где пользователь может ошибиться, тем лучше. Смысл — выставить API, который бы скрывал как можно больше вещей (например, этот тег аутентификации). Конечно, могут (должны) быть и более специфические перегрузки.
  • ИМХО не стоит заморачиваться унификацией интерфейсов с другими сервисами шифрования, если они просто не подходят. Это то, что openssl сделал с их CLI, и результат плохой (например, нет возможности предоставить тег аутентификации).

Мы (группа безопасности .NET) совещались между собой и с более широкой командой по криптографии в Microsoft. Мы обсудили многие вопросы и опасения, упомянутые в этой теме. В конечном счете, эти опасения не были достаточно убедительными, чтобы оправдать введение потокового GCM API в качестве основного строительного блока в структуре.

Это решение может быть пересмотрено в будущем, если возникнет такая необходимость. И в то же время мы не сделали ситуацию хуже, чем она есть сегодня: разработчики, которые в настоящее время используют стороннюю криптобиблиотеку для потоковой поддержки GCM, могут продолжать это делать, и они не будут нарушены нашим намерением ввести API-интерфейс GCM без потоковой передачи.

Как быть с шифрованием данных, которые не помещаются в память?

@pgolebiowski Вы используете высокоуровневые криптографические библиотеки .NET , специально разработанные для обеспечения безопасного потокового шифрования.

@sdrapkin это легче сказать, чем сделать. «безопасный» — это слишком много. что там доказано и на самом деле можно доверять? ты сам говоришь:

Библиотека Bouncy Castle c# (типичная рекомендация StackOverflow). Bouncy Castle c# — это огромный (145 тыс. LOC), плохо работающий музейный каталог криптографии (некоторые из них древние), со старыми реализациями Java, перенесенными на такой же старый .NET (2.0?).

хорошо, а какие варианты? может быть, ваша собственная библиотека? не совсем . хм... может быть, libsodium-net? не совсем .

когда вы на самом деле ищете проверенную библиотеку, полученную из довольно надежного источника (например, Microsoft или широко используемого сообществом), я не думаю, что такая библиотека существует в мире .NET Core.


  • клиент: аутентифицированное шифрование данных, которые не помещаются в память?
  • Microsoft: извините, но вы не тот клиент, которого мы имеем в виду. используйте библиотеку, которая не проверена и ее безопасность сомнительна, не наша проблема, если вы подвергаетесь атаке по сторонним каналам.
  • клиент:

@pgolebiowski Можно использовать установленную инфраструктуру .NET, т.е. то самое, что доказано и которому можно доверять, как пожелаете. Другие библиотеки (включая мою) ждут, пока Microsoft добавит недостающие криптографические примитивы, такие как ECDH, в NetStandard.

Вы также можете посмотреть на вилки Inferno. Есть по крайней мере 2 форка, в которых несколько тривиальных изменений достигли NetStandard20.

Ваша библиотека была проверена 2 года назад в течение 2 дней одним парнем . Я не верю в это, извините. В Microsoft для этого должна быть специальная команда из людей, которым я доверяю больше, чем в Cure53.

Серьезно, мы можем говорить о сторонней поддержке многих вещей. Но все необходимые вещи, связанные с безопасностью, должны быть предоставлены стандартной библиотекой.

@pgolebiowski Я далек от того, чтобы пытаться убедить кого-либо доверять чему-либо, но ваше утверждение неверно. Inferno был проверен 2 профессионалами из организации «Cure53». Аудит занял 2 дня, а вся библиотека составила ~1000 строк кода. Это примерно 250 строк кода на аудитора в день — вполне управляемо.

Фактически, легкая аудируемость является одной из ключевых особенностей Inferno, особенно для тех, кто не хочет доверять.

Цель этой темы — добавить поддержку AES-GCM. Ваша библиотека даже не поддерживает AES-GCM. Если вы хотите, чтобы люди использовали ваш код, отправьте его как предложение для corefx. В соответствующей ветке.

Другое дело, даже если он и поддерживал этот алгоритм — он не прошел проверку .net crypto board и не является частью corefx. Он даже не кандидат на такое рассмотрение. Это означает конец этого бесполезного обсуждения и рекламы.

@pgolebiowski Я ничего не рекламировал - просто ответил на ваш вопрос, предложил альтернативы для вашего варианта использования и исправил неточные утверждения. AES-GCM не подходит для потокового шифрования, и это было проверено и согласовано командой безопасности .NET, поэтому вы можете доверять этому .

я где-нибудь защищаю потоковую передачу AES-GCM? не могу вспомнить. но могу вспомнить слова:

  • Потоки важны. Если вы не можете поддерживать их напрямую, потому что это будет означать уязвимость системы безопасности, то, если возможно, предоставьте оболочку более высокого уровня, построенную поверх вашего низкоуровневого API, которая будет раскрывать потоки (неэффективным, но безопасным способом).
  • Если AES-GCM абсолютно не может использовать потоки ни в каком сценарии, предоставьте законную реализацию AES-CBC-HMAC, основанную на потоках. Или какой-то другой алгоритм АЭ.

или констатация открытой проблемы для corefx:

Как быть с шифрованием данных, которые не помещаются в память?


бонус:

Вы также можете посмотреть на вилки Inferno. Есть по крайней мере 2 форка, в которых несколько тривиальных изменений достигли NetStandard20. [...] Inferno был проверен 2 профессионалами из организации "Cure53" [...] легкая аудируемость - одна из ключевых особенностей Inferno, именно для тех, кто не хочет доверять.

я ничего не рекламировал

Между прочим, я никогда не хотел «стриминга» в том смысле, в котором думают большинство. Я просто хотел «блочную» обработку по причинам производительности и использования памяти. На самом деле я не хочу «выводить» результаты. Это то, что поддержка openssl и cng кажется позорной, чтобы потерять это, и в основном делает «примитивы» бесполезными в любом сценарии, о котором я могу думать.

@Drawae Если подумать, блочная операция может быть намного безопаснее, чем использование потоков. Неспециалист может использовать потоки, но он скорее будет использовать одноразовый API, чем блочную операцию. Более того, блочную операцию нельзя _прямо_ совмещать, скажем, с XmlReader . Так что на самом деле многие из обсуждаемых опасностей относятся к потоковым объектам, но не к блочным операциям.

Чтобы работать с блочной операцией, когда также доступен одноразовый API, предполагается, что мы знаем, что делаем, и что нам конкретно требуется низкоуровневая настройка. Мы могли бы защитить непрофессионала _и_ иметь гибкость.

Что касается отказа от RUP, я все еще размышляю над тем, насколько эффективна блочная операция для GCM. Шифрование извлекает все преимущества, а расшифровка — лишь некоторые. Мы можем избежать хранения полного зашифрованного текста, но мы все равно должны буферизовать полный открытый текст. Дешифратор _мог_ выбрать сохранение промежуточного открытого текста на диске. Но взамен мы ввели больше права на ошибку. Есть ли у нас убедительный аргумент, чтобы не решать эту проблему на более высоком уровне (например, использовать фрагменты или использовать настоящий потоковый алгоритм)?

TLS и конвейеры. В настоящее время (и в обозримом будущем) конвейеры используют блоки размером 4 КБ, но сообщение tls может состоять из 16 КБ зашифрованного текста. Одним выстрелом вам нужно будет скопировать 16 КБ в один непрерывный буфер, прежде чем вы сможете расшифровать. С блоками у вас может быть, скажем, 4 или 5, и вам может потребоваться буферизация до 16 байтов, чтобы обеспечить конкуренцию блоков.

@Drawae 16k по-прежнему постоянен и невелик. В данном контексте это имеет большое значение?

Да, это означает, что еще одна копия находится в разработке. Это оказывает серьезное и измеримое влияние на производительность.

Что нужно, чтобы это произошло? Каковы следующие шаги? @Drawaes

Что касается AES-GCM, я думаю, что его доставка затруднена из-за блокировки соответствующей проблемы: https://github.com/dotnet/corefx/issues/7023. @blowdart , ты не мог бы разблокировать? Действительно трудно добиться прогресса, когда люди не могут обсуждать. Или, если это не вариант, возможно, предложите альтернативное решение, позволяющее сделать эту функцию общедоступной.

Нет, я не разблокирую это. Решение принято, тема готова.

@blowdart Спасибо за ответ. Я понимаю, что, возможно, это было недостаточно ясно:

Или, если это не вариант, возможно, предложите альтернативное решение, позволяющее сделать эту функцию общедоступной.

Я ценю, что есть решение для поддержки AES-GCM. Это здорово, я определенно хочу этот алгоритм. Таким образом, теперь было бы здорово, чтобы он действительно поддерживался. Вы хотите, чтобы обсуждение разработки и реализации AES-GCM проводилось здесь или в новом выпуске?

И вообще, если эта тема закрыта, то почему бы ее не закрыть? И сделайте это более явным, изменив название этой проблемы, так как сейчас предполагается, что обсуждение реализации будет проходить здесь: https://github.com/dotnet/corefx/issues/7023. Может быть, что-то вроде Решите, какой алгоритм AEAD поддерживать в первую очередь .

Другими словами: я сообщаю, что в текущей ситуации неясно, что нужно для продвижения AES-GCM вперед.

@карелз

@pgolebiowski Уже вышел пиар. Вероятно, он будет доступен в мастер-среде на следующей неделе.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

jamesqo picture jamesqo  ·  3Комментарии

btecu picture btecu  ·  3Комментарии

matty-hall picture matty-hall  ·  3Комментарии

omajid picture omajid  ·  3Комментарии

jzabroski picture jzabroski  ·  3Комментарии