Runtime: Allgemeines Primitiv auf niedriger Ebene für Chiffren (AES-GCM ist das erste)

Erstellt am 29. Aug. 2017  ·  143Kommentare  ·  Quelle: dotnet/runtime

Begründung

Es besteht ein allgemeiner Bedarf an einer Anzahl von Chiffren zur Verschlüsselung. Die heutige Mischung aus Interfaces und Klassen ist ein wenig unzusammenhängend geworden. Außerdem gibt es keine Unterstützung für Verschlüsselungen im AEAD-Stil, da sie die Möglichkeit benötigen, zusätzliche Authentifizierungsinformationen bereitzustellen. Die aktuellen Designs sind auch anfällig für Allokationen, und diese sind aufgrund der zurückkehrenden Arrays schwer zu vermeiden.

Vorgeschlagene API

Eine abstrakte Basisklasse für allgemeine Zwecke, die von konkreten Klassen implementiert wird. Dies ermöglicht eine Erweiterung, und da wir eine Klasse anstelle von statischen Methoden haben, haben wir die Möglichkeit, Erweiterungsmethoden zu erstellen und den Zustand zwischen Aufrufen zu halten. Die API sollte die Wiederverwendung der Klasse ermöglichen, um niedrigere Zuordnungen zu ermöglichen (nicht jedes Mal eine neue Instanz zu benötigen und beispielsweise nicht verwaltete Schlüssel abzufangen). Aufgrund der häufig nicht verwalteten Art der nachverfolgten Ressourcen sollte die Klasse IDisposable implementieren

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);
}

Beispielnutzung

(Die Eingabe- / Ausgabequelle ist ein mythischer Span-basierter Stream wie die IO-Quelle)

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-Verhalten

  1. Wenn get tag vor finish aufgerufen wird, sollte ein [Exception Type?] geworfen und der interne Zustand auf ungültig gesetzt werden
  2. Wenn das Tag zum Entschlüsseln ungültig ist, sollte eine Ausnahme ausgelöst werden
  3. Sobald beendet ist, wird ein Aufruf an etwas anderes als eine der Init-Methoden ausgelöst
  4. Sobald Init aufgerufen wird, wird ein zweiter Aufruf ohne "Beenden" ausgelöst
  5. Wenn der Typ einen gelieferten Schlüssel erwartet (eine gerade "new'd" up-Instanz), wenn der anfängliche "Init"-Aufruf nur einen IV hat, wird er ausgelöst
  6. Wenn der Typ beispielsweise aus einem speicherbasierten Schlüssel generiert wurde und Sie versuchen, den Schlüssel über Init zu ändern, und nicht nur den IV, den er auslöst
  7. Wenn get tag nicht vor dispose oder Init aufgerufen wird, soll eine Ausnahme ausgelöst werden? Um zu verhindern, dass der Benutzer das Tag versehentlich sammelt?

Referenz dotnet/corefx#7023

Aktualisierung

  1. Nonce in IV geändert.
  2. Verhaltensabschnitt hinzugefügt
  3. Die einzelnen Input/Output-Span-Fälle wurden aus Finish und Update entfernt, sie können nur Erweiterungsmethoden sein
  4. Eine Reihe von Spans in readonlyspan geändert, wie von @bartonjs vorgeschlagen
  5. Reset entfernt, Init mit IV sollte stattdessen verwendet werden
api-suggestion area-System.Security

Hilfreichster Kommentar

@bartonjs Sie ignorieren buchstäblich alle technischen und logischen Analysen und verwenden stattdessen die Daten der Go- und Libsodium-Projekte als schwachen Proxy für die tatsächliche Analyse?

Nein, ich verwende den Rat professioneller Kryptografen, die sagen, dass es außerordentlich gefährlich ist und dass wir das Streamen von AEAD vermeiden sollten. Dann verwende ich Informationen vom CNG-Team von „vielen Leuten sagen, dass sie es theoretisch wollen, aber in der Praxis tut es fast niemand“ (ich weiß nicht, wie viel davon Telemetrie im Vergleich zu anekdotischen Hilfsanfragen ist). Die Tatsache, dass andere Bibliotheken den One-Shot-Weg gegangen sind, _verstärkt_ einfach die Entscheidung.

Warum ist die bisher gezeigte Nachfrage auf GitHub unzureichend?

Einige Szenarien wurden erwähnt. Die Verarbeitung fragmentierter Puffer könnte wahrscheinlich durch das Akzeptieren ReadOnlySequence angegangen werden, wenn es den Anschein hat, dass es genug Szenario gibt, um eine Komplizierung der API zu rechtfertigen, anstatt dass der Aufrufer die Daten wieder zusammensetzt.

Große Dateien sind ein Problem, aber große Dateien sind bereits ein Problem, da GCM eine Grenze von knapp 64 GB hat, was "nicht allzu groß" ist (okay, es ist ziemlich groß, aber es ist nicht das "Wow, das ist groß". ich war). Speicherabgebildete Dateien würden die Verwendung von Spans (von bis zu 2^31-1) ermöglichen, ohne dass 2 GB RAM erforderlich wären. Also haben wir ein paar Bits vom Maximum abgespeckt ... das würde wahrscheinlich sowieso im Laufe der Zeit passieren.

Ihnen ist klar, dass die Entscheidung für eine Nicht-Streaming-Schnittstelle für AEAD alle derartigen Implementierungen in der Zukunft ausschließt, oder?

Ich bin mehr und mehr davon überzeugt, dass @GrabYourPitchforks Recht hatte (https://github.com/dotnet/corefx/issues/23629#issuecomment-334638891), dass es wahrscheinlich keine vernünftige vereinheitlichende Schnittstelle gibt. GCM _requiring_ a nonce/IV und SIV _forbidding_ bedeutet, dass die Initialisierung eines AEAD-Modus/-Algorithmus bereits Wissen darüber erfordert, was passieren wird ... es gibt nicht wirklich eine "abstrahierte" Vorstellung von AEAD. SIV diktiert, wohin "das Tag" geht. GCM/CCM nicht. SIV ist Tag-First, per Spezifikation.

SIV kann nicht mit der Verschlüsselung beginnen, bis es alle Daten hat. Die Streaming-Verschlüsselung wird also entweder werfen (was bedeutet, dass Sie wissen müssen, dass Sie sie nicht aufrufen) oder puffern (was zu n ^ 2-Operationszeit führen kann). CCM kann erst beginnen, wenn die Länge bekannt ist; aber CNG erlaubt keinen Vorverschlüsselungshinweis auf die Länge, also ist es im selben Boot.

Wir sollten keine neue Komponente entwerfen, bei der es einfacher ist, standardmäßig das Falsche als das Richtige zu tun. Die Streaming-Entschlüsselung macht es sehr einfach und verlockend, eine Stream-Klasse zu verdrahten (ähnlich wie Ihr Vorschlag, dies mit CryptoStream zu tun), wodurch es sehr einfach wird, einen Datenvalidierungsfehler zu erhalten, bevor das Tag überprüft wird, was den Vorteil von AE fast vollständig zunichte macht . ( IGcmDecryptor => CryptoStream => StreamReader => XmlReader => "Warte, das ist kein legales XML..." => adaptives Chiffretext-Orakel) .

Es kommt auf den Punkt ... Kundennachfrage.

Wie ich leider schon viel zu oft in meinem Leben gehört habe: Es tut mir leid, aber Sie sind nicht der Kunde, an den wir denken. Ich gebe zu, dass Sie vielleicht wissen, wie man GCM sicher durchführt. Sie wissen, dass Sie bis nach der Tag-Überprüfung nur in eine flüchtige Datei/Puffer/usw. streamen dürfen. Sie wissen, was Nonce-Management bedeutet, und Sie kennen die Risiken, Fehler zu machen. Sie wissen, dass Sie auf die Stream-Größen achten und nach 2 ^ 36-64 Bytes auf ein neues GCM-Segment wechseln müssen. Sie wissen, dass es letztendlich Ihr Fehler ist, wenn Sie diese Dinge falsch machen.

Der Kunde, an den ich hingegen denke, ist jemand, der weiß, „das muss ich verschlüsseln“, weil sein Chef es ihm gesagt hat. Und sie wissen, dass bei der Suche nach der Verschlüsselung in einem Tutorial "immer AE verwenden" und GCM erwähnt wird. Dann finden sie ein Tutorial "Verschlüsselung in .NET", das CryptoStream verwendet. Sie schließen dann die Pipeline an, ohne zu ahnen, dass sie gerade dasselbe getan haben wie SSLv2 gewählt haben ... theoretisch ein Kästchen angekreuzt, aber nicht wirklich in der Praxis. Und wenn sie es tun, gehört _dieser_ Fehler jedem, der es besser wusste, aber es zu einfach war, das Falsche zu tun.

Alle 143 Kommentare

Schnell zu beurteilendes Feedback zur vorgeschlagenen API (versucht, hilfreich zu sein):

  • Span<T> ist nicht in NetStandard2 enthalten .
  • "Nonce" ist sehr implementierungsspezifisch - dh. riecht nach GCM. Aber selbst GCM-Dokumente (z. B. NIST SP800-38D) bezeichnen es als "IV", was - im Fall von GCM - zufällig eine Nonce ist. Bei anderen AEAD-Implementierungen muss die IV jedoch keine Nonce sein (z. B. die Wiederholung der IV unter CBC + HMAC ist nicht katastrophal).
  • Streaming AEAD sollte entweder nahtlos mit CryptoStream zusammenarbeiten oder einen eigenen "AEADCryptoStream" bereitstellen, der genauso einfach ein- und auszuströmen ist wie CryptoStream.
  • AEAD-API-Implementierungen sollten die interne Schlüsselableitung basierend auf AAD (Associated Data) erlauben. Die Verwendung von AAD nur zur Tag-Berechnung/-Verifizierung ist zu restriktiv und verhindert ein stärkeres AEAD-Modell.
  • "Get*"-Methoden sollten etwas zurückgeben (GetTag). Wenn sie ungültig sind, müssen sie etwas setzen/den Zustand ändern.
  • Der Versuch, ein Tag vor dem „Fertigstellen“ zu erhalten, ist wahrscheinlich eine schlechte Idee, daher könnte „IsFinished“ hilfreich sein.
  • Die Leute, die ICryptoTransform entworfen haben, dachten über Wiederverwendung, Multi-Block-Unterstützung und unterschiedlich große Input/Output-Blockgrößen nach. Diese Bedenken werden nicht erfasst.

Als Beweis für die Vernunft der AEAD-API sollte die erste Implementierung einer solchen vorgeschlagenen API nicht AES-GCM sein, sondern das klassische/Standard-AES-CBC mit einem HMAC-Tag. Der einfache Grund dafür ist, dass jeder heute eine AES-CBC+HMAC AEAD-Implementierung mit einfachen, bestehenden und bekannten .NET-Klassen erstellen kann. Lassen Sie uns zuerst ein langweiliges altes [AES-CBC+HMAC] dazu bringen, die neuen AEAD-APIs zu bearbeiten, da dies für alle einfach zu MVP und Testfahrten ist.

Das Nonce/IV-Namensproblem war etwas, worüber ich unentschlossen war, glücklich mit einer Änderung zu IV, also wird es sich ändern.

Bei Get-Methoden, die etwas zurückgeben, werden Zuweisungen vermieden. Es könnte eine Überladung Get() geben, die etwas zurückgibt. Vielleicht ist eine Namensänderung erforderlich, aber ich bin ziemlich mit der Idee verheiratet, dass die gesamte API im Grunde zuweisungsfrei sein muss.

Was Streams usw. betrifft, so stört mich das nicht übermäßig, da es sich um APIs auf höherer Ebene handelt, die leicht aus den Primitives auf niedrigerer Ebene erstellt werden können.

Das Abrufen eines Tags, bevor Sie fertig sind, sollte nicht erlaubt sein, aber Sie sollten wissen, wann Sie Finish aufgerufen haben, also bin ich mir nicht sicher, ob es in der API sein sollte, aber es sollte ein definiertes Verhalten sein, also habe ich das API-Design aktualisiert, um es einzuschließen einen Verhaltensabschnitt, damit wir alle anderen erfassen können, an die wir denken.

Was die Chiffre angeht, denke ich nicht, dass eine bestimmte Chiffre das einzige Ziel sein sollte, um eine neue Allzweck-API zu beweisen, die einer Zahl entsprechen muss. AES GCM und CBC sollten beide abgedeckt sein.

(Alles Feedback zum Thema gut oder schlecht ist immer hilfreich!)

  • Klasse oder Schnittstelle?
  • Wie, wenn überhaupt, interagieren die aktuellen SymmetricAlgorithm-Klassen damit?
  • Wie würde dies für dauerhafte Schlüssel verwendet werden, wie es TripleDESCng und AesCng tun können?
  • Viele dieser Spans scheinen ReadOnlySpan zu sein.

@Drawaes Vielen Dank, dass Sie diese API ins Rollen gebracht haben. Ein paar Gedanken:

  1. Die Generierung und Überprüfung von Tags ist ein sehr wichtiger Teil dieser API, da der Missbrauch von Tags den gesamten Zweck zunichte machen kann. Wenn möglich, möchte ich, dass Tags in die Initialisierungs- und Endoperationen integriert werden, um sicherzustellen, dass sie nicht versehentlich ignoriert werden können. Das impliziert wahrscheinlich, dass Verschlüsselung und Entschlüsselung nicht dieselben Initialisierungs- und Finalisierungsmethoden verwenden sollten.
  2. Ich habe gemischte Gefühle, wenn es darum geht, Blöcke während der Entschlüsselung auszugeben, bevor ich zum Ende komme, da die Daten nicht vertrauenswürdig sind, bis das Tag überprüft wurde (was nicht möglich ist, bis alle Daten verarbeitet wurden). Wir müssen diesen Kompromiss sehr sorgfältig abwägen.
  3. Ist ein Reset notwendig? Sollte nur Reset beenden? Wir machen das mit inkrementellen Hashes (aber sie brauchen keine neuen IVs)

@bartonjs

  1. Klasse, wie schon oft in der BCL mit einer Schnittstelle gesehen, kann man sie später nicht erweitern, ohne alles kaputt zu machen. Eine Schnittstelle ist wie ein Welpe fürs Leben ... es sei denn, Standardschnittstellenmethoden können als Lösung für dieses Problem angesehen werden.
    Auch eine versiegelte Klasse von einem abstrakten Typ ist tatsächlich schneller (Stand jetzt), weil der Jitt die Methoden jetzt devirtualisieren kann ... Also im Grunde kostenlos. Schnittstellenversand ist nicht so gut (noch gut nur nicht so gut)
  2. Ich weiß nicht, wie soll das funktionieren? Ich habe wenig Interesse an dem aktuellen Zeug, da es so verwirrend ist, dass ich einfach alle vernünftigen modernen Algos direkt einfügen würde (lass 3DES in den anderen Klassen :) Aber ich habe nicht alle Antworten, also hast du weitere Gedanken dazu?
  3. Beständige Schlüssel sollten einfach sein. Erstellen Sie eine Erweiterungsmethode für die Schlüsselmethode oder den Persistenzspeicher.
MyKeyStore.GetCipher();

Es ist nicht initialisiert. Es ist wegwerfbar, so dass alle Refs nach dem normalen Wegwerfmuster fallen gelassen werden können. Wenn sie versuchen, den Schlüssel festzulegen, wird eine Ausnahme wegen ungültiger Operation ausgelöst.

Ja, die schreibgeschützten Spannen werde ich anpassen, wenn ich nicht auf der Röhre auf meinem Telefon bin.

@morganbr kein Problem ... Ich möchte nur sehen, dass es mehr als alles andere passiert;)

  1. Können Sie ein Code-Snippet geben, wie das Ihrer Meinung nach funktioniert? Ich bin mir nicht sicher, aber Code bringt immer Klarheit
  2. Schade, aber man muss die Klötzchen wirklich früh ausspucken. Mit Hmac und Hashing haben Sie keine Zwischendaten, sondern nur den Status. In diesem Fall müssten Sie also eine unbekannte Datenmenge puffern. Werfen wir einen Blick auf das Pipeline-Beispiel und TLS. Wir können 16 KB Klartext schreiben, aber die Pipeline-Puffer haben heute die Größe einer 4-KB-Seite. Wir würden also bestenfalls 4*4k verschlüsseln/entschlüsseln wollen. Geben Sie mir die Antwort erst am Ende, wenn Sie internen Speicher zuweisen müssen, um all das zu speichern, und ich nehme an, es wegzuwerfen, wenn ich das Ergebnis erhalte? Oder wirst du es abwischen. Was ist, wenn ich 10 MB entschlüssele und Sie diesen Speicher behalten, nachdem ich mich jetzt um die Verwendung des latenten Speichers kümmern muss?
  3. Nicht 100% auf die Init/Reset-Sache (nicht Ihre Ideen, meine aktuelle API-Form), es passt nicht gut zu mir, also bin ich offen für einen neuen Vorschlag!

Ich habe wenig Interesse an den aktuellen Sachen, da sie so verwirrend sind, dass ich einfach alle vernünftigen modernen Algos direkt einfügen würde (3DES in den anderen Klassen lassen :)

Das Problem wäre, dass Containerformate wie EnvelopedCms (oder EncryptedXml) möglicherweise mit 3DES-CBC, AES-CBC usw. funktionieren müssen. Jemand, der etwas entschlüsseln möchte, das mit ECIES/AES-256-CBC-PKCS7/HMAC-SHA-2 verschlüsselt ist -256 würde wahrscheinlich nicht das Gefühl haben, dass sie alte und miese Sachen machen.

Wenn es nur für AE sein soll, dann sollte sich das irgendwo im Namen wiederspiegeln. Im Moment ist es eine generische "Chiffre" (die ich irgendwann mit einem Wörterbuch / Glossar zusammensetzen und herausfinden werde, ob es ein Wort für "einen Verschlüsselungsalgorithmus in einem Betriebsmodus" gibt), da ich das denke "chiffre" == "Algorithmus", also "Aes").

:) Ich habe lediglich darauf hingewiesen, dass es nicht mein Fachgebiet ist oder mich nicht sehr interessiert, daher bin ich bereit, mich Ihnen und der Community zu diesem Thema zu widmen. Ich habe die Auswirkungen darauf nicht durchdacht.


Nachdem Sie diese schnell durchsucht haben, besteht eine Möglichkeit darin, ihnen zu erlauben, eine Instanz der „Cipher“-Klasse oder wie auch immer sie genannt wird, zu übernehmen. Dies wird möglicherweise nicht in der ersten Welle erfolgen, könnte aber schnell nachgeholt werden. Wenn die API supereffizient ist, sehe ich keinen Grund, warum sie intern ihr eigenes Ding machen sollten, und das ist genau der Anwendungsfall für diese API.

Als Randleiste bei der Benennung ... Ich muss zugeben, dass es jedoch schwierig ist
Openssl = Chiffre
Rubin = Chiffre
Go = Chiffrierpaket mit Ententyp-Schnittstellen für AEAD etc
Java = Chiffre

Jetzt bin ich dafür, anders zu sein, aber... es gibt einen Trend. Wenn etwas Besseres möglich ist, ist das cool.

Eventuell "BlockModeCipher" ... ?

Ich habe ein paar Änderungen vorgenommen, ich werde die Benennung ändern, wenn sich ein besserer Name ergibt.

Als ich anfing, Fragen zu beantworten, stellte ich fest, dass der API bereits die Unterscheidung zwischen Verschlüsselung und Entschlüsselung fehlt, sodass sie in Ihrem Beispiel nicht weiß, ob sie Daten verschlüsseln oder entschlüsseln soll. Wenn Sie das einfügen, könnte dies etwas Klarheit bringen.

Ich könnte mir ein paar Möglichkeiten vorstellen, wie die API die ordnungsgemäße Verwendung von Tags erzwingen könnte (basierend auf der Annahme, dass dies eine AEAD-API ist, nicht nur eine symmetrische Verschlüsselung, da wir bereits SymmetricAlgorithm/ICryptoTransform/CryptoStream haben). Betrachten Sie diese nicht als vorgeschrieben, sondern nur als Beispiel für die Durchsetzung des Tagging.
Nach Methode:

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
}

Nach Klasse:

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 {}
}

Das heißt, wenn es bei der Entschlüsselung nicht puffert, ist es dann praktisch sicherzustellen, dass die Entschlüsselung tatsächlich abgeschlossen und das Tag überprüft wird? Kann Update irgendwie herausfinden, dass es Zeit ist, dies zu überprüfen? Ist das etwas, was Dispose tun sollte? (Entsorgen ist etwas gefährlich, da Sie den Daten möglicherweise bereits vertraut haben, wenn Sie das Objekt entsorgen.)

In Bezug auf die Benennung ist unser Präzedenzfall SymmetricAlgorithm und AsymmetricAlgorithm. Wenn dies für AEAD vorgesehen ist, könnten einige Ideen AuthenticatedSymmetricAlgorithm oder AuthenticatedEncryptionAlgorithm sein.

Einige Gedanken und API-Ideen:

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 , danke, dass du Gedanken beigesteuert hast. Wo sollten Tags in Ihrer API platziert werden? Sind sie implizit Teil des Geheimtextes? Wenn dies der Fall ist, impliziert dies ein Protokoll, das einige Algorithmen nicht haben. Ich würde gerne verstehen, welche Protokolle die Leute interessieren könnten, um zu sehen, ob die implizite Tag-Platzierung akzeptabel ist oder ob sie separat durchgeführt werden muss.

@morganbr Ich habe auch das Verschlüsselungs-/Entschlüsselungsproblem bemerkt, hatte aber heute Abend keine Zeit, es zu beheben, so froh über Ihr Design. Ich bevorzuge die Methoden gegenüber der Klasse, da sie ein besseres aggressives Recycling ermöglichen (Puffer für Schlüssel und IVs können sich summieren).

Wie für eine Überprüfung vor der Entsorgung. Das Ende einer Operation kann man leider nicht sagen.

@sdrapkin- Schnittstellen sind meiner Meinung nach aufgrund des zuvor erwähnten Versionsproblems ein No-Go. Es sei denn, wir verlassen uns in Zukunft auf eine Standardimplantation. Auch der Schnittstellenversand ist langsamer. Die Array-Segmente sind auch unsere, da die Spanne das vielseitigere untere Grundelement ist. Es könnten jedoch Erweiterungsmethoden hinzugefügt werden, damit sich arraysegment überspannt, wenn später Bedarf besteht.

Einige Ihrer Eigenschaften sind von Interesse und werden daher aktualisiert, wenn ich an einem Computer und nicht an meinem Telefon sitze.

Rundum gute Resonanz!

@morganbr Tags sind Teil des Geheimtextes. Dies ist der CAESAR-API (die AES-GCM enthält) nachempfunden.

@Drawaes Ich habe Schnittstellen nur verwendet, um Gedanken zu veranschaulichen - ich bin mit statischen Methoden/Klassen vollkommen einverstanden. Spanneist nicht vorhanden. Es ist mir egal, was kommt oder nicht kommt - es ist nicht in NetStandard2 und es ist nicht im normalen .NET, das ernsthafte Projekte tatsächlich verwenden (ja, ja, ich weiß, es ist in Core, aber Core ist ein Spielzeug zur Zeit). Ich würde Span gerne persönlich in Betracht ziehenwenn ich es sehe - bis dahin ArraySegmentist die am nächsten kommende NetStandard-API, die tatsächlich ausgeliefert wird.

Ich werde einen tieferen Blick auf die nützliche CAESAR-API werfen.

Was Span betrifft, wird es meiner Meinung nach um den 2.1-Zeitrahmen herum ausgeliefert, was hoffentlich zur gleichen Zeit ist, zu der die erste Implementierung dieser API ausgeliefert wird (oder zumindest zum frühestmöglichen Zeitpunkt).

Wenn Sie sich das aktuelle Vorabversions-Nuget-Paket ansehen, unterstützt es bis hinunter zum .net-Standard 1.0, und es gibt keine Pläne, dies bei der Veröffentlichung zu ändern.

Vielleicht kann @stephentoub das bestätigen, während er daran arbeitet, Span-basierte APIs über das gesamte Framework hinzuzufügen, während wir hier sprechen.

(Nuget für Span)[https://www.nuget.org/packages/System.Memory/4.4.0-preview2-25405-01]

Ich würde also sagen, dass es die einzige wirkliche Wahl für eine brandneue API ist. Dann können Erweiterungsmethoden usw. hinzugefügt werden, um ein ArraySegment zu übernehmen, wenn Sie dies wünschen, und wenn es nützlich genug ist, kann es dem Framework hinzugefügt werden, aber es ist trivial, ein ArraySegment in eine Spanne umzuwandeln, aber auf die andere Weise müssen Daten kopiert werden.

Das Problem, das ich mit dieser API oben sehe, ist, dass es eine Katastrophe für die Leistung bei allen "chunked" Daten sein wird. Sagen wir zum Beispiel Netzwerkverkehr, wenn ein einzelner authentifizierter Block auf mehrere Lesevorgänge aus einem vorhandenen Stream aufgeteilt wird, muss ich alles in einer einzigen [Datenstruktur einfügen] puffern und auf einmal verschlüsseln/entschlüsseln. Das vereitelt alle Versuche, irgendeine Art von Nullkopie auf diesen Daten zu erstellen.

Netzwerk-Frameworks wie die von Pipelines schaffen es, fast alle Kopien zu vermeiden, aber wenn sie dann auf irgendeine Art von Krypto in dieser API treffen, ist all das weg.

Das separate Konfigurationsobjekt (oder Bag) war tatsächlich in eine kürzlich von mir geführte Diskussion über eine andere API involviert. Ich bin nicht grundsätzlich dagegen, denn wenn es in Zukunft wächst, kann es ein Durcheinander werden, viele Eigenschaften auf dem Hauptobjekt zu haben.

Ein paar Dinge sind mir eingefallen.

  • Der aktuelle Vorschlag nennt TagSize einen Out-Wert (also eine Get-Only-Property). Aber sowohl für GCM als auch für CCM ist es eine Eingabe für die Verschlüsselung (und von der Entschlüsselung ableitbar, da Sie das eigentliche Tag angegeben haben).
  • Der Vorschlag geht davon aus, dass Eingabe und Ausgabe gleichzeitig und stückweise erfolgen können.

    • IIRC CCM kann keine Streaming-Verschlüsselung durchführen (die Länge des Klartexts ist eine Eingabe in den ersten Schritt des Algorithmus).

    • Aufgefüllte Modi verzögern die Entschlüsselung um (mindestens einen) Block, da sie bis zum Aufruf von Final nicht wissen, ob weitere Daten kommen / ob der aktuelle Block das Auffüllen entfernen muss

  • Einige Algorithmen betrachten das AD-Element möglicherweise zu Beginn der Operation als erforderlich, wodurch es eher einem Init/ctor-Parameter als einer spät gebundenen Assoziation ähnelt.

Ich weiß nicht, ob Containerformate (EnvelopedCms, EncryptedXml) den Schlüssel extrahieren müssten oder ob es einfach an ihnen wäre, ihn zu generieren und sich daran zu erinnern (so lange sie ihn aufschreiben müssen).

(Anscheinend habe ich gestern nicht auf die Schaltfläche "Kommentar" geklickt, daher wird nach "Ich habe ein paar Änderungen vorgenommen" bei 1910Z nichts bestätigt.)

Die wahre Tag-Größe müsste variabel sein. Einverstanden.

Betrachten wir zunächst nur die Verschlüsselung, um den Anwendungsfall zu vereinfachen. Sie haben Recht, dass einige Chiffren nichts, weniger oder mehr zurückgeben. Es gibt eine allgemeine Frage, was passiert, wenn Sie keinen ausreichend großen Puffer bereitstellen.

Bei den neuen TextEncoding-Schnittstellen, die span verwenden, gab es einen Vorschlag, den Rückgabetyp eine Aufzählung zu haben, um zu definieren, ob genügend Platz für die Ausgabe vorhanden ist oder nicht, und die Größe stattdessen tatsächlich in einen "out" -Parameter zu schreiben. Dies ist eine Möglichkeit.

Im CCM-Fall würde ich einfach sagen, dass es nichts zurückgibt und intern puffern muss, bis Sie finish aufrufen, an welchem ​​​​Punkt es das ganze Los ausgeben möchte. Nichts hindert Sie daran, nur finish als Ihren ersten Aufruf aufzurufen, wenn Sie alle Daten in einem einzigen Block haben (in diesem Fall gibt es möglicherweise einen besseren Namen). Oder es ist möglich zu werfen, wenn Sie versuchen, diese Chiffren zu aktualisieren. CNG gibt einen Fehler wegen ungültiger Größe zurück, wenn Sie beispielsweise versuchen, eine Fortsetzung auf CCM durchzuführen.

Wann das Tag auf Entschlüsselung gesetzt ist, wissen Sie oft erst, wenn Sie das gesamte Paket gelesen haben. Wenn wir TLS als Beispiel nehmen, müssen wir möglicherweise 8 * 2k-Netzwerkpakete lesen, um am Ende zum Tag zu gelangen eines 16k-Blocks. Wir müssen also jetzt die gesamten 16 KB puffern, bevor wir mit der Entschlüsselung beginnen können, und es besteht keine Möglichkeit einer Überlappung (ich sage nicht, dass dies für TLS verwendet würde, nur dass ein IO-gebundener Prozess für diese Arten von Verschlüsselungen üblich ist, sei es Festplatte oder Netzwerk).

@Drawaes re. Aufgeteilte Streams und Puffergrenzen:
Du musst deine Schlachten auswählen. Sie werden nicht in der Lage sein, eine vereinheitlichende API zu erstellen, die auf jedes einzelne Nice-to-have-Ziel in der AE-Welt ausgerichtet ist – und es gibt viele solcher Ziele. Ex. Es gibt ein anständiges chunked AEAD-Streaming in Inferno , aber es ist keineswegs ein Standard, und ein solcher Standard existiert nicht. Auf einer höheren Ebene ist das Ziel „sichere Kanäle“ (siehe this , this , and this ).

Allerdings müssen wir vorerst kleiner denken. Chunking/Buffer-Limiting sind nicht einmal auf dem Radar der Standardisierungsbemühungen (Abschnitt „ AEADs mit großen Klartexten “).

Bei Verschlüsselungs-/Entschlüsselungsvorgängen geht es im Wesentlichen um Transformationen. Diese Transformationen erfordern Puffer und sind nicht vorhanden (Ausgabepuffer müssen größer sein als Eingabepuffer – zumindest für die Encrypt-Transformation).

RFC 5116 könnte auch von Interesse sein.

@Drawaes , interessant, dass Sie TLS ansprechen. Ich würde argumentieren, dass SSLStream (wenn es diese API verwenden würde) keine nicht authentifizierten Ergebnisse an eine Anwendung zurückgeben darf, da die Anwendung keine Möglichkeit hat, sich selbst zu verteidigen.

Sicher, aber das ist ein SSLStreams-Problem. Ich habe genau dieses Ding (verwaltetes TLS auf Protokollebene, das CNG und OpenSSL für die Krypto-Bits aufruft) auf Pipelines prototypisiert. Die Logik war ziemlich einfach, verschlüsselte Daten kommen herein, entschlüsseln den vorhandenen Puffer, hängen ihn an den Ausgang an und wiederholen, bis Sie zum Tag gelangen. Beim Tag-Call fertig...

Wenn es wirft, schließen Sie die Rohrleitung. Wenn es nicht wirft, dann spülen, damit die nächste Stufe entweder im selben Thread oder per Dispatch an die Arbeit gehen kann.

Mein Proof of Concept war noch nicht bereit für die Primetime, aber durch die Verwendung und das Vermeiden vieler Kopien usw. zeigte sich eine sehr anständige Leistungssteigerung;)

Das Problem bei jedem Netzwerk besteht darin, dass Dinge in der Pipeline beginnen, ihre eigenen Puffer zuzuweisen und nicht so viel wie möglich von denen zu verwenden, die sich bereits durch das System bewegen.

Die Krypta von OpenSsl und CNG haben dieselbe Methode Update, Update, Finish. Finish kann das Tag wie besprochen ausgeben. Aktualisierungen müssen in Blockgröße erfolgen (für CNG) und für OpenSsl wird nur minimal gepuffert, um eine Blockgröße zu erreichen.

Da es sich um Primitive handelt, bin ich mir nicht sicher, ob wir von ihnen eine höhere Funktionalität erwarten würden. Wenn wir eher eine API auf Benutzerebene als Primitive entwerfen würden, um diese zu konstruieren, würde ich argumentieren, dass Schlüsselgenerierung, IV-Konstruktion und ganze authentifizierte Blöcke alle implementiert werden sollten, also denke ich, dass es darauf ankommt, was die Zielebene dieser API wirklich ist.

Falscher Knopf

@blowdart , der einige interessante Ideen zum Nonce-Management hatte.

Die Nonce-Verwaltung ist also im Grunde ein Benutzerproblem und spezifisch für ihre Einrichtung.

Also ... machen Sie es zu einer Anforderung. Sie müssen die Nonce-Verwaltung einbinden ... und keine Standardimplementierung oder überhaupt Implementierungen bereitstellen. Dies ist eher als eine einfache

cipher.Init(myKey, nonce);

zwingt Benutzer, eine bestimmte Geste zu machen, damit sie die Risiken verstehen.

Die Idee von @blowdart könnte sowohl bei Nonce-Verwaltungsproblemen als auch bei Unterschieden zwischen Algorithmen helfen. Ich stimme zu, dass es wahrscheinlich wichtig ist, keine integrierte Implementierung zu haben, um sicherzustellen, dass Benutzer verstehen, dass die Nonce-Verwaltung ein Problem ist, das sie lösen müssen. Wie sieht so etwas aus?

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
}

Aber was ist der Sinn des INonceProvider? Es ist nur eine zusätzliche Schnittstelle/ein zusätzlicher Typ, wenn die Init nur keine nimmt und aufgerufen werden muss, bevor Sie einen Block starten, ist das nicht dasselbe ohne eine zusätzliche/Schnittstelle?

Ich bin auch kein Krypto-Experte, aber AES erfordert keine IV (die keine Nonce ist, sondern vom Benutzer bereitgestellt werden muss?)

Es ist nur eine zusätzliche Schnittstelle/Typ

Das ist sozusagen der Punkt. Es besagt im Wesentlichen, dass _Nonce-Management_ ein Problem ist und nicht nur ein auf Null gesetztes oder sogar zufälliges Byte-Array übergeben wird. Es kann auch dazu beitragen, eine versehentliche Wiederverwendung zu verhindern, wenn Leute GetNextNonce so interpretieren, dass es bedeutet, "etwas anderes zurückzugeben als beim letzten Mal".

Es ist auch hilfreich, es nicht für Algorithmen zu benötigen, die keine Nonce-Verwaltungsprobleme haben (wie AES SIV oder vielleicht AES+CBC+HMAC).

Die genauen IV/Nonce-Anforderungen variieren je nach Modus. Beispielsweise:

  • AES ECB erfordert keine Nonce oder IV
  • AES GCM erfordert eine 96-Bit-Nonce, die niemals wiederverwendet werden darf, oder die Sicherheit des Schlüssels bricht. Niedrige Entropie ist in Ordnung, solange die Nonce nicht wiederverwendet wird.
  • AES CBC erfordert eine 128-Bit-IV, die zufällig sein muss. Wiederholt sich der IV, verrät er nur, ob die gleiche Nachricht schon einmal gesendet wurde.
  • AES SIV benötigt keinen expliziten IV, da es ihn von anderen Eingaben ableitet.

AES CBC braucht IV richtig? Werden Sie also einen InitializationVectorProvider haben? Es ist nicht eine einmalige aber
nonce like und die Wiederverwendung des letzten Blocks führten zu einem tls-Angriff, da der iv vorhergesagt werden kann. Sie können explizit keine sequenzielle Nonce für CBC verwenden.

Ja, aber eine IV ist keine Nonce, also können Sie den Begriff Nonce-Anbieter nicht haben

Ich wollte nicht andeuten, dass AES CBC keine IV benötigt - das tut es. Ich wollte nur über einige Schemata spekulieren, die die IV aus anderen Daten ableiten.

Sicher, ich denke, mein Punkt ist, dass ich es im Allgemeinen mag ... Ich kann den Anbieter zusammenfassen;) aber nenne es entweder einen IV-Anbieter oder habe zwei Schnittstellen, um die Absicht klar zu machen

@morganbr INonceProvider Fabriken, die an Verschlüsselungskonstruktoren übergeben werden, sind ein schlechtes Design. Es übersieht völlig die Tatsache, dass _nonce_ nicht alleine existiert: Die Einschränkung "_...used once_" hat einen _context_. In Fällen von CTR- und GCM-Modi (die CTR verwenden) ist der _Kontext_ der _Nonce_ der _Schlüssel_. Dh. _Nonce-Anbieter_ muss eine Nonce zurückgeben, die nur einmal innerhalb eines Kontexts eines bestimmten _Schlüssels_ verwendet wird.

Da das INonceProvider in Ihrer vorgeschlagenen API nicht schlüsselbewusst ist, kann es keine korrekten Nonces generieren (außer über Zufälligkeit, was keine Nonce ist, selbst wenn der Bitraum groß genug war, damit die statistische Zufälligkeit funktioniert sicher).

Ich bin mir nicht ganz sicher, was dieser Diskussionsthread erreichen soll. Verschiedene Designideen für die authentifizierte Verschlüsselung werden diskutiert ... ok. Was ist mit Schnittstellen mit authentifizierter Verschlüsselung, die bereits in .NET Core integriert sind – insbesondere in der ASP.NET-API? Es gibt IAuthenticatedEncryptor usw. Alle diese Funktionen sind bereits implementiert, erweiterbar und werden heute als Teil von .NET Core ausgeliefert. Ich sage nicht, dass DataProtection-Krypto perfekt ist, aber ist es geplant, sie zu ignorieren? Ändere sie? Assimilation oder Refactoring?

DataProtection-Krypto wurde von @GrabYourPitchforks (Levi Broderick) entwickelt. Er kennt das Thema und seine Meinung/Eingabe/Feedback wäre für diese Community sehr wertvoll. Ich genieße Krypto-Themen-Unterhaltung so sehr wie jeder andere, aber wenn sich jemand ernsthaft mit dem Design von Krypto-APIs befassen möchte, sollten echte Experten, die bereits im MS-Team sind, engagiert werden.

@sdrapkin , Nonce-Anbieter, die schlüsselbewusst sein müssen, sind ein guter Punkt. Ich frage mich, ob es eine vernünftige Möglichkeit gibt, diese APIs zu ändern, um dies zu erzwingen.

DataProtection ist eine gute API, aber ein Konstrukt auf höherer Ebene. Es kapselt die Schlüsselgenerierung und -verwaltung, IVs und das Ausgabeprotokoll. Wenn jemand (sagen wir) GCM in einer Implementierung eines anderen Protokolls verwenden muss, macht DataProtection dies nicht möglich.

Zum .NET-Crypto-Team gehören @bartonjs , @blowdart und ich selbst. Wenn sich @GrabYourPitchforks einschalten möchte, ist er natürlich mehr als willkommen.

Ich stimme @morganbr darin zu, dass dies ein primitives Element auf niedriger Ebene sein soll (tatsächlich steht das im Titel). Während Datenschutz usw. dafür ausgelegt sind, direkt im Benutzercode verwendet zu werden und die Fähigkeit, sich selbst in den Fuß zu schießen, verringert, sehe ich dieses Primitiv so, dass es dem Framework und den Bibliotheken ermöglicht wird, Konstrukte auf höherer Ebene auf einer gemeinsamen Basis zu erstellen.

In Anbetracht dieses Gedankens ist es für den Anbieter in Ordnung, wenn er immer geliefert werden muss, wenn ein Schlüssel geliefert wird. Es macht es ein wenig chaotisch, lassen Sie mich die Verwendung von TLS erklären (es ist alles eine bekannte Verwendung von AES-Blockmodi für den Netzwerkverkehr).

Ich bekomme einen "Rahmen" (vielleicht über 2 + TU's mit der MTU ~1500 des Internets). Es enthält die Nonce (oder einen Teil der Nonce mit 4 Bytes links "versteckt"). Ich muss diesen Wert dann auf einem Shell-"Provider" festlegen und dann decrypt aufrufen und meinen Zyklus zum Entschlüsseln der Puffer durchlaufen, um einen einzelnen Klartext zu erhalten .

Wenn du damit zufrieden bist, kann ich damit leben. Ich bin sehr daran interessiert, dies voranzubringen, also möchte ich das obige Design auf etwas aktualisieren, auf das wir uns einigen können.

Danke, dass Sie die Diskussion gegabelt haben und etwas freie Zeit bekommen haben, um darauf zu springen. @Drawaes , kannst du den obersten Beitrag als Goldstandard/Ziel dieser sich entwickelnden Konversation bestätigen/aktualisieren? Wenn nicht, können Sie es aktualisieren?

Ich sehe, dass der aktuelle Vorschlag ein fatales Problem hat und dann andere Probleme, weil er zu gesprächig ist.

// 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);
}

Wenn Sie sich ein echtes AEAD-Grundelement ansehen, sind die Datenschutzdaten und die authentifizierten Daten im Lock-Step gemischt. Siehe dies für Auth Data 1 und CipherText1. Dies setzt sich natürlich für mehrere Blöcke fort, nicht nur für 1.highlighted

Da die ganze Welt ein Meme ist, kann ich nicht widerstehen, sorry :)
Can't resist

Außerdem scheint die API mit Neu, Init, Update usw. gesprächig zu sein. Ich würde das Modell dieses Programmierers vorschlagen

// 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. Typischerweise AAD << Klartext, also habe ich cipher.Init(mykey, nonce, aad); gesehen, wo das gesamte AAD als Puffer übergeben wird und dann die Chiffre über den Rest des potenziell Gigabyte+ Streams knirscht. (zB CipherModeInfo param von BCryptEncrypts ). Außerdem legt die Größe von myKey bereits AES128, 192, 256 fest, es ist kein weiterer Parameter erforderlich.
  2. Init wird zu einer optionalen API, falls der Aufrufer die vorhandene Klasse, vorhandene AES-Konstanten wiederverwenden und die AES-Unterschlüsselgenerierung überspringen möchte, wenn der AES-Schlüssel gleich ist
  3. Die API von cipher sollte den Aufrufer vor Interna der Blockgrößenverwaltung schützen, wie die meisten anderen Krypto-APIs oder sogar vorhandene .NET-APIs. Anrufer besorgt über Puffergröße, die für seinen Anwendungsfall optimiert ist (z. B. Netzwerk-E/A über 16K+ Puffer). Demoing mit einer Primzahl > 16K zum Testen von Implementierungsannahmen
  4. inputSpan ist schreibgeschützt. Und eingeben. Brauchen Sie also eine OutSpan
  5. Update() verschlüsselt oder entschlüsselt? Haben Sie einfach Encrypt- und Decrypt-Schnittstellen, die dem mentalen Modell eines Entwicklers entsprechen. tag sind in diesem Moment auch die wichtigsten gewünschten Daten, geben Sie das zurück.

Eigentlich einen Schritt weiter gehen, warum nicht einfach

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

Bitte halten Sie sich auch von INonceProvider und dergleichen fern. Die Krypto-Primitive brauchen das nicht, bleiben Sie einfach bei byte[] iv (mein Favorit für kleine Daten) oder Span (das angeblich neue coole, aber meiner Meinung nach zu viel Abstraktion). Der Nonce-Anbieter arbeitet auf einer darüber liegenden Ebene, und das Ergebnis davon könnten nur die iv sein, die hier zu sehen sind.

Das Problem dabei, Primitive so primitiv zu machen, ist, dass die Leute sie einfach falsch verwenden. Mit einem Anbieter können wir zumindest einige Gedanken in deren Einsatz zwingen.

Wir sprechen über AEAD im Allgemeinen, für das GCM spezifisch ist. Also sollte zuerst der verallgemeinerte Fall ( iv ) das Design steuern, nicht der spezifische Fall ( nonce ).

Zweitens, wie löst der bloße Wechsel von byte[] iv zu GetNextNonce(Span<byte> writeNonceHere) tatsächlich das Nonce-Problem? Sie haben nur den Namen/die Bezeichnung des Problems geändert und es gleichzeitig komplexer gemacht, als es sein sollte.

Drittens, da wir uns mit Richtlinien zum iv -Schutz befassen, sollten wir uns auch mit wichtigen Schutzrichtlinien befassen? Was ist mit Schlüsselverteilungsrichtlinien? Das sind offensichtlich Bedenken auf höherer Ebene.

Schließlich ist Nonce bei der Verwendung in höheren Schichten äußerst situativ. Sie möchten keine spröde Architektur haben, in der schichtübergreifende Belange miteinander gekoppelt werden.

Ehrlich gesagt, wenn wir Primitive verstecken könnten, es sei denn, jemand macht eine Geste, um zu sagen, dass ich weiß, was ich tue, würde ich darauf drängen. Aber wir können nicht. Es gibt viel zu viele schlechte Krypto-Implementierungen da draußen, weil die Leute denken "Oh, das ist verfügbar, ich werde es verwenden". Schauen Sie sich AES selbst an, ich verwende das einfach ohne HMAC.

Ich möchte, dass APIs standardmäßig sicher sind, und wenn das etwas mehr Schmerz bedeutet, dann bin ich offen gesagt dafür. 99 % der Entwickler wissen nicht, was sie tun, wenn es um Krypto geht, und es für die 1 %, die es tun, einfacher zu machen, sollte eine geringere Priorität haben.

Spannweite existiert nicht. Es ist mir egal, was kommt oder nicht kommt – es ist nicht in NetStandard2 enthalten

@sdrapkin wie @Drawaes darauf hinweist, dass Span<T> .NET Standard 1.0 ist und daher in jedem Framework verwendet werden kann. Es ist auch sicherer als ArraySegment<T> , da Sie nur auf das eigentliche Fenster zugreifen können, auf das verwiesen wird; eher als das ganze Array.

Außerdem verhindert ReadOnlySpan<T> die Änderung dieses Fensters; wiederum im Gegensatz zu Array-Segmenten, bei denen alles, was übergeben wird, einen Verweis auf das übergebene Array ändern und/oder beibehalten kann.

Span sollte die allgemeine Anlaufstelle für Synchronisierungs-APIs sein (Die Tatsache, dass eine API, die Span verwendet, auch mit Stackalloc, nativem Speicher sowie Arrays umgehen kann; ist das i-Tüpfelchen)

dh
Bei ArraySegment wird das Readonly über docs vorgeschlagen; und es werden keine Lesevorgänge/Änderungen außerhalb der Grenzen verhindert

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
    );

Bei Span wird Readonly jedoch von API erzwungen; sowie Lesevorgänge außerhalb der Grenzen der Arrays werden verhindert

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
    );

Es vermittelt die Absicht mit den Parametern viel besser; und ist weniger fehleranfällig in Bezug auf Lese-/Schreibvorgänge außerhalb der Grenzen.

@benaadams @Drawaes hat nie gesagt, dass Span<T> in NetStandard war ( jeder ausgelieferte NetStandard ). Was er sagte, ist (1) stimme zu, dass Span<T> in keinem ausgelieferten NetStandard enthalten ist; (2) dass Span<T> _"um den 2.1-Zeitrahmen versandt werden"_.

Für dieses spezielle Github-Problem wird derzeit jedoch (nur zum Lesen) Span<T> diskutiert - es gibt keine Klarheit über den Umfang oder Zweck der zu entwerfenden API.

Entweder wir gehen mit roher primitiver AEAD-API auf niedriger Ebene (z. B. ähnlich wie CAESAR):

  • Vorteile: Gute Passform für AES-GCM/CCM, vorhandene Testvektoren aus guten Quellen (NIST, RFC). @sidshetye wird sich freuen. @blowdart wird darüber meditieren, _"Primitive so primitiv zu machen"_, aber irgendwann das Yin und Yang sehen, weil Primitive primitiv sind und es keine Möglichkeit gibt, sie kindersicher zu machen.
  • Nachteile: Erfahrene Benutzer (die sprichwörtlichen 1 %) werden die Low-Level-APIs verantwortungsbewusst verwenden, während die anderen nicht erfahrenen Benutzer (99 %) sie missbrauchen werden, um defekte .NET-Software zu schreiben, die für die überwiegende Mehrheit von .NET verantwortlich sein wird CVEs, die stark zur Wahrnehmung beitragen werden, dass .NET eine unsichere Plattform ist.

Oder wir entscheiden uns für eine missbrauchsunmögliche oder -resistente AEAD-API auf hohem Niveau:

  • Vorteile: 99 % der Laien werden weiterhin Fehler machen, aber zumindest nicht im AEAD-Code. @blowdarts _„Ich möchte, dass APIs standardmäßig sicher sind“_ schwingt tief im Ökosystem mit, und Sicherheit, Wohlstand und gutes Karma widerfahren allen. Viele gute API-Designs und -Implementierungen sind bereits verfügbar.
  • Nachteile: Keine Standards. Keine Testvektoren. Kein Konsens darüber, ob AEAD überhaupt das richtige Ziel für eine High-Level-Online-Streaming-API ist (Spoiler: ist es nicht – siehe Rogaways Artikel).

Oder wir machen beides. Oder wir treten in eine Analyse-Paralyse ein und können dieses Thema genauso gut gleich schließen.

Ich bin der festen Überzeugung, dass Krypto als Teil der Kernsprache eine solide, grundlegende API auf niedriger Ebene haben muss. Sobald Sie das haben, kann das Erstellen von High-Level-APIs oder „Stützrad“-APIs schnell vom Kern oder der Community überbrückt werden. Aber ich fordere jeden auf, elegant das Gegenteil zu tun. Außerdem ist das Thema " Allgemeine Low-Level -Primitive für Chiffren "!

@Drawaes gibt es einen Zeitplan, um dies zu konvergieren und zu lösen? Gibt es Pläne, Nicht-Microsoft-Leute über solche GitHub-Warnungen hinaus einzubeziehen? Wie eine 30-minütige Telefonkonferenz? Ich versuche, mich aus einem Kaninchenbau herauszuhalten, aber wir wetten, dass .NET-Core-Krypto einen bestimmten Grad an Reife und Stabilität erreichen wird.

Wir sind immer noch aufmerksam und arbeiten daran. Wir haben uns mit dem Microsoft Cryptography Board (der Gruppe von Forschern und anderen Experten, die Microsofts Verwendung von Kryptographie beraten) getroffen, und @bartonjs wird bald weitere Informationen zu teilen haben.

Basierend auf ein wenig Datenflusskritzel und den Ratschlägen des Crypto Board sind wir zu folgendem gekommen. Unser Modell war GCM, CCM, SIV und CBC+HMAC (beachten Sie, dass wir im Moment nicht über SIV oder CBC+HMAC sprechen, sondern nur, dass wir die Form beweisen wollten).

```C#
öffentliche Schnittstelle INonceProvider
{
ReadOnlySpanGetNextNonce(int nonceSize);
}

öffentliche abstrakte Klasse AuthenticatedEncryptor : IDisposable
{
public int NonceOrIVSizeInBits { erhalten; }
public int TagSizeInBits { erhalten; }
public bool SupportsAssociatedData { get; }
öffentliche ReadOnlySpanLastNonceOrIV { erhalten; }
öffentliche ReadOnlySpanLastTag { erhalten; }

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.

}

öffentlich versiegelte Klasse AesGcmEncryptor : AuthenticatedEncryptor
{
öffentlich AesGcmEncryptor(ReadOnlySpankeySize, INonceProvider, nonceProvider)
: basis(128, wahr, 96)
{
}
}

öffentlich versiegelte Klasse AesCcmEncryptor : AuthenticatedEncryptor
{
öffentlicher AesCcmEncryptor(
ReadOnlySpanSchlüssel,
int nonceSizeInBits,
INonceProvider nonceProvider,
int tagSizeInBits)
: base(tagSizeInBits, wahr, nonceSizeInBits)
{
validiere nonceSize und tagSize anhand der Algorithmusspezifikation;
}
}

öffentliche abstrakte Klasse AuthenticatedDecryptor : IDisposable
{
public abstract bool TryDecrypt(
ReadOnlySpanSchild,
ReadOnlySpannonceOrIV,
ReadOnlySpanverschlüsselte Daten,
ReadOnlySpanzugehörigeDaten,
SpanneDaten,
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.

}

öffentlich versiegelte Klasse AesGcmDecryptor : AuthenticatedDecryptor
{
public AesGcmDecryptor(ReadOnlySpankey) => null werfen;
}

öffentlich versiegelte Klasse AesCcmDecryptor : AuthenticatedDecryptor
{
public AesCcmDecryptor(ReadOnlySpankey) => null werfen;
}
```

Dieser Vorschlag eliminiert Datenstreaming. Da haben wir nicht wirklich viel Spielraum. Der reale Bedarf (niedrig) in Kombination mit den damit verbundenen Risiken (extrem hoch für GCM) oder deren Unmöglichkeit (CCM) bedeutet, dass es einfach weg ist.

Dieser Vorschlag verwendet eine externe Nonce-Quelle für die Verschlüsselung. Wir werden keine öffentlichen Implementierungen dieser Schnittstelle haben. Jede Anwendung/jedes Protokoll sollte den Schlüssel an den Kontext binden, damit es die Dinge entsprechend einspeisen kann. Während jeder Aufruf von TryEncrypt nur einen Aufruf von GetNextNonce macht, gibt es keine Garantie dafür, dass dieser bestimmte TryEncrypt erfolgreich sein wird, also ist es immer noch Sache der Anwendung zu verstehen, ob das bedeutet, dass sie die Nonce erneut versuchen sollte. Für CBC+HMAC würden wir eine neue Schnittstelle, IIVProvider, erstellen, um eine Verunreinigung der Terminologie zu vermeiden. Für SIV wird die IV konstruiert, also gibt es keinen akzeptablen Parameter; und basierend auf der Spezifikation scheint die Nonce (wenn verwendet) nur als Teil der AssociatedData betrachtet zu werden. Daher schlägt zumindest SIV vor, dass nonceOrIV als Parameter für TryEncrypt nicht allgemein anwendbar ist.

TryDecrypt löst definitiv ein ungültiges Tag aus. Es gibt nur false zurück, wenn das Ziel zu klein ist (gemäß den Regeln von Try-Methoden).

Dinge, die definitiv offen für Feedback sind:

  • Sollten Größen in Bits (wie signifikante Teile der Spezifikationen) oder Bytes sein (da nur %8-Werte sowieso zulässig sind und wir immer dividieren werden, und einige Teile der Spezifikationen über Dinge wie die Nonce-Größe in Bytes sprechen )?
  • Parameternamen und Reihenfolge.
  • Die LastTag/LastNonceOrIV-Eigenschaften. (Sie zu (beschreibbaren) Spans auf einem öffentlichen TryEncrypt zu machen, bedeutet nur, dass es drei Puffer gibt, die zu klein sein könnten. Wenn Sie sie auf der Seite sitzen lassen, ist das "Try" klarer; die Basisklasse kann das Versprechen geben, dass dies der Fall ist Bieten Sie niemals einen zu kurzen Puffer an.).
  • Einen AE-Algorithmus anbieten, für den dies nicht funktioniert.
  • Soll associatedData mit einem Standardwert von ReadOnlySpan<byte>.Empty ans Ende verschoben werden?

    • Oder Überladungen gemacht, die es weglassen?

  • Möchte irgendjemand Liebe oder Hass auf die byte[] -Rückkehrmethoden geltend machen? (Low Allocation kann mit der Span-Methode erreicht werden, dies dient nur der Bequemlichkeit)
  • Die Größenbereichsmethoden wurden am Ende irgendwie angeschraubt.

    • Ihr Zweck ist das

    • Wenn die Zielspanne kleiner als min ist, wird sofort false zurückgegeben.

    • Die byte[] -Rückgabemethoden weisen einen Puffer von max zu und Array.Resize ihn dann nach Bedarf.

    • Ja, für GCM und CCM min=max=input.Length , aber das gilt nicht für CBC+HMAC oder SIV

    • Gibt es einen Algorithmus, der die Länge der zugehörigen Daten berücksichtigen müsste?

Auf jeden Fall Bytes - keine Bits.
Ein Nonce-Anbieter, der nicht schlüsselbewusst ist, ist ein großer Fehler.

Ein Nonce-Anbieter, der nicht schlüsselbewusst ist, ist ein großer Fehler.

Sie können Ihren Nonce-Anbieter schreiben, wie Sie möchten. Wir stellen keine zur Verfügung.

Was ist mit der deterministischen Bereinigung/ IDisposable ?

Was ist mit deterministischer Bereinigung/IDisposable?

Guter Anruf. Es wurde zu AuthenticatedEncryptor/AuthenticatedDecryptor hinzugefügt. Ich denke nicht, dass sie die Verfügbarkeit des Nonce-Anbieters untersuchen sollten, der Aufrufer kann einfach die using-Anweisungen stapeln.

INonceProvider Konzept/Zweck ergibt für mich keinen Sinn (wie andere). Lassen Sie Primitiven primitiv sein - übergeben Sie die Nonce auf die gleiche Weise, wie Sie den Schlüssel übergeben (dh als Bytes - wie auch immer deklariert). Keine AE/AEAD-Spezifikation erzwingt einen Algorithmus dafür, wie Nonces generiert/abgeleitet werden – dies ist eine Verantwortung auf höherer Ebene (zumindest im Let-Primitives-be-Primitive-Modell).

Kein Streaming? Wirklich? Was ist die Rechtfertigung, das Streaming zwangsweise aus einer Stream-Chiffre wie AES-GCM auf einer grundlegenden Kernebene zu entfernen?

Was empfiehlt Ihr Krypto-Board beispielsweise für diese beiden aktuellen Szenarien, die wir überprüft haben?

  1. Der Kunde hat große Gesundheitsdateien zwischen 10-30 GB. Der Kern sieht jedoch nur einen Datenstrom zwischen zwei Maschinen, es handelt sich also um einen One-Pass-Stream. Offensichtlich wird für jede 10-GB-Datei ein neuer Schlüssel ausgegeben, aber Sie haben gerade jeden solchen Workflow unbrauchbar gemacht. Sie möchten jetzt, dass wir a) diese Daten puffern (Speicher, kein Pipe-Lining) b) eine Verschlüsselung durchführen (alle Maschinen in der Pipeline sind jetzt im Leerlauf!) c) die Daten ausschreiben (das erste nach a und b geschriebene Byte ist 100% fertig) ? Bitte sag mir, dass du Witze machst. Ihr Jungs bringt bewusst „Verschlüsselung ist eine Last“ wieder ins Spiel.

  2. Die physische Sicherheitseinheit verfügt über mehrere 4K-Streams, die auch für Ruheszenarien verschlüsselt sind. Die Ausgabe neuer Schlüssel erfolgt an der 15-GB-Grenze. Sie schlagen vor, den gesamten Clip zu puffern?

Ich sehe keinen Beitrag von der Community, von Leuten, die tatsächlich reale Software entwickeln und darum bitten, die Streaming-Unterstützung zu entfernen. Aber dann verschwindet das Team aus dem Community-Dialog, drängt sich intern zusammen und kommt dann mit etwas zurück, wonach niemand gefragt hat, etwas, das echte Anwendungen tötet und bekräftigt, dass „Verschlüsselung langsam und teuer ist, überspringen Sie es?“.

Sie können Encrypt und EncryptFinal bereitstellen, die beide Optionen unterstützen würden, anstatt Ihre Entscheidung für das gesamte Ökosystem aufzuzwingen.

Elegantes Design eliminiert Komplexität, nicht Kontrolle.

Was ist die Rechtfertigung, das Streaming zwangsweise aus einer Stream-Chiffre wie AES-GCM auf einer grundlegenden Kernebene zu entfernen?

Ich denke, es war so etwas wie

Dieser Vorschlag eliminiert Datenstreaming. Da haben wir nicht wirklich viel Spielraum. Der reale Bedarf (niedrig) in Kombination mit den damit verbundenen Risiken (extrem hoch für GCM) oder deren Unmöglichkeit (CCM) bedeutet, dass es einfach weg ist.

GCM hat zu viele Hoppla-Momente, in denen es die Schlüsselwiederherstellung zulässt. Wenn ein Angreifer einen ausgewählten Chiffretext erstellen und die Streaming-Ausgabe vor der Tag-Verifizierung ansehen kann, kann er den Schlüssel wiederherstellen. (Das sagt mir jedenfalls einer der Kryptoanalytiker). Wenn GCM-verarbeitete Daten zu irgendeinem Zeitpunkt vor der Tag-Verifizierung beobachtbar sind, ist der Schlüssel effektiv kompromittiert.

Ich bin mir ziemlich sicher, dass das Crypto Board empfehlen würde, für das erste Szenario NICHT GCM zu verwenden, sondern CBC+HMAC.

Wenn Ihr zweites Szenario 4k-Framing ist und Sie jeden 4k-Frame verschlüsseln, funktioniert das mit diesem Modell. Jeder 4k + Nonce + Tag-Frame wird entschlüsselt und verifiziert, bevor Sie die Bytes zurückerhalten, sodass Sie niemals den Schlüsselstrom / Schlüssel verlieren.

Zum Vergleich: Ich entwickle gerade diese Krypto-API „Let primitives be primitive“. Hier ist meine Klasse für authentifizierte Verschlüsselung.

Für mich hat es sich als nützlich erwiesen, unabhängig von einem Schlüssel über ein Kryptoprimitiv sprechen zu können. Zum Beispiel möchte ich oft ein bestimmtes Primitiv in eine Methode stecken, die mit jedem AEAD-Algorithmus funktioniert, und die Generierung von Schlüsseln usw. dieser Methode überlassen. Daher gibt es eine AeadAlgorithm -Klasse und eine separate Key -Klasse.

Eine weitere sehr nützliche Sache, die bereits mehrere Fehler verhindert hat, ist die Verwendung unterschiedlicher Typen zur Darstellung von Daten unterschiedlicher Form, z. B. ein Schlüssel und eine Nonce , anstatt für alles ein einfaches byte[] oder Span<byte> zu verwenden.


AeadAlgorithm API (zum Erweitern klicken)

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 er / sie hat Recht, Sie müssen sich darauf verlassen, dass das Programm erst nach der Authentifizierung ausgibt. Wenn Sie sich beispielsweise nicht authentifizieren (oder noch nicht), können Sie die Eingabe für einen Block steuern und daher die Ausgabe kennen und von dort aus rückwärts arbeiten ...

Beispielsweise kann ein Man-in-the-Middle-Angriff bekannte Blöcke in einen CBC-Stream injizieren und einen klassischen Bit-Flipping-Angriff ausführen.

Ich bin mir nicht sicher, wie ich das Problem mit den großen Datenblöcken wirklich lösen soll, indem ich sie mit seriellen Nonces oder ähnlichem chunke ... ala TLS.

Nun, lassen Sie mich umformulieren, dass ich das mache, aber nur im Fall kleiner Netzwerkgrößen, was für eine Allzweckbibliothek nicht ausreicht.

Ist es im Sinne der Offenheit möglich, offenzulegen, wer im Microsoft Cryptography Review Board sitzt (und idealerweise die Kommentare/Meinungen bestimmter Mitglieder, die dieses Thema überprüft haben)? Brian LaMacchia und wer noch?

_mit umgekehrter Psychologie:_

Ich bin froh , dass das Streaming von AEAD draußen ist. Das bedeutet, dass Inferno weiterhin das einzig praktikable CryptoStream -basierte Streaming-AEAD für den durchschnittlichen Joe ist. Vielen Dank an das MS Crypto Review Board!

Aufbauend auf dem Kommentar von @ektrah basiert sein (ihr?) Ansatz auf RFC 5116 , auf das ich bereits verwiesen habe. Es gibt viele bemerkenswerte Zitate in RFC 5116:

3.1. Anforderungen an die Nonce-Generierung
Aus Sicherheitsgründen ist es wesentlich, dass die Nonces in einer Weise konstruiert werden, die die Anforderung respektiert, dass jeder Nonce-Wert für jeden Aufruf der authentifizierten Verschlüsselungsoperation für jeden festen Wert des Schlüssels unterschiedlich ist.
...

  1. Anforderungen an AEAD-Algorithmusspezifikationen
    Jeder AEAD-Algorithmus MUSS jede Nonce mit einer Länge zwischen einschließlich N_MIN und N_MAX Oktetts akzeptieren, wobei die Werte von N_MIN und N_MAX für diesen Algorithmus spezifisch sind. Die Werte von N_MAX und N_MIN KÖNNEN gleich sein. Jeder Algorithmus SOLLTE eine Nonce mit einer Länge von zwölf (12) Oktetts akzeptieren. Randomisierte oder zustandsbehaftete Algorithmen, die unten beschrieben werden, KÖNNEN einen N_MAX-Wert von Null haben.
    ...
    Ein Authentifizierter Verschlüsselungsalgorithmus KANN eine Zufallsquelle enthalten oder verwenden, zB für die Erzeugung eines internen Initialisierungsvektors, der in die Chiffretextausgabe integriert wird. Ein solcher AEAD-Algorithmus wird randomisiert genannt; Beachten Sie jedoch, dass nur die Verschlüsselung zufällig und die Entschlüsselung immer deterministisch ist. Ein randomisierter Algorithmus KANN einen Wert von N_MAX haben, der gleich Null ist.

Ein Authentifizierter Verschlüsselungsalgorithmus KANN interne Zustandsinformationen enthalten, die zwischen Aufrufen der Verschlüsselungsoperation beibehalten werden, zB um die Konstruktion unterschiedlicher Werte zu ermöglichen, die als interne Nonces durch den Algorithmus verwendet werden. Ein solcher AEAD-Algorithmus wird Stateful genannt. Dieses Verfahren könnte von einem Algorithmus verwendet werden, um eine gute Sicherheit bereitzustellen, selbst wenn die Anwendung Nonces der Länge Null eingibt. Ein zustandsbehafteter Algorithmus kann einen Wert von N_MAX haben, der gleich Null ist.

Eine Idee, die es möglicherweise wert ist, erkundet zu werden, ist das Übergeben von Nonce mit Nulllänge/Null, was sogar der Standard sein könnte. Das Übergeben eines solchen "speziellen" Nonce-Werts wird den tatsächlichen Nonce-Wert randomisieren, der als Ausgabe von Encrypt verfügbar sein wird.

Wenn INonceProvider aus "Gründen" bleibt, besteht eine andere Idee darin, einen Reset() -Aufruf hinzuzufügen, der jedes Mal ausgelöst wird, wenn AuthenticatedEncryptor neu eingegeben wird. Wenn andererseits geplant ist, AuthenticatedEncryptor -Instanzen niemals neu zu verschlüsseln, wird dies GC zerstören, wenn wir eine Streaming-Chunk-Verschlüsselungs-API (z. B. Chunk = Netzwerkpaket) erstellen möchten, und jeder Chunk muss es sein mit einem anderen Schlüssel verschlüsselt (z. B. Netflix MSL -Protokoll, Inferno, andere). Insbesondere für parallele enc/dec-Vorgänge, bei denen wir einen Pool von AEAD-Engines unterhalten und Instanzen aus diesem Pool ausleihen möchten, um enc/dec auszuführen. Geben wir GC etwas Liebe :)

Aus meiner Sicht besteht der einzige Zweck von Kryptoprimitiven darin, gut gestaltete Sicherheitsprotokolle auf höherer Ebene zu implementieren. Jedes dieser Protokolle besteht darauf, Nonces auf seine eigene Weise zu erzeugen. Beispielsweise:

  • TLS 1.2 folgt den Empfehlungen von RFC 5116 und verkettet einen 4-Byte-IV mit einem 8-Byte-Zähler,
  • TLS 1.3 xor ist ein 8-Byte-Zähler, der mit einem 12-Byte-IV auf 12 Bytes aufgefüllt wird.
  • Noise verwendet einen 8-Byte-Zähler, der auf 12 Bytes in Big-Endian-Byte-Reihenfolge für AES-GCM aufgefüllt wird, und einen 8-Byte-Zähler, der auf 12 Bytes in Little-Endian-Byte-Reihenfolge für ChaCha/Poly aufgefüllt wird.

GCM ist viel zu spröde für randomisierte Nonces bei typischen Nonce-Größen (96 Bit). Und mir ist kein Sicherheitsprotokoll bekannt, das zufällige Nonces tatsächlich unterstützt.

Es besteht keine große Nachfrage nach weiteren APIs, die Krypto-Primitive bereitstellen. 99,9 % der Entwickler benötigen allgemeine Rezepte für sicherheitsrelevante Szenarien: Speichern eines Kennworts in einer Datenbank, Verschlüsseln einer Datei im Ruhezustand, sicheres Übertragen eines Software-Updates usw.

APIs für solche High-Level-Rezepte sind jedoch selten. Die einzigen verfügbaren APIs sind oft nur HTTPS und die Krypto-Primitiven, was Entwickler dazu zwingt, ihre eigenen Sicherheitsprotokolle zu entwickeln. Meiner Meinung nach besteht die Lösung darin, sich nicht viel Mühe zu geben, APIs für die Arbeit mit Primitiven zu entwerfen. Es sind APIs für High-Level-Rezepte.

Danke für das Feedback, alle! Einige Fragen:

  1. Während die Streaming-Entschlüsselung katastrophal fehlschlagen kann, könnte die Streaming-Verschlüsselung machbar sein. Klingt Streaming-Verschlüsselung (zusammen mit einer Nicht-Streaming-Option), aber nur Nicht-Streaming-Entschlüsselung nützlicher? Wenn ja, gibt es ein paar Probleme zu lösen:
    A. Einige Algorithmen (CCM, SIV) unterstützen kein Streaming. Sollten wir die Streaming-Verschlüsselung auf die Basisklasse setzen und gestreamte Eingaben puffern oder von den abgeleiteten Klassen werfen?
    B. Das Streamen von AAD ist aufgrund von Implementierungseinschränkungen wahrscheinlich nicht möglich, aber verschiedene Algorithmen benötigen es zu unterschiedlichen Zeiten (einige benötigen es am Anfang, andere nicht bis zum Ende). Sollten wir es im Voraus verlangen oder eine Methode zum Hinzufügen haben, die funktioniert, wenn die einzelnen Algorithmen dies zulassen?
  1. Wir sind offen für Verbesserungen an INonceProvider, solange der Punkt darin besteht, dass Benutzer Code schreiben müssen, der eine neue Nonce generiert. Hat jemand eine andere vorgeschlagene Form dafür?

1 . a = Ich denke, es könnte ein Problem sein, den Benutzer nicht frühzeitig zu warnen. Stellen Sie sich das Szenario von jemandem oben vor, eine 10-GB-Datei. Sie denken, dass sie Streaming bekommen, dann ändert irgendwann später ein anderer Entwickler die Chiffre und als nächstes puffert der Code 10 GB (oder versucht es), bevor er einen Wert zurückgibt.

  1. b = Wieder mit der "Streaming"- oder Netzwerkidee, z. B. AES GCM usw., Sie erhalten die AAD-Informationen erst am Ende zur Entschlüsselung. Was die Verschlüsselung betrifft, habe ich noch keinen Fall gesehen, in dem Sie die Daten nicht im Voraus haben. Ich würde also sagen, zumindest für die Verschlüsselung sollten Sie es am Anfang verlangen, die Entschlüsselung ist komplexer.
  1. Ich denke, es ist wirklich kein Problem, die "Bytes" für die Nonce über eine Schnittstelle oder einfach direkt bereitzustellen, ist weder hier noch dort. Sie können auf beide Arten dasselbe erreichen, ich finde es nur hässlicher für einen Primitiven, bin aber nicht vehement dagegen, wenn es Menschen nachts besser schlafen lässt. Ich würde dies einfach als erledigt abhaken und mit den anderen Themen fortfahren.

Apropos Beratungsprozess

@bartonjs : Wir könnten den ganzen Tag darüber streiten, ob Entscheidungen hinter verschlossenen Türen ohne Beteiligung der Gemeinschaft eine wirksame Rechtfertigung sind, aber wir werden vom Thema abschweifen, also lasse ich das sein. Plus ohne reichhaltigere Face-to-Face- oder Echtzeit-Kommunikation möchte ich dort niemanden verärgern.

Apropos Streaming

1. das Argument „Streaming impliziert keine AES-GCM-Sicherheit“.

Insbesondere Steaming => entschlüsselte Daten vor der Tag-Verifizierung an den Anrufer zurückgeben => keine Sicherheit. Das ist kein Ton. @bartonjs behauptet 'ausgewählter Chiffretext => Ausgabe beobachten => Schlüssel wiederherstellen', während @drawaes behauptet 'Eingabe für einen Block steuern => daher Ausgabe kennen => "von dort aus arbeiten" '

Nun, in AES-GCM ist das Einzige , was das Tag tut, die Integritätsprüfung (Manipulationsschutz). Es hat 0 Auswirkungen auf die Privatsphäre. Wenn Sie die GCM/GHASH-Tag-Verarbeitung aus AES-GCM entfernen, erhalten Sie einfach den AES-CTR-Modus. Es ist dieses Konstrukt, das den Datenschutzaspekt behandelt. Und CTR ist für Bit-Flips formbar, aber nicht auf die von Ihnen beiden behauptete Weise (Wiederherstellung von Schlüssel oder Klartext) "kaputt", da dies bedeuten würde, dass das grundlegende AES-Primitiv kompromittiert wird. Wenn Ihr Kryptoanalytiker (wer ist das?) etwas weiß, was der Rest von uns nicht weiß, sollte er/sie es veröffentlichen. Das einzige, was möglich ist, ist, dass ein Angegriffener Bit N umdrehen kann und weiß, dass Bit N des Klartextes umgedreht wurde - aber er weiß nie, was der eigentliche Klartext ist.

So

1) Klartext-Datenschutz wird immer erzwungen
2) Die Integritätsprüfung wird einfach verschoben (bis zum Ende des Streams) und
3) kein Schlüssel wird jemals kompromittiert.

Für Produkte und Systeme, bei denen Streaming grundlegend ist, können Sie jetzt zumindest einen Kompromiss entwickeln, bei dem man vorübergehend von AEAD auf reguläre AES-Verschlüsselung umsteigt und dann nach der Tag-Verifizierung wieder auf AEAD umsteigt. Das setzt mehrere innovative Konzepte frei, um die Sicherheit zu nutzen, anstatt zu sagen: „Sie wollen das alles puffern – sind Sie verrückt? Wir können keine Verschlüsselung machen!“.

Alles nur, weil Sie nur EncryptFinal implementieren möchten und nicht sowohl Encrypt als auch EncryptFinal (oder Äquivalente).

2. Nicht spezifisch für GCM!

Nun, AES-GCM ist kein magisches Biest, das „Ups-Momente“ in Hülle und Fülle hat. Es ist einfach AES-CTR + GHASH (eine Art Hash, wenn ich darf). Nonce-Überlegungen in Bezug auf den Datenschutz werden vom CTR-Modus geerbt, und Tag-Überlegungen in Bezug auf die Integrität stammen von den in der Spezifikation zulässigen variablen Tag-Größen. Dennoch ist AES-CTR + GHASH etwas wie AES-CBC + HMAC-SHA256 sehr ähnlich, da der erste Algorithmus den Datenschutz und der zweite die Integrität behandelt. In AES-CBC + HMAC-SHA256 werden Bit-Flips im Chiffretext den entsprechenden Block im entschlüsselten Text beschädigen (im Gegensatz zu CTR) UND auch Bits im folgenden entschlüsselten Klartextblock (wie CTR) deterministisch umkehren. Auch hier weiß ein Angreifer nicht, was der resultierende Klartext sein wird – nur, dass Bits umgedreht wurden (wie CTR). Schließlich wird die Integritätsprüfung (HMAC-SHA256) es abfangen. Aber nur das letzte Byte verarbeiten (wie GHASH).

Wenn also Ihr Argument, ALLE entschlüsselten Daten zurückzuhalten, bis die Integrität in Ordnung ist, wirklich gut ist, sollte es konsequent angewendet werden. Daher sollten ALLE Daten, die aus dem AES-CBC-Pfad kommen, auch gepuffert werden (intern von der Bibliothek), bis HMAC-SHA256 besteht. Das bedeutet im Grunde, dass auf .NET keine Streaming-Daten von AEAD-Fortschritten profitieren können. .NET erzwingt ein Downgrade von Streaming-Daten. Wählen Sie zwischen keiner Verschlüsselung oder normaler Verschlüsselung. Kein AEAD. Wo Pufferung technisch nicht praktikabel ist, sollten Architekten zumindest die Möglichkeit haben, Endbenutzer zu warnen, dass „Drohnenaufnahmen möglicherweise beschädigt sind“, anstatt „keine Augen für Sie“.

3. Es ist das Beste, was wir haben

Daten werden immer größer und die Sicherheit muss stärker sein. Streaming ist auch eine Realität, die Designer annehmen müssen. Bis die Welt einen wirklich integrierten AEAD-Algorithmus entwickelt, der nativ Manipulationen während des Streams erkennen kann, stecken wir mit Verschlüsselung + Authentifizierung als angeschraubte Kumpel fest. Echte AEAD-Primitive werden erforscht, aber wir haben vorerst nur Verschlüsselung + Authentifizierung.

Ich interessiere mich weniger für „AES-GCM“, als vielmehr für einen schnellen, beliebten AEAD-Algorithmus, der Streaming-Workloads unterstützen kann – was in einer datenreichen, hypervernetzten Welt sehr verbreitet ist.

4. Verwenden Sie AES-CBC-HMAC, Use (Workaround einfügen)

Das Crypto Board würde empfehlen, für das erste Szenario NICHT GCM zu verwenden, sondern CBC+HMAC.

Abgesehen von allem oben Erwähnten oder sogar den Besonderheiten des Szenarios – was darauf hindeutet, dass AES-CBC-HMAC nicht kostenlos ist. Es ist ~3x langsamer als AES-GCM, da die AES-CBC-Verschlüsselung nicht parallelisierbar ist und GHASH über die PCLMULQDQ-Anweisung beschleunigt werden kann. Wenn Sie also mit AES-GCM bei 1 GB/s sind, werden Sie jetzt mit AES-CBC-HMAC ~ 300 MB/s erreichen. Dies führt erneut zu der Denkweise „Krypto verlangsamt Sie, überspringen Sie es“ – eine Denkweise, gegen die Sicherheitsleute hart kämpfen.

Verschlüsselung jedes 4k-Frames

Video-Codecs sollten plötzlich Verschlüsselung tun? Oder muss die Verschlüsselungsschicht jetzt Video-Codecs verstehen? Es ist nur ein Bitstream auf der Datensicherheitsebene. Die Tatsache, dass es sich um Videos/genomische Daten/Bilder/proprietäre Formate usw. handelt, sollte kein Problem für die Sicherheitsebene sein. Eine Gesamtlösung sollte Kernaufgaben nicht vermischen.

Nonce

NIST erlaubt randomisierte IVs mit einer Länge von mehr als 96 Bit. Siehe Abschnitt 8.2.2 bei NIST 800-38D . Nichts Neues hier, Nonce-Anforderungen kommen aus dem CTR-Modus. Was bei den meisten Stream-Chiffren auch ziemlich Standard ist. Ich verstehe die plötzliche Angst vor Nonces nicht - es war schon immer number used once . Obwohl die INonce -Debatte für eine klobige Benutzeroberfläche sorgt, beseitigt sie zumindest nicht Innovationen wie die No-Stream-for-you-Auferlegung. Ich gebe jeden Tag INonce zu, wenn wir die AEAD-Sicherheits- und Streaming-Workload-Innovationen bekommen können. Ich hasse es, so etwas Einfaches wie Streaming als Innovation zu bezeichnen – aber ich fürchte, wir würden hier einen Rückschritt machen.

Ich würde mich gerne als falsch erweisen

Ich bin nur ein Typ, der nach einem langen Arbeitstag den Filmabend mit meinen Kindern aufgegeben hat, um das zu tippen. Ich bin müde und könnte mich irren. Aber führen Sie zumindest einen offenen faktenbasierten Community-Dialog statt Anekdoten oder "Ausschussgründe" oder irgendeinen anderen Voodoo. Meine Aufgabe ist es, sichere .NET- und Azure-Innovationen zu fördern. Ich denke, wir haben gemeinsame Ziele.

Apropos Community-Dialog ...

Können wir bitte einen Community-Skype-Anruf haben? Ein komplexes Thema wie dieses auszudrücken, sprengt eine riesige Textwand. Hübsch bitte?

Bitte führen Sie keine Skype-Anrufe durch – das ist die genaue Definition von „Closed Door Meeting“, ohne dass Aufzeichnungen für die Community verfügbar sind. Github-Probleme sind das richtige Mittel für alle Parteien, um einen zivil dokumentierten Diskurs zu führen (wobei Präzedenzfälle zum Entfernen von MS-Kommentaren ignoriert werden).

Das MS Crypto Review Board hat wahrscheinlich auch einen Skype-Anruf geführt. Es ist nicht die Schuld der MS-Leute, die an diesem Thread teilnehmen – sie haben wahrscheinlich nur sehr begrenzten Zugang zu und Überzeugungskraft über die Elfenbeintürme des MS Crypto Review Board (was auch immer/wer das ist).

Zum Streamen von AEAD:

Streaming in Bytegröße _Verschlüsselung_ ist für MAC-Last-Modi wie GCM, CTR+HMAC möglich, aber nicht für MAC-First-Modi wie CCM. Streaming in Bytegröße _Entschlüsselung_ ist grundsätzlich undicht und wird daher von niemandem in Betracht gezogen. Blocksize-Streaming _Verschlüsselung_ ist auch für CBC+HMAC möglich, aber das ändert nichts. Dh. Ansätze in Bytegröße oder Blockgröße zum Streamen von AEAD sind fehlerhaft.

Chunk-Size-Streaming _Verschlüsselung_ und _Entschlüsselung_ funktionieren hervorragend, haben aber zwei Einschränkungen:

  • sie erfordern Pufferung (jenseits der Blockgröße). Dies kann durch die Bibliothek/API erfolgen, wenn die Pufferung kontrolliert/begrenzt wird (z. B. Inferno), oder der oberen Schicht (Aufrufschicht) überlassen wird, damit umzugehen. So oder so funktioniert.

  • Chunked Streaming AEAD ist nicht standardisiert. Ex. nacl-stream , Inferno, MS-eigener DataProtection, Make-your-own.

Dies ist nur eine Zusammenfassung dessen, was jeder in dieser Diskussion bisher bereits weiß.

@sdrapkin , um sicherzustellen, dass ich es richtig verstehe, sind Sie damit einverstanden, dass diese API Streaming-Verschlüsselung, aber keine Streaming-Entschlüsselung bereitstellt?

@sdrapkin Nun , menschliches Brainstorming in Echtzeit ist sicherlich von Vorteil, Bedenken hinsichtlich der Aufzeichnung von Aufzeichnungen können mit Besprechungsprotokollen gelöst werden. Zurück zur technischen Seite, während Chunking für die Streaming-Entschlüsselung funktioniert, ist dies kein primitives Sicherheitselement auf niedriger Ebene. Es ist ein benutzerdefiniertes Protokoll. Und ein Nicht-Standard, wie Sie bemerkt haben.

@morganbr

_Sind Sie damit einverstanden, dass diese API Streaming-Verschlüsselung, aber keine Streaming-Entschlüsselung bereitstellt?_

Nein, bin ich nicht. Wenn eine solche API verfügbar wäre, wäre es einfach, einen stromverschlüsselten Chiffretext einer Größe zu erstellen, die keine Pufferentschlüsselung entschlüsseln kann (Speichermangel).

^^^^ Bisher gab es noch nicht viel Einigkeit, aber ich denke, wir sind uns alle einig, wie auch immer es geht, eine asymmetrische API wäre eine Katastrophe. Sowohl von einem "Hey, was sind die Stream-Entschlüsselungsmethoden, auf die ich mich verlassen würde, weil es Verschlüsselungsmethoden gab", als auch wegen der obigen Kommentare von @sdrapkin .

@Drawaes Einverstanden. Asymmetrische Enc/Dec-API wäre schrecklich.

Irgendwelche Updates Leute?

Anscheinend habe ich ein paar Attacken zusammengeführt.

Inhärente Schwächen in Stream-Chiffren (die AES-CTR und AES-GCM sind) ermöglichen ausgewählte Chiffretext-Angriffe, um eine willkürliche Klartextwiederherstellung zu ermöglichen. Die Verteidigung gegen ausgewählte Chiffretext-Angriffe ist die Authentifizierung; AES-GCM ist also immun ... es sei denn, Sie führen eine Streaming-Entschlüsselung durch und können anhand von Seitenkanalbeobachtungen erkennen, was der Klartext gewesen wäre. Wenn die entschlüsselten Daten beispielsweise als XML verarbeitet werden, schlägt dies sehr schnell fehl, wenn andere Zeichen als Leerzeichen oder < am Anfang der entschlüsselten Daten stehen. Das ist also „die Streaming-Entschlüsselung führt erneut zu Bedenken hinsichtlich des Stream-Cipher-Designs“ (was, wie Sie vielleicht bemerkt haben, .NET nicht hat).

Auf der Suche nach der Herkunft der Schlüsselwiederherstellung gibt es Papiere wie AuthenticationSchwächen in GCM (Ferguson/Microsoft) , aber dass man den Authentifizierungsschlüssel basierend auf kurzen Tag-Größen wiederherstellt (was ein Teil dessen ist, warum die Windows-Implementierung nur 96-Bit-Tags zulässt). Ich wurde wahrscheinlich auf andere Authentifizierungsschlüssel-Wiederherstellungsvektoren hingewiesen, warum das Streamen von GCM gefährlich ist.

In einem früheren Kommentar bemerkte @sdrapkin : „Die Byte-Size-Streaming-Entschlüsselung ist grundsätzlich undicht und wird daher von niemandem in Betracht gezogen. … Byte-Size- oder Block-Size-Ansätze für das Streaming von AEAD sind fehlerhaft.“ Das, kombiniert mit CCM (und SIV), die nicht in der Lage sind, Streaming-Verschlüsselung durchzuführen, und der Kommentar dazu wäre seltsam, das eine Streaming zu haben und das andere nicht, deutet darauf hin, dass wir wieder bei dem Vorschlag sind, nur One-Shot-Verschlüsselung zu haben und entschlüsseln.

Es scheint also, als wären wir gleich wieder bei meinem letzten API-Vorschlag (https://github.com/dotnet/corefx/issues/23629#issuecomment-329202845). Es sei denn, es gibt andere offene Fragen, die ich in meiner Auszeit vergessen habe.

Willkommen zurück @bartonjs

Ich gehe kurz schlafen, aber kurz:

  1. Wir haben in diesem Thread bereits Protokolldesign mit primitivem Design zusammengeführt. Ich sage nur, dass gezielte Chiffretext-Angriffe ein Anliegen des Protokolldesigns sind, kein primitives Anliegen.

  2. Die Streaming-AEAD-Entschlüsselung ermöglicht Ihnen zumindest, Datenschutz zu haben, und aktualisiert dann sofort auf Datenschutz + Authentizität im letzten Byte. Ohne Streaming-Unterstützung auf AEAD (dh herkömmlicher Datenschutz) schränken Sie die Leute dauerhaft auf eine niedrigere, reine Datenschutzgarantie ein.

Wenn die technischen Vorzüge unzureichend sind oder Sie (zu Recht) skeptisch gegenüber der Autorität meiner Argumente sind, werde ich den Weg der externen Autorität versuchen. Sie sollten wissen, dass Ihre tatsächlich zugrunde liegende Implementierung AEAD (einschließlich AES GCM) im Streaming-Modus unterstützt. Das Windows-Kernbetriebssystem ( bcrypt ) ermöglicht das Streamen von GCM über die Funktionen BCryptEncrypt oder BCryptDecrypt . Siehe dort dwFlags . Oder ein Benutzercodebeispiel . Oder ein von Microsoft erstellter CLR-Wrapper . Oder dass die Implementierung erst Anfang dieses Jahres nach NIST FIP-140-2 zertifiziert wurde. Oder dass sowohl Microsoft als auch NIST beträchtliche Ressourcen für die AES-Implementierung aufgewendet und sie hier und hier zertifiziert haben. Und trotz alledem hat niemand den Primitiven Vorwürfe gemacht. Es macht überhaupt keinen Sinn, dass .NET Core plötzlich auftaucht und seine eigene Kryptothese durchsetzt, um die leistungsstarke zugrunde liegende Implementierung zu verwässern. Vor allem, wenn sowohl Streaming als auch One-Shot gleichzeitig unterstützt werden können, sehr trivial.

Mehr? Nun, das Obige gilt für OpenSSL, sogar mit ihren 'neueren' evp-APIs.

Und das gilt auch für BouncyCastle.

Und es stimmt mit der Java Cryptography Architecture.

Beifall!
Sid

@sidshetye ++10 Wenn das Cryptoboard so besorgt ist, warum lassen sie Windows CNG dies tun?

Wenn Sie die NIST FIPS-140-2 AES-Validierung von Microsoft überprüfen (z. B. # 4064 ), werden Sie Folgendes bemerken:

AES-GCM:

  • Klartextlängen: 0, 8, 1016, 1024
  • AAD-Längen: 0, 8, 1016, 1024

AES-CCM:

  • Klartextlänge: 0-32
  • AAD-Länge: 0-65536

Es gibt keine Validierung für das Streaming. Ich bin mir nicht einmal sicher, ob NIST das Ex überprüft. Die AES-GCM-Implementierung sollte nicht mehr als 64 GB Klartext verschlüsseln dürfen (eine weitere lächerliche Einschränkung von GCM).

Ich bin nicht massiv mit Streaming verheiratet, da meine Verwendung nicht über 16k fahren sollte, aber fragmentierte Puffer wären nett und sollten überhaupt kein Risiko darstellen (ich vermute tatsächlich, dass cng seine Schnittstelle genau für diesen Zweck so gemacht hat) ... zB möchte ich in der Lage sein, eine Reihe von Spannen oder ähnliches (z. B. eine verknüpfte Liste) zu übergeben und sie auf einmal entschlüsseln zu lassen. Wenn es in einen zusammenhängenden Puffer entschlüsselt, ist alles in Ordnung.

Also denke ich, dass das Verschieben des schattigen Krypto-Boards auf der API im "Streaming-Stil" vorerst ein No-Go ist, also lassen Sie uns weitermachen und eine One-Shot-API erstellen. Es besteht immer die Möglichkeit, eine API zu erweitern, WENN genügend Leute später einen Bedarf zeigen

@sdrapkin Der Punkt ist, dass es die Streaming-API ist, die von NIST Labs und MSFT einer umfassenden Überprüfung unterzogen wurde. Jeder validierte Build kostet zwischen 80.000 und 50.000 US-Dollar, und MSFT (und OpenSSL und Oracle und andere Krypto-Schwergewichte) haben seit über 10 Jahren SCHWER in die Validierung dieser API und Implementierungen investiert. Lassen wir uns nicht von den spezifischen Klartextgrößen des Testplans ablenken, da ich zuversichtlich bin, dass .NET andere Größen als 0, 8, 1016, 1024 unabhängig von Streaming oder One-Shot unterstützen wird. Der Punkt ist, dass all diese kampferprobten APIs (wörtlich: auf Waffenunterstützungssystemen) auf all diesen Plattformen das Streaming von AEAD auf krypto-primitiver API-Ebene unterstützen. Leider war bisher jedes Argument dagegen ein Anliegen auf Anwendungs- oder Protokollebene, das als Pseudo-Bedenken auf der Krypto-Primitivebene angeführt wurde.

Ich bin dafür, „die beste Idee gewinnen zu lassen“, aber wenn das .net-Kern-Krypto-Team (MSFT oder Community) keine bahnbrechenden Entdeckungen gemacht hat, sehe ich einfach nicht, wie alle, die bisher Krypto machen, von allen verschiedenen Organisationen falsch liegen und sie haben recht.

PS: Ich weiß, dass wir uns hier nicht einig sind, aber wir alle wollen das Beste für die Plattform und ihre Kunden.

@Drawaes Wenn die heute definierte AEAD-Schnittstelle (nicht unbedingt die Implementierung) keine Streaming-API-Oberfläche unterstützt, sehe ich nicht, wie die Leute sie erweitern können, ohne zwei Schnittstellen oder benutzerdefinierte Schnittstellen zu haben. Das wäre eine Katastrophe. Ich hoffe, dass diese Diskussion zu einer Schnittstelle führt, die zukunftssicher ist (oder zumindest andere AEAD-Schnittstellen widerspiegelt, die es seit vielen Jahren gibt!).

Ich neige dazu, zuzustimmen. Aber dieses Problem geht nirgendwo schnell hin und wenn das passiert, werden wir wahrscheinlich einen kritischen Punkt erreichen, entweder wird es es nicht für 2.1 schaffen oder es muss durchgerammt werden, ohne dass Zeit bleibt, um Probleme auszubügeln. Ich bin ehrlich, ich bin zu meinen alten Verpackungen zurückgekehrt und überarbeite sie gerade für 2.0;)

Wir haben einige Referenz-APIs für Java , OpenSSL oder C# Bouncy Castle oder CLR Security . Offen gesagt, jeder von ihnen wird es tun, und langfristig wünsche ich mir, dass C # so etwas wie die Java-Kryptographiearchitektur von Java hat, bei der alle Kryptoimplementierungen gegen eine gut etablierte Schnittstelle sind, die es ermöglicht, Kryptobibliotheken auszutauschen, ohne den Benutzercode zu beeinträchtigen.

Hier hinten denke ich, dass es am besten ist, die ICryptoTransform -Schnittstelle von .NET Core als zu erweitern

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

Wenn wir Span alle byte[] s ifizieren, sollte dies die gesamte API im System.Security.Cryptography -Namespace durchdringen, um die Gesamtkonsistenz zu gewährleisten.

Bearbeiten: JCA-Links behoben

Wenn wir alle byte[]s spanifizieren, sollte dies die gesamte API im System.Security.Cryptography-Namespace durchdringen, um die Gesamtkonsistenz zu gewährleisten.

Das haben wir schon gemacht. Alles außer ICryptoTransform, weil wir Schnittstellen nicht ändern können.

Ich denke, es ist am besten, wir erweitern ICryptoTransform von .NET Core ...

Das Problem dabei ist, dass das Aufrufmuster sehr umständlich ist, wenn es darum geht, das Tag am Ende herauszuholen (insbesondere wenn CryptoStream beteiligt ist). Ich habe das ursprünglich geschrieben, und es war hässlich. Es gibt auch das Problem, wie man einen davon bekommt, da die GCM-Parameter anders sind als die CBC/ECB-Parameter.

Also, hier sind meine Gedanken.

  • Streaming-Entschlüsselung ist gefährlich für AE.
  • Im Allgemeinen bin ich ein Fan von „Gib mir das Primitiv und lass mich mein Risiko managen“
  • Ich bin auch ein Fan von „.NET sollte nicht (leicht) völlig unsichere Dinge zulassen, weil das ein Teil seines Wertversprechens ist“
  • Wenn, wie ich ursprünglich falsch verstanden habe, die Risiken einer schlechten GCM-Entschlüsselung die Wiederherstellung des Eingabeschlüssels wären, wäre ich immer noch bei "das ist zu unsicher". (Der Unterschied zwischen .NET und allem anderen wäre "dafür hat die Welt länger gebraucht, hat mehr gelernt")
  • Aber da dies nicht der Fall ist, wenn Sie wirklich wollen, dass die Stützräder abfallen, denke ich, dass ich diesen Gedanken in Betracht ziehen werde.

Meine ziemlich rohen Gedanken zu diesem Zweck (Hinzufügen zu den vorhandenen Vorschlägen, damit der One-Shot bleibt, obwohl ich vermute, dass es sich um ein virtuelles Standard-Impl anstelle eines Abstracts handelt):

```C#
Teilklasse AuthenticatedEncryptor
{
// wird ausgelöst, wenn bereits eine Operation ausgeführt wird
public abstract void Initialize(ReadOnlySpanzugehörigeDaten);
// wahr bei Erfolg, falsch bei „Ziel zu klein“, Ausnahme bei allem anderen.
public abstract bool TryEncrypt(ReadOnlySpanDaten, Spanverschlüsselte Daten, out int bytesRead, out int bytesWritten);
// false, wenn restlicheEncryptedData zu klein ist, löst aus, wenn andere Eingaben zu klein sind, siehe NonceOrIVSizeInBits- und TagSizeInBits-Eigenschaften.
// NonceOrIvUsed könnte zu Initialize wechseln, aber dann könnte es als Eingabe interpretiert werden.
public abstract bool TryFinish(ReadOnlySpanverbleibende Daten, Spanrestliche verschlüsselte Daten, aus int bytesWritten, SpanEtikett, SpannonceOrIvUsed);
}

partielle Klasse AuthenticatedDecryptor
{
// wird ausgelöst, wenn bereits eine Operation ausgeführt wird
public abstract void Initialize(ReadOnlySpan-Tag, ReadOnlySpannonceOrIv, ReadOnlySpanzugehörigeDaten);
// wahr bei Erfolg, falsch bei „Ziel zu klein“, Ausnahme bei allem anderen.
public abstract bool TryDecrypt(ReadOnlySpanDaten, SpandecryptedData, out int bytesRead, out int bytesWritten);
// Wirft auf ein schlechtes Tag, kann aber trotzdem die Daten verlieren.
// (RemainingDecryptedData ist für CBC+HMAC erforderlich, und so können auch restlicheDaten hinzugefügt werden, denke ich?)
public abstract bool TryFinish(ReadOnlySpanverbleibende Daten, SpanresidualDecryptedData, out int bytesWritten);
}
```

AssociatedData kommt zu Initialize, weil Algorithmen, die es zuletzt brauchen, daran festhalten können, und Algorithmen, die es zuerst brauchen, können es nicht anders haben.

Sobald eine Form festgelegt ist, wie Streaming aussehen würde (und ob die Leute denken, dass CCM im Streaming-Verschlüsselungsmodus intern puffern oder auslösen sollte), gehe ich zurück zum Board.

@bartonjs Ich weiß, was Sie damit meinen, das Tag vom Ende des Streams zu pflücken und zu programmieren, um die Symmetrie beim Verschlüsseln / Entschlüsseln zu gewährleisten. Es ist schwierig, aber schlimmer, wenn es jedem Benutzer überlassen wird, es zu lösen. Ich habe eine Implementierung, die ich unter MIT teilen kann; muss intern mit meinem Team nachsehen (nicht an meinem Schreibtisch/Mobil)

Ein Mittelweg könnte wie bcrypt von OpenSSL oder NT sein, wo Sie das Tag direkt vor dem endgültigen Entschlüsselungsaufruf einfügen müssen, da dann die Tag-Vergleiche stattfinden. dh ein SetExpectedTag (vor der endgültigen Entschlüsselung) und GetTag (nach der endgültigen Verschlüsselung) würden funktionieren, aber die Tag-Verwaltung an den Benutzer auslagern. Die meisten werden das Tag einfach an den Cipherstream anhängen, da es die natürliche zeitliche Reihenfolge ist.

Ich denke, das Erwarten des Tags in Initialize selbst (beim Entschlüsseln) bricht die Symmetrie in Raum (Bytefluss) und Zeit (Tag-Prüfung am Ende, nicht am Anfang), was seine Nützlichkeit einschränkt. Aber die oben genannten Tag-APIs lösen das.

Auch für die Verschlüsselung benötigt Initialize den IV, bevor Krypto transformiert wird.

Schließlich benötigt Initialize zum Verschlüsseln und Entschlüsseln die AES-Verschlüsselungsschlüssel vor allen Transformationen. (Mir fehlt etwas Offensichtliches, oder Sie haben vergessen, diesen Teil einzugeben?)

Ich denke, das Erwarten des Tags in Initialize self (in decrypt) bricht die Symmetrie

Bei CBC+HMAC lautet die übliche Empfehlung, den HMAC zu überprüfen, bevor mit der Entschlüsselung begonnen wird, es handelt sich also um einen Tag-First-Entschlüsselungsalgorithmus. In ähnlicher Weise könnte es einen "reinen AE" -Algorithmus geben, der während der Berechnungen destruktive Operationen am Tag durchführt und lediglich überprüft, ob die endgültige Antwort 0 war. Also, wie der zugehörige Datenwert, da es Algorithmen geben könnte, die ihn zuerst benötigen in einer vollständig verallgemeinerten API an erster Stelle stehen.

Wenn Sie sie in SetAssociatedData und SetTag ausgeben, besteht das Problem, dass die Basisklasse algorithmenunabhängig war, die Verwendung jedoch algorithmenabhängig wird. Das Ändern von AesGcm in AesCbcHmacSha256 oder SomeTagDesctructiveAlgorithm würde jetzt dazu führen, dass TryDecrypt ausgelöst wird, da das Tag noch nicht bereitgestellt wurde. Für mich ist das schlimmer, als überhaupt nicht polymorph zu sein, also schlägt das Zulassen der Flexibilität vor, das Modell auseinander zu brechen, um es vollständig pro Algorithmus zu isolieren. (Ja, es könnte durch mehr charakteristische Eigenschaften der Algorithmusidentifikation wie NeedsTagFirst gesteuert werden, aber das führt wirklich nur dazu, dass es schwieriger zu verwenden ist.)

Auch für die Verschlüsselung benötigt Initialize den IV, bevor Krypto transformiert wird.

Schließlich benötigt Initialize zum Verschlüsseln und Entschlüsseln die AES-Verschlüsselungsschlüssel vor allen Transformationen.

Der Schlüssel war ein Klassenctor-Parameter. Die IV/Nonce kommt vom IV/Nonce-Anbieter im ctor-Parameter.

Das Anbietermodell löst SIV, wobei während der Verschlüsselung keine IV angegeben wird, sondern eine für die Daten generiert wird. Andernfalls verfügt SIV über den Parameter und erfordert, dass ein leerer Wert bereitgestellt wird.

oder hast du vergessen, das bisschen einzugeben?

Die Streaming-Methoden wurden zu meinem bestehenden Vorschlag hinzugefügt, der bereits den Schlüssel und den IV/Nonce-Anbieter als ctor-Parameter hatte.

@bartonjs : Guter Punkt, dass einige Algos zuerst taggen möchten, während andere am Ende und danke für die Erinnerung, dass es eine Ergänzung zur ursprünglichen Spezifikation ist. Ich habe festgestellt, dass es einfacher ist, einen Anwendungsfall zu berücksichtigen, daher hier ein Cloud-First-Beispiel:

Wir werden Analysen an einer oder mehreren 10-GB-AES-GCM-verschlüsselten Dateien (dh Tags nach Chiffretext) durchführen, die im Speicher aufbewahrt werden. Ein Analyse-Worker entschlüsselt gleichzeitig mehrere eingehende Streams in separate Computer/Cluster und startet nach den letzten Byte- und Tag-Prüfungen jede Analyse-Workload. Alle Speicher-, Worker- und Analyse-VMs befinden sich in Azure US-West.

Hier gibt es keine Möglichkeit, das Tag am Ende jedes Streams abzurufen und es der Initialize -Methode von AuthenticatedDecryptor bereitzustellen. Selbst wenn ein Benutzer freiwillig Code für die GCM-Nutzung ändert, kann er nicht einmal anfangen, die API zu verwenden.

Denken Sie mal darüber nach, die einzige Möglichkeit, wie wir eine API haben könnten, die verschiedene AEADs aufnimmt UND keine Änderungen des Benutzercodes hat, ist, wenn die Krypto-Anbieter für verschiedene AEAD-Algorithmen die Tags automatisch handhaben. Java tut dies, indem es die Tags an das Ende des Chiffretexts für GCM setzt und ihn während der Entschlüsselung ohne Benutzereingriff herauszupft. Abgesehen davon muss jeder, der den Algorithmus erheblich ändert (z. B. CBC-HMAC => GCM), seinen Code aufgrund der sich gegenseitig ausschließenden Natur der Tag-First- und Tag-Last-Verarbeitung ändern.

IMHO sollten wir zuerst entscheiden, ob

Option 1) Die Algorithmus-Anbieter kümmern sich intern um das Tag-Management (wie Java)

oder

Option 2) Stellen Sie genügend auf der API bereit, damit Benutzer es selbst tun können (wie WinNT bcrypt oder openssl)

Option 1 würde das Gesamterlebnis für Bibliotheksbenutzer wirklich vereinfachen, da die Pufferverwaltung komplex werden kann. Lösen Sie es gut in der Bibliothek und jeder Benutzer muss es jetzt nicht jedes Mal lösen. Außerdem erhalten alle AEADs die gleiche Schnittstelle (Tag-First, Tag-Last, Tag-less) und das Austauschen von Algorithmen ist auch einfacher.

Meine Stimme wäre für Variante 1.

Schließlich konnten wir unsere Implementierung ausgraben, die ICryptoTransform Streaming-Operationen über GCM ermöglichte, um das Tag In-Stream- Quelle automatisch auszulesen . Dies war ein bedeutendes Update des eigenen Wrappers von CLR Security und trotz der zusätzlichen Pufferkopien ist es immer noch sehr schnell (~4 GB/s auf unserem Test-Macbook Pro im Windows 10-Bootcamp). Wir haben im Grunde die CLR-Sicherheit umgangen, um Option 1 für uns selbst zu erstellen, damit wir dies nicht überall sonst tun müssen. Dieses Bild hilft wirklich zu erklären, was in den TransformBlock und TransformFinalBlock der ICryptoTransform -Oberfläche vor sich geht.

@sidshetye Ich bin mir nicht sicher, warum Ihr Cloud-First-Beispiel blockiert ist. Wenn Sie aus dem Speicher lesen, können Sie zuerst die letzten paar Tag-Bytes herunterladen und diese dem Decryptor-Ctor zur Verfügung stellen. Bei Verwendung der Azure Storage-APIs würde dies über CloudBlockBlob.DownloadRangeXxx erfolgen.

@GrabYourPitchforks Um bei diesem Beispiel nicht zu sehr abgelenkt zu werden, aber das ist eine spezifische Funktion von Azure Blob Storage. Im Allgemeinen erhalten VM-basierte Speicher (IaaS) oder Nicht-Azure-Speicherarbeitslasten normalerweise einen Netzwerkstream, der nicht durchsucht werden kann.

Ich persönlich freue mich sehr, @GrabYourPitchforks zu sehen - yay!

Wir werden Analysen an einer oder mehreren 10-GB-AES-GCM-verschlüsselten Dateien (dh Tags nach Chiffretext) durchführen, die im Speicher aufbewahrt werden. Ein Analyse-Worker entschlüsselt gleichzeitig mehrere eingehende Streams in separate Computer/Cluster und startet nach den letzten Byte- und Tag-Prüfungen jede Analyse-Workload. Alle Speicher-, Worker- und Analyse-VMs befinden sich in Azure US-West.

@sidshetye , du warst so hartnäckig dabei, dumme und gefährliche Primitiven und intelligente und knuddelige Protokolle getrennt zu halten! Ich hatte einen Traum – und ich habe daran geglaubt. Und dann werfen Sie uns das zu. Dies ist ein Protokoll - ein Systemdesign. Wer auch immer das von Ihnen beschriebene Protokoll entworfen hat - hat es vermasselt. Es hat keinen Sinn, jetzt über die Unfähigkeit zu weinen, einen quadratischen Stift in ein rundes Loch zu stecken.

Wer auch immer GCM-verschlüsselte 10-Gb-Dateien lebt, lebt nicht nur gefährlich nahe am primitiven Rand (GCM ist nach 64 Gb nicht mehr gut), sondern es wurde auch implizit behauptet, dass der gesamte Chiffretext gepuffert werden muss.

Wer GCM-Dateien mit 10 Gb verschlüsselt, macht mit überwältigender Wahrscheinlichkeit einen Protokollfehler. Die Lösung: Chunked-Verschlüsselung. TLS verfügt über 16k-begrenztes Chunking mit variabler Länge, und es gibt andere, einfachere, PKI-freie Varianten. Der „Wolke-zuerst“-Sexappeal dieses hypothetischen Beispiels mindert die Konstruktionsfehler nicht.

(In diesem Thread habe ich noch einiges nachzuholen.)

@sdrapkin hat einen Punkt zur Wiederverwendung der IAuthenticatedEncryptor -Schnittstelle aus der Datenschutzebene angesprochen. Um ehrlich zu sein, denke ich nicht, dass dies die richtige Abstraktion für ein Primitiv ist, da die Datenschutzschicht ziemlich eigensinnig ist, wie sie Kryptografie durchführt. Zum Beispiel verbietet es die Selbstauswahl einer IV oder Nonce, es schreibt vor, dass eine konforme Implementierung das Konzept von AAD versteht, und es erzeugt ein Ergebnis, das etwas proprietär ist. Im Fall von AES-GCM ist der Rückgabewert von IAuthenticatedEncryptor.Encrypt die Verkettung eines seltsamen Fast-Nonce-Dings, das für die Ableitung von Unterschlüsseln verwendet wird, der Chiffretext, der sich aus der Ausführung von AES-GCM über den bereitgestellten Klartext ergibt (aber nicht die AAD!) und das AES-GCM-Tag. Während also jeder Schritt beim Generieren der geschützten Nutzlast sicher ist, folgt die Nutzlast selbst keiner akzeptierten Konvention, und Sie werden außer der Datenschutzbibliothek niemanden finden, der den resultierenden Chiffretext erfolgreich entschlüsseln kann. Das macht es zu einem guten Kandidaten für eine Bibliothek für App-Entwickler, aber zu einem schrecklichen Kandidaten für eine Schnittstelle, die von Primitiven implementiert werden soll.

Ich sollte auch sagen, dass ich keinen nennenswerten Wert darin sehe, ein One True Interface(tm) IAuthenticatedEncryptionAlgorithm zu haben, das alle authentifizierten Verschlüsselungsalgorithmen implementieren sollten. Diese Primitive sind "komplex", im Gegensatz zu einfachen Blockverschlüsselungs-Primitiven oder Hash-Primitiven. Es gibt einfach zu viele Variablen in diesen komplexen Primitives. Ist das primitive AE nur oder ist es AEAD? Akzeptiert der Algorithmus überhaupt eine IV / Nonce? (Ich habe einige gesehen, die dies nicht tun.) Gibt es Bedenken, wie die Eingabe IV / Nonce oder Daten strukturiert sein müssen? IMO sollten die komplexen Primitiven einfach eigenständige APIs sein, und Bibliotheken auf höherer Ebene würden zur Unterstützung der spezifischen komplexen Primitiven backen, die ihnen wichtig sind. Dann legt die übergeordnete Bibliothek die einheitliche API offen, die ihrer Meinung nach für ihre Szenarien geeignet ist.

@sdrapkin Wir schweifen wieder vom Thema ab. Ich sage nur, dass ein System unter Verwendung von Primitiven aufgebaut wird. Die Krypto-Primitiven hier sind nackt und mächtig. Während die System-/Protokollschicht die Pufferung handhabt; das auch auf Cluster-Ebene, sicherlich nicht im Hauptsystemspeicher, den die One-Shot-Primitive erzwingen würden. Die 'Chunking'-Grenze ist X (X = 10 GB hier), weil < 64 GB, weil die Pufferkapazität des Clusters nahezu unbegrenzt war und nichts starten würde/konnte, bis das letzte Byte im Cluster geladen ist. Dies ist genau die Trennung der Bedenken, die Optimierung jeder Schicht für ihre Stärken, über die ich gesprochen habe. Und dies kann nur passieren, wenn die zugrunde liegenden Grundelemente keine Designs/Einschränkungen auf höheren Ebenen behindern (beachten Sie, dass Apps aus der realen Welt ihre eigenen Legacy-Behinderungen haben).

NIST 800-38d sec9.1 besagt:

Um eine unbefugte Partei daran zu hindern, die Generierung von IVs zu kontrollieren oder zu beeinflussen,
GCM darf nur innerhalb eines kryptografischen Moduls implementiert werden, das die Anforderungen von erfüllt
FIPS-Veröffentlichung. 140-2. Insbesondere muss die kryptografische Grenze des Moduls enthalten a
„Erzeugungseinheit“, die IVs nach einer der Konstruktionen in Sec. 8.2 oben.
Die Dokumentation des Moduls für seine Validierung gegen die Anforderungen von FIPS 140-2 muss
beschreiben, wie das Modul die Eindeutigkeitsanforderung für IVs erfüllt.

Das impliziert für mich, dass GCM IVs intern automatisch generiert werden müssen (und nicht extern weitergegeben werden).

@sdrapkin Guter Punkt, aber wenn Sie noch genauer lesen, werden Sie sehen, dass Abschnitt 8.2.2 für IV-Längen von 96 Bit und mehr das Generieren eines IV mit einem Zufallsbitgenerator (RBG) ermöglicht, bei dem mindestens 96 Bit zufällig sind (Sie könnte nur 0 andere Bits). Ich habe dies letzten Monat in diesem Thread selbst erwähnt ( hier unter nonce).

LT;DR: INonce ist eine Falle, die zur Nichteinhaltung der NIST- und FIPS-Richtlinien führt.

Abschnitt 9.1 besagt einfach, dass für FIPS 140-2 die IV-Generierungseinheit (vollständig zufällig, dh Abschnitt 8.2.2 oder deterministische Implementierung, dh Abschnitt 8.2.1) innerhalb der Modulgrenze liegen muss, die einer FIPS-Validierung unterzogen wird. Seit ...

  1. RBGs sind bereits FIPS-validiert
  2. IV-Linse >= 96 wird empfohlen
  3. Beim Entwerfen einer Einheit der IV-Generation, die Neustarts fortsetzt, ist ein unbestimmter Stromausfall in einer kryptoprimitiven Schicht schwierig
  4. Die Implementierung von 3 oben in die Kryptobibliothek UND die Zertifizierung ist schwierig und teuer (50.000 US-Dollar für alles, was zu einem nicht bitgenauen Build-Image führt).
  5. Kein Benutzercode wird jemals 3 implementieren und es wegen 4 oben zertifizieren lassen. (lassen wir einige exotische Militär-/Regierungsanlagen beiseite).

... die meisten Kryptobibliotheken (siehe Java von Oracle, bcryptprimitives von WinNT, OpenSSL usw.), die sich einer FIPS-Zertifizierung unterziehen, verwenden die RBG-Route für IV und nehmen einfach ein Byte-Array als Eingabe. Beachten Sie, dass die INonce -Schnittstelle aus Sicht von NIST und FIPS eigentlich eine Falle ist, da sie implizit vorschlägt, dass ein Benutzer eine Implementierung dieser Schnittstelle an die Kryptofunktion übergeben sollte. Aber es ist fast garantiert, dass jede Benutzerimplementierung von INonce NICHT den mehr als 9 Monate dauernden und über 50.000 $ teuren NIST-Zertifizierungsprozess durchlaufen hat. Wenn sie jedoch gerade ein Byte-Array mit dem RGB-Konstrukt (bereits in der Kryptobibliothek) gesendet hätten, wären sie vollständig konform mit den Richtlinien.

Ich habe es bereits gesagt – diese bestehenden Krypto-Bibliotheken haben ihre API-Oberfläche weiterentwickelt und wurden in mehreren Szenarien kampferprobt. Mehr als das, was wir in diesem langen Thread angesprochen haben. Ich stimme erneut dafür, dieses Wissen und diese Erfahrung in all diesen Bibliotheken, all diesen Validierungen und all diesen Installationen zu nutzen, anstatt zu versuchen, das Rad neu zu erfinden. Erfinden Sie das Rad nicht neu. Verwenden Sie es, um die Rakete zu erfinden :)

Hallo Leute,

Irgendwelche Updates dazu? Ich habe keine Aktualisierungen im Krypto-Roadmap-Thread von @karelz oder im AES GCM-Thread gesehen.

Danke
Sid

Der letzte konkrete Vorschlag ist also von 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);
}

Seitdem wurden nur wenige potenzielle Probleme angesprochen:

  • Das Tag ist im Voraus erforderlich, was bestimmte Szenarien behindert. Entweder muss die API erheblich komplexer werden, um mehr Flexibilität zu ermöglichen, oder dieses Problem muss als ein Protokollproblem (dh ein Problem auf hoher Ebene) betrachtet werden.
  • INonceProvider könnte unnötig komplex sein und/oder zu einer Nichteinhaltung der NIST- und FIPS-Richtlinien führen.
  • Die beabsichtigte Abstraktion von authentifizierten Verschlüsselungsprimitiven könnte ein Wunschtraum sein, da die Unterschiede zu groß sein könnten. Dieser Vorschlag wurde nicht weiter diskutiert.

Ich möchte folgendes vorschlagen:

  1. Die zusätzliche Komplexität, das Tag nicht im Voraus zu erfordern, scheint schwerwiegend, das entsprechende Problemszenario scheint ungewöhnlich, und das Problem klingt tatsächlich sehr nach einer Frage des Protokolls. Gutes Design kann vieles unterbringen, aber nicht alles. Ich persönlich fühle mich wohl dabei, dies dem Protokoll zu überlassen. (Starke Gegenbeispiele willkommen.)
  2. Die Diskussion hat sich konsequent in Richtung einer flexiblen Implementierung auf niedriger Ebene bewegt, die nicht vor Missbrauch schützt, mit Ausnahme der IV-Generation . Seien wir konsequent. Der allgemeine Konsens scheint zu sein, dass eine API auf hoher Ebene ein wichtiger nächster Schritt ist, der für die ordnungsgemäße Verwendung durch die Mehrheit der Entwickler von entscheidender Bedeutung ist. Auf diese Weise kommen wir davon, dass wir in der API auf niedriger Ebene keinen Schutz vor Missbrauch bieten. Aber es scheint, dass eine Extraportion Angst die Idee der Missbrauchsprävention _im Bereich der IV-Generation_ gestützt hat. Im Zusammenhang mit einer Low-Level-API und um konsistent zu sein, würde ich zu einem byte[] -Äquivalent tendieren. Aber das Austauschen der Implementierung ist nahtloser mit dem injizierten INonceProvider . Ist der Kommentar von @sidshetye unwiderlegbar oder könnte eine einfache INonceProvider -Implementierung, die lediglich den RNG aufruft, immer noch als konform angesehen werden?
  3. Die Abstraktionen scheinen nützlich zu sein, und es wurde so viel Mühe in ihre Gestaltung gesteckt, dass ich inzwischen davon überzeugt bin, dass sie mehr nützen als schaden. Außerdem können sich High-Level-APIs immer noch dafür entscheiden, Low-Level-APIs zu implementieren, die nicht den Low-Level-Abstraktionen entsprechen.
  4. IV ist der allgemeine Begriff, und eine Nonce ist eine bestimmte Art von IV, richtig? Dies erfordert Umbenennungen von INonceProvider in IIVProvider und von nonceOrIv* in iv* . Schließlich haben wir es immer mit einer IV zu tun, aber nicht unbedingt mit einer Nonce.

Das Tag im Voraus ist kein Starter für mein Szenario, also werde ich wahrscheinlich nur meine eigene Implementierung behalten. Was in Ordnung ist. Ich bin mir nicht sicher, ob es jedermanns Sache ist, in diesem Bereich Hochleistungscode zu schreiben.

Das Problem ist, dass es unnötige Latenz verursacht. Sie müssen eine gesamte Nachricht vorpuffern, um das Tag am Ende zu erhalten, um mit der Dekodierung des Frames zu beginnen. Das bedeutet, dass Sie IO und Entschlüsselung grundsätzlich nicht überlappen können.

Ich bin mir nicht sicher, warum es so schwer ist, es am Ende zuzulassen. Aber ich werde keine Straßensperre für diese API errichten, es wird in meinem Szenario einfach nicht von Interesse sein.

IV ist der allgemeine Begriff, und eine Nonce ist eine bestimmte Art von IV, richtig?

Nein. A nonce ist eine Zahl, die nur einmal verwendet wird . Ein Algorithmus, der eine Nonce spezifiziert, zeigt an, dass die Wiederverwendung die Garantien des Algorithmus verletzt. Im Fall von GCM kann die Verwendung derselben Nonce mit demselben Schlüssel und einer anderen Nachricht zur Kompromittierung des GHASH-Schlüssels führen, wodurch GCM auf CTR reduziert wird.

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

Nonce: Ein in Sicherheitsprotokollen verwendeter Wert, der niemals mit demselben Schlüssel wiederholt wird. Zum Beispiel dürfen Nonces, die als Challenges in Challenge-Response-Authentifizierungsprotokollen verwendet werden, im Allgemeinen nicht wiederholt werden, bis Authentifizierungsschlüssel geändert werden. Andernfalls besteht die Möglichkeit eines Replay-Angriffs. Die Verwendung einer Nonce als Herausforderung ist eine andere Anforderung als eine zufällige Herausforderung, da eine Nonce nicht unbedingt unvorhersehbar ist.

Eine "IV" hat nicht die gleichen strengen Anforderungen. Wenn Sie beispielsweise eine IV mit CBC wiederholen, wird nur preisgegeben, ob die verschlüsselte Nachricht dieselbe oder eine andere als eine vorherige mit derselben IV ist. Es schwächt den Algorithmus nicht.

Eine Nonce ist eine Zahl, die einmal verwendet wird.
Eine "IV" hat nicht die gleichen strengen Anforderungen.

@bartonjs Ja. Ich würde argumentieren, dass, da ein Nonce verwendet wird, um das Krypto-Primitive zu initialisieren , es sein Initialisierungsvektor ist. Es passt perfekt zu jeder Definition von IV, die ich finden kann. Es hat strengere Anforderungen, ja, genauso wie eine Kuh strengere Anforderungen hat als ein Tier. Der aktuelle Wortlaut scheint nach einem "cowOrAnimal"-Parameter zu fragen. Die Tatsache, dass verschiedene Modi unterschiedliche Anforderungen an die IV haben, ändert nichts an der Tatsache, dass sie alle nach irgendeiner Form von IV fragen. Wenn mir etwas fehlt, behalten Sie auf jeden Fall den aktuellen Wortlaut bei, aber soweit ich das beurteilen kann, sind nur "iv" oder "IIVProvider" sowohl einfach als auch korrekt.

Um sich dem nonceOrIv Bikeshedding hinzugeben:

Das 96-Bit-GCM IV wird manchmal als 4-Byte- salt und 8-Byte- nonce definiert (z. B. RFC 5288). RFC 4106 definiert GCM nonce als 4-Byte salt und 8-Byte iv . RFC 5084 (GCM in CMS) besagt, dass CCM nonce nimmt, GCM iv , aber _"...um einen gemeinsamen Satz von Begriffen für AES-CCM und AES-GCM zu haben , das AES-GCM IV wird im Rest dieses Dokuments als Nonce bezeichnet."_ RFC 5647 (GCM für SSH) sagt _"Hinweis: In [RFC5116] wird das IV als Nonce bezeichnet."_ RFC 4543 ( GCM in IPSec) sagt _"wir bezeichnen die AES-GMAC IV-Eingabe als Nonce, um sie von den IV-Feldern in den Paketen zu unterscheiden."_ RFC 7714 (GCM für SRTP) spricht von einem 12-Byte IV und behält fast seine Konsistenz bei, sagt dann aber "_minimale & maximale Nonce (IV) Länge: MUSS 12 Oktette sein."_

Angesichts des völligen Mangels an Konsistenz in den meisten GCM-Spezifikationen macht nonceOrIv irgendwie Sinn. 0,02 $

Tag im Voraus ist ein Nichtstarter

Wie andere Kunden, die sich hier äußern, kommt es auch für uns nicht infrage, das Etikett im Voraus zu verlangen. Es gibt keine Möglichkeit, dass .NET mit dieser künstlich eingeführten Einschränkung gleichzeitige Streams verarbeiten kann. Tötet die Skalierbarkeit vollständig.

Können Sie die Behauptung untermauern, dass es die Komplexität erhöht? Weil es eigentlich trivial sein sollte. Außerdem hat keine der plattformspezifischen Krypto-Implementierungen (die Sie einpacken werden) diese Einschränkung. Der Grund liegt insbesondere darin, dass das Eingabe-Tag lediglich konstantzeitlich mit dem berechneten Tag verglichen werden muss. Und das berechnete Tag ist erst verfügbar, nachdem der letzte Block während TryFinish entschlüsselt wurde. Wenn Sie also mit Ihrer Implementierung beginnen, werden Sie feststellen, dass Sie lediglich tag in Ihrer Instanz bis zu TryFinish speichern. Sie könnten es sehr gut als optionale Eingabe haben

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

Ich denke auch, dass wir uns zu sehr bemühen, uns auf eine einzige Schnittstelle zu normalisieren, die alle Krypto-Szenarien abdeckt. Auch ich bevorzuge verallgemeinerte Schnittstellen, aber niemals auf Kosten der Funktionalität oder Skalierbarkeit - insbesondere auf einer so grundlegenden Ebene wie der Standard-Cryto-Bibliothek der Sprache selbst. IMHO, wenn man sich dabei erwischt, bedeutet dies normalerweise, dass die Abstraktion fehlerhaft ist.

Wenn eine einfache konsistente Schnittstelle benötigt wird, bevorzuge ich den Java-Ansatz - der hier auch bereits als Option 1 angesprochen wurde. Es umgeht auch das obige Problem von Tag First/Tag Last, indem es sie innerhalb der Algorithmusimplementierungen hält (IMHO, wie ich denke, dass es sollte). Mein Team implementiert dies nicht, also ist es nicht unsere Entscheidung, ABER wenn wir eine Entscheidung treffen und mit der Implementierung beginnen müssten – wir würden diesen Weg mit Sicherheit gehen.

Bitte vermeiden Sie die Schnittstelle INonce , ein einfaches byte[] oder span<> sollte für eine kompatible Low-Level-Schnittstelle ausreichen.

IV gegen Nonce – Der verallgemeinerte Fall ist tatsächlich IV. Für GCM muss der IV ein Nonce sein (z. B. Car vs RedOrCar). Und während ich das kopiere und einfüge, ist mir gerade aufgefallen, dass @timovzl ein sehr ähnliches Beispiel verwendet hat :)

@sidshetye Können Sie einen genauen Vorschlag machen, dass sowohl (1) Algorithmen unterstützt, die das Tag im Voraus benötigen, als auch (2) das Tag in allen anderen Situationen nur bis TryFinish benötigt?

Ich nehme an, Sie denken etwas in die folgende Richtung?

  • Das Tag in Initialize darf null sein. Nur die Algorithmen, die es im Voraus benötigen, werden auf null setzen.
  • Das Tag in TryFinish ist erforderlich oder darf (alternativ) für die Algorithmen, die es bereits im Voraus erfordert haben, null sein.

Ich nehme an, das Obige fügt nur Komplexität in Form von Dokumentation und Know-how hinzu. Für eine Low-Level-API könnte dies als kleines Opfer angesehen werden, da ohnehin eine angemessene Dokumentation und Know-how erforderlich sind.

Ich bin allmählich davon überzeugt, dass dies aus Gründen der Kompatibilität mit anderen Implementierungen und Streaming möglich sein sollte.

@timovzl Klar, ich hoffe morgen etwas Zeit dafür einzuplanen.

@Timovzl , ich hatte gerade heute Zeit und das stellte sich als ziemlicher Kaninchenbau heraus! Dies ist lang, aber ich denke, es erfasst die meisten Anwendungsfälle, erfasst die Stärken von .NET Crypto ( ICryptoTransform ) und umfasst gleichzeitig die .NET Core/Standard-Richtung ( Span<> ). Ich habe es noch einmal gelesen, hoffe aber, dass unten keine Tippfehler enthalten sind. Ich denke auch, dass etwas Echtzeitkommunikation (Chat, Konferenzanruf usw.) für ein schnelles Brainstorming unerlässlich ist. Ich hoffe, Sie können das berücksichtigen.

Programmiermodell

Ich werde zuerst über das resultierende Programmiermodell für Benutzer der API sprechen.

Streaming verschlüsseln

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

Streaming entschlüsseln

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

Nicht-Streaming

Da Nicht-Streaming ein Sonderfall des Streamings ist, können wir den obigen Benutzercode in Hilfsmethoden auf AuthenticatedSymmetricAlgorithm (unten definiert) einpacken, um eine einfachere API bereitzustellen. dh

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); 
  ...
}

Dies kann auch als Präsentation einer einfacheren API dienen

Nicht-Streaming-Verschlüsselung

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

Nicht-Streaming-Entschlüsselung

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

Unter der Haube

Wenn man sich die Corefx-Quelle ansieht, ist Span<> überall. Dazu gehört System.Security.Cryptography.* - mit Ausnahme von symmetrischen Chiffren, also können wir diese zuerst reparieren und die authentifizierte Verschlüsselung darüber legen.

1. Erstellen Sie ICipherTransform für Span-E/A

Dies ist wie eine Span-fähige Version von ICryptoTransform . Ich würde nur die Schnittstelle selbst als Teil des Framework-Upgrades ändern, aber da die Leute deswegen empfindlich werden können, nenne ich es 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);
}

Markieren Sie außerdem ICryptoTransform als [Obsolete]

Höflich gegenüber Personen mit Vorkenntnissen in .NET-Krypto zu sein

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

2. Erweitern Sie die vorhandene SymmetricAlgorithm -Klasse für Span-E/A

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. Erweitern Sie das vorhandene CryptoStream für Span-E/A

Dies ist genau wie Stream in System.Runtime. Außerdem werden wir einen c'tor für unseren AEAD-Fall hinzufügen, der folgen wird.

KRITISCH: CryptoStream benötigt ein obligatorisches Upgrade in FlushFinalBlock , um das Tag während der Verschlüsselung am Ende des Streams hinzuzufügen und das Tag (TagSize-Bytes) während der Entschlüsselung automatisch zu extrahieren . Dies ähnelt anderen kampferprobten APIs wie Javas Cryptographic Architecture oder C# BouncyCastle. Dies ist unvermeidlich, aber der beste Ort, um dies zu tun, da beim Streaming das Tag am Ende erzeugt wird, aber nicht benötigt wird, bis der letzte Block während der Entschlüsselung transformiert wird. Der Vorteil ist, dass es das Programmiermodell erheblich vereinfacht.

Hinweis: 1) Mit CBC-HMAC können Sie das Tag zuerst verifizieren. Es ist die sicherere Option, aber wenn ja, ist es tatsächlich ein Zwei-Pass-Algorithmus. Der 1. Durchgang berechnet das HMAC-Tag, dann führt der 2. Durchgang tatsächlich die Entschlüsselung durch. Der Speicherstrom oder Netzwerkstrom muss also immer im Speicher gepuffert werden, was ihn auf das One-Shot-Modell reduziert; nicht streamen. Echte AEAD-Algorithmen wie GCM oder CCM können effizient streamen.

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`
    ...
  }

  ...
}

Schicht in der authentifizierten Verschlüsselung

Mit dem Obigen können wir die fehlenden Bits hinzufügen, um eine authentifizierte Verschlüsselung mit zugehörigen Daten (AEAD) zu ermöglichen.

Verlängern Sie das neue ICipherTransform für AEAD

Dadurch kann CryptoStream seine Arbeit richtig erledigen. Wir können auch die IAuthenticatedCipherTransform -Schnittstelle verwenden, um unsere eigene benutzerdefinierte Streaming-Klasse/-Nutzung zu implementieren, aber die Arbeit mit CryptoStream sorgt für eine super zusammenhängende und konsistente .net-Krypto-API.

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

Basisklasse der authentifizierten Verschlüsselung

Erweitert einfach 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-Klasse

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 Ich begrüße die Bemühungen.

Streaming-Encrypt über GCM ist machbar. Streaming-Decrypt über GCM ist

  • in NIST 800-38d nicht erlaubt. Abschnitt 5.2.2 „Authentifizierte Entschlüsselungsfunktion“ macht deutlich, dass die Rückgabe des entschlüsselten Klartextes P eine korrekte Authentifizierung über das Tag T implizieren muss.
  • nicht sicher. Es gibt eine Sicherheitsvorstellung, dass Algorithmen in der Einstellung „Release of Unverified Plaintext“ (RUP) sicher sind. Die RUP-Sicherheit wurde 2014 in einem Papier von Andreeva-et-al. formalisiert. GCM ist in der RUP-Einstellung nicht sicher. Der CAESAR-Wettbewerb, bei dem jeder Beitrag mit GCM verglichen wird, listet die RUP-Sicherheit als wünschenswerte Eigenschaft auf. Nicht verifizierter Klartext, der von GCM veröffentlicht wird, ist trivial anfällig für Bit-Flipping-Angriffe.

Früher in diesem Thread wurde die Möglichkeit asymmetrischer Verschlüsselungs-/Entschlüsselungs-APIs angesprochen (konzeptionell), und ich denke, der Konsens war, dass dies eine sehr schlechte Idee wäre.

Zusammenfassend lässt sich sagen, dass Sie keine bytegranulare Streaming-API auf hoher Ebene für die GCM-Entschlüsselung haben können. Ich habe es schon oft gesagt, und ich sage es noch einmal. Die einzige Möglichkeit, eine Streaming-API zu haben, ist die Chunked-Verschlüsselung. Ich erspare allen das Karussell mit der Chunked-Verschlüsselung.

Was auch immer MS für die GCM-API beschließt, RUP kann nicht zugelassen werden.

@sdrapkin RUP wurde hier ausführlich besprochen und wir haben diese Brücke bereits überschritten. Kurz gesagt bedeutet RUP, dass entschlüsselte Daten nicht verwendet werden müssen, bis das Tag verifiziert ist, aber wie bei Java JCE, WinNT bcrypt, OpenSSL usw. muss es nicht an der Methodengrenze erzwungen werden. Wie bei den meisten Krypto-Primitiven, insbesondere denen auf niedriger Ebene, mit Vorsicht verwenden.

^^^ so viel. Ich stimme den Stream-APIs usw. auf höherer Ebene zu und erzwinge sie dann in Ordnung. Aber wenn ich ein primitives Element auf niedriger Ebene verwenden möchte, muss ich in der Lage sein, Dinge wie geteilte Puffer usw. zu verwenden, und es liegt an mir, sicherzustellen, dass die Daten nicht verwendet werden. Werfen Sie eine Ausnahme in den Tag-Berechnungs-/Prüfpunkt, aber behindern Sie keine primitiven Elemente auf niedriger Ebene.

Es liegt an mir, dafür zu sorgen, dass die Daten nicht verwendet werden

Falsch. Es liegt nicht an dir. AES-GCM hat eine sehr _spezifische_ Definition, und diese Definition stellt sicher, dass es nicht an Ihnen liegt. Was Sie wollen, ist ein separates AES-CTR-Primitive und ein separates GHASH-Primitive, die Sie dann nach Belieben kombinieren und anwenden können. Aber wir diskutieren nicht über separate AES-CTR- und GHASH-Primitive, oder? Wir diskutieren über AES-GCM. Und AES-GCM erfordert , dass RUP nicht erlaubt ist.

Ich schlage auch vor, die Antwort von Ilmari Karonen von crypto.stackexchange zu lesen.

@sdrapkin Du machst einen guten Punkt. Es wäre jedoch wünschenswert, schließlich einen Algorithmus zu haben, der unter RUP sicher ist, und dass dieser Algorithmus zu der API passt, über die hier entschieden wird. Wir müssen uns also entscheiden:

  1. Die API unterstützt kein Streaming. Einfach, aber ohne Low-Level-API. Wir könnten dies eines Tages bereuen.
  2. Die AES-GCM- Implementierung verhindert Streaming und hält sich an die Spezifikation, ohne die API einzuschränken.

Können wir das Streaming-Szenario erkennen und eine Ausnahme auslösen, die erklärt, warum diese Verwendung falsch ist? Oder müssen wir darauf zurückgreifen, dass die Streaming-Implementierung den gesamten Puffer verbraucht? Letzteres wäre unglücklich: Sie denken vielleicht, dass Sie streamen, aber das sind Sie nicht.

Wir könnten eine SupportsStreaming(out string whyNot) -Methode hinzufügen, die von der Streaming-Implementierung überprüft wird.

Haben wir ein solides Argument gegen Streaming/Tag-at-the-End im Allgemeinen ? Wenn nicht, dann sollten wir meiner Meinung nach darauf abzielen, dies mit der API nicht auszuschließen.

@sdrapkin : Lassen Sie uns einen breiteren Blick auf RUP werfen, da dies eine Bibliothek und keine Anwendung ist. Dies ist also eher ein Pufferungs- und Layer-Design-Problem als die tatsächliche Freigabe/Verwendung von nicht verifizierten Daten. Wenn wir uns die NIST-Sonderveröffentlichung 800-38D für AES GCM ansehen, sehen wir das

  1. Die GCM-Spezifikation definiert die maximale Klartextlänge mit 2^39-256 Bit ~ 64 GB. Das Puffern irgendwo in der Nähe davon im Systemspeicher ist unvernünftig.

  2. Die GCM-Spezifikation definiert die Ausgabe als FAIL, wenn das Tag fehlschlägt. Es ist jedoch nicht vorgeschrieben, welche Schicht in einer Implementierung bis zur Tag-Verifizierung puffern muss. Schauen wir uns einen Call-Stack wie folgt an:

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)

Wo
A ist AES GCM auf der Anwendungsschicht
B ist AES-GCM auf der Sprachschicht
C ist AES-GCM auf der Plattformebene

Der Klartext wird bei (A) freigegeben, wenn das Tag auscheckt, aber andernfalls FAIL zurückgegeben wird. Es wird jedoch absolut nirgendwo in der Spezifikation darauf hingewiesen, dass der Hauptspeicher der einzige Ort ist, an dem Klartext in Bearbeitung gepuffert wird, oder dass die Pufferung bei (B) oder (C) oder anderswo erfolgen sollte. Tatsächlich OpenSSL, Windows NT Bcrypt am Beispiel von (C), wo Streaming das Puffern auf der höheren Schicht erlaubt. Und Java JCA, CLR Security von Microsoft und mein obiger Vorschlag sind Beispiele für (B), wo Streaming das Puffern auf der Anwendungsebene zulässt. Es ist anmaßend anzunehmen, dass Designer von A keine besseren Pufferfähigkeiten haben, bevor sie Klartext veröffentlichen. Dieser Puffer könnte theoretisch und in der Praxis Speicher oder SSDs oder ein Speichercluster im gesamten Netzwerk sein. Oder Lochkarten ;) !

Selbst wenn man die Pufferung beiseite lässt, diskutiert das Papier andere praktische Bedenken (siehe Abschnitt 9.1, Designüberlegungen und 9.2, Betriebsüberlegungen) wie Schlüsselaktualität oder IV-Nichtwiederholung bei unbestimmten Stromausfällen. Wir werden dies offensichtlich nicht in Schicht B backen, dh hier.

@timovzl Der obige jüngste Vorschlag behandelt beide Szenarien - One-Shot (Architekt kümmert sich nicht um Pufferung) sowie Streaming (Architekt hat bessere Pufferfunktionen). Solange in der Low-Level-Streaming-API- Dokumentation klar ist, dass der Verbraucher jetzt für die Pufferung verantwortlich ist, gibt es keine Verringerung des Sicherheitsnachweises und keine Abweichung von der Spezifikation.

BEARBEITEN: Grammatik, Tippfehler und der Versuch, Markdown zum Laufen zu bringen

Bingo .. Auch hier ist es die Entscheidung der Layer-Designer, wo die Tag-Überprüfung stattfindet. Ich plädiere in keiner Weise dafür, unverifizierte Daten auf der Anwendungsschicht freizugeben.

Die Diskussion dreht sich darum, ob diese APIs auf "Verbraucherebene" oder echte Primitive sind. Wenn es sich um echte Primitive handelt, sollten sie die Funktionalität offenlegen, damit "sicherere" APIs auf höherer Ebene darauf aufgebaut werden können. Es wurde bereits oben bei der Nonce-Diskussion entschieden, dass dies echte Primitive sein sollten, was bedeutet, dass Sie sich in den Fuß schießen könnten, ich denke, das gleiche gilt für die Streaming-/partielle Chiffretext-Decodierung.

Um dies zu sagen, ist es unerlässlich, schnell die "höheren" und sichereren APIs bereitzustellen, um zu verhindern, dass die Leute ihre eigenen auf diese "rollen".

Mein Interesse kommt von Netzwerken/Pipelines, und wenn Sie keine Teilpuffer erstellen können und "One-Shot" ausführen müssen, gibt es keinen Vorteil für diese APIs, nur der Nachteil, also würde ich direkt zu BCrypt/OpenSsl usw. gehen.

Mein Interesse kommt von Netzwerken/Pipelines, und wenn Sie keine Teilpuffer erstellen können und "One-Shot" ausführen müssen, gibt es keinen Vorteil für diese APIs, nur der Nachteil, also würde ich direkt zu BCrypt/OpenSsl usw. gehen.

Exakt. Die Notwendigkeit wird nicht verschwinden, also werden die Leute andere Implementierungen verwenden oder ihre eigenen entwickeln. Das ist nicht unbedingt ein sichereres Ergebnis, als das Streaming mit einer guten Warndokumentation zuzulassen.

@Timovzl , ich denke, wir haben viele technische Rückmeldungen und Designanforderungen rund um den letzten Vorschlag erhalten. Gedanken zur Implementierung und Veröffentlichung?

@Sidshetye hat einen detaillierten Vorschlag gemacht, der meiner Meinung nach alle Anforderungen erfüllt. Die einzige Kritik bezüglich RUP wurde ohne weiteren Widerspruch angegangen. (Insbesondere kann RUP in einer von mehreren Schichten verhindert werden, und die Low-Level-API sollte nicht vorschreiben, welche; und das Anbieten von _kein_ Streaming wird voraussichtlich schlimmere Auswirkungen haben.)

Im Interesse des Fortschritts möchte ich jeden einladen, der weitere Bedenken bezüglich des neuesten Vorschlags hat, sich zu Wort zu melden – und natürlich Alternativen anzubieten.

Ich bin begeistert von diesem Vorschlag und davon, dass eine API Gestalt annimmt.

@Sidshetye , ich habe einige Fragen und Anregungen:

  1. Ist es wünschenswert, von den bestehenden SymmetricAlgorithm zu erben? Gibt es vorhandene Komponenten, die wir integrieren möchten? Wenn mir bei diesem Ansatz kein Vorteil fehlt, würde ich lieber AuthenticatedEncryptionAlgorithm ohne Basisklasse sehen. Nicht zuletzt vermeidet es die Offenlegung unerwünschter CreateEncryptor / CreateDecryptor (nicht authentifizierter!) Methoden.
  2. Keine der beteiligten Komponenten ist mit asymmetrischer Krypto nutzbar, ja? Fast alle Komponenten lassen "Symmetric" in ihren Namen weg, dem stimme ich zu. Wenn wir SymmetricAlgorithm nicht weiter erben, könnte AuthenticatedSymmetricAlgorithm in AuthenticatedEncryptionAlgorithm umbenannt werden, wobei der herkömmliche Begriff Authenticated Encryption beibehalten wird.
  3. Ändern Sie TryEncrypt / TryDecrypt , um in das Tag zu schreiben / es zu empfangen, anstatt eine einstellbare Tag -Eigenschaft für den Algorithmus zu haben.
  4. Was ist der Zweck, key , iv und authenticatedAdditionalData über öffentliche Setter zu setzen? Ich würde mich von mehreren gültigen Ansätzen und veränderlichen Eigenschaften so weit wie möglich fernhalten. Könnten Sie ohne sie ein aktualisiertes Angebot erstellen?
  5. Wollen wir überhaupt einen Staat in AesGcm ? Mein Instinkt ist, iv und authenticatedAdditionalData auf jeden Fall auszulassen, da diese pro Nachricht gelten. Die key könnten sich als Zustand lohnen, da wir im Allgemeinen mehrere Operationen mit einer einzigen Taste ausführen möchten. Es ist jedoch auch möglich, den Schlüssel pro Anruf zu übernehmen. Dieselben Fragen gelten für CreateAuthenticatorEncryptor . In jedem Fall sollten wir uns auf _einen Weg_ einigen, um die Parameter zu übergeben. Ich bin gespannt, Vor- und Nachteile zu diskutieren. Ich neige zum Schlüsselzustand in AesGcm und der Rest in CreateAuthenticatedEncryptor bzw. TryEncrypt . Wenn wir uns bereits einig sind, zeigen Sie uns bitte einen aktualisierten Vorschlag. :-)
  6. ICipherTransform sollte wahrscheinlich eine abstrakte Klasse sein, CipherTransform , damit Methoden hinzugefügt werden können, ohne bestehende Implementierungen zu beschädigen.
  7. Alle Funktionsparameter sollten camelCase verwenden, dh in Kleinbuchstaben beginnen. Sollen wir auch authenticatedData oder authenticatedAdditionalData sagen? Außerdem denke ich, dass wir die Parameternamen plaintext und ciphertext wählen sollten.
  8. Wo immer die IV übergeben wird, würde ich sie gerne als optionalen Parameter sehen, der es einfacher macht, eine richtig generierte (kryptozufällige) IV zu erhalten, als unsere eigene bereitzustellen. Erschwert zumindest den Missbrauch der Low-Level-API, und wir bekommen das kostenlos.
  9. Ich versuche immer noch herauszufinden, wie der Client-Code von TryEncrypt die erforderliche Span-Länge kennen kann, um ciphertext bereitzustellen! Gleiches gilt für TryDecrypt und die Länge von plaintext . Sicherlich sollen wir sie nicht in einer Schleife bis zum Erfolg ausprobieren und die Länge nach jeder fehlgeschlagenen Iteration verdoppeln?

Wenn Sie abschließend vorausdenken, wie könnte eine darauf aufbauende High-Level-API aussehen? Betrachtet man nur die API-Nutzung, scheint es wenig Raum für Verbesserungen zu geben, da sowohl die Streaming- als auch die Nicht-Streaming-APIs bereits so einfach sind! Die Hauptunterschiede, die ich mir vorstelle, sind eine automatische IV, automatische Ausgabegrößen und möglicherweise eine Begrenzung der verschlüsselten Datenmenge.

Windows erlaubt Streaming. OpenSSL auch. Beide haben es größtenteils in bestehende Konzepte eingeordnet (obwohl sie beide einen Schraubenschlüssel mit „und da ist dieses Ding auf der Seite, mit dem Sie sich befassen müssen, oder ich werde einen Fehler machen“).

Go nicht und libsodium nicht.

Es scheint, als ob die erste Welle es erlaubte und spätere nicht. Da wir uns unbestreitbar in einer späteren Welle befinden, denke ich, dass wir dabei bleiben werden, dies nicht zuzulassen. Wenn es nach der Einführung eines One-Shot-Modells (zur Verschlüsselung/Entschlüsselung, der Schlüssel kann über Anrufe hinweg beibehalten werden) eine erhöhte Nachfrage nach Streaming gibt, können wir eine Neubewertung vornehmen. Daher scheint ein API-Vorschlag, der sich an dieses Muster hält, vorteilhaft. Obwohl weder SIV noch CCM Streaming-Verschlüsselung unterstützen, ist die Streaming-API für sie möglicherweise stark puffernd. Es scheint besser, die Dinge klar zu halten.

Vorschläge sollten das Tag auch nicht in die Nutzdaten einbetten (GCM und CCM bezeichnen es als separates Datum), es sei denn, der Algorithmus selbst (SIV) integriert es in die Ausgabe der Verschlüsselung. ( E(...) => (c, t) vs. E(...) => c || t oder E(...) => t || c ). Benutzer der API können es sicherlich als Concat verwenden (öffnen Sie einfach die Spans entsprechend).

Die GCM-Spezifikation erlaubt keine Freigabe von etwas anderem als FAIL bei Tag-Nichtübereinstimmung. NIST ist darüber ganz klar. Das Original-GCM-Papier von McGrew & Viega sagt auch:

Die Entschlüsselungsoperation würde eher FAIL als den Klartext zurückgeben, und die Entkapselung würde anhalten und der Klartext würde verworfen, anstatt weitergeleitet oder weiter verarbeitet zu werden.

Keiner der vorherigen Kommentare hat RUP angesprochen – sie haben es lediglich per Hand weggewunken („die höhere Schicht wird sich darum kümmern“ – ja, richtig).

Ganz einfach: GCM-Verschlüsselung kann streamen. Die GCM-Entschlüsselung kann nicht gestreamt werden. Alles andere ist nicht mehr GCM.

Es scheint, als ob die erste Welle es erlaubte und spätere nicht. Da wir uns unbestreitbar in einer späteren Welle befinden, denke ich, dass wir dabei bleiben werden, dies nicht zuzulassen.

@bartonjs Sie ignorieren buchstäblich alle technischen und logischen Analysen und verwenden stattdessen die Daten der Go- und Libsodium-Projekte als schwachen Proxy für die tatsächliche Analyse? Stellen Sie sich vor, ich argumentiere ähnlich, basierend auf den Namen der Projekte. Außerdem entscheiden wir über die Schnittstelle UND Implementierungen. Ihnen ist klar, dass die Entscheidung für eine Nicht-Streaming- Schnittstelle für AEAD alle derartigen Implementierungen in der Zukunft ausschließt, oder?

Wenn es nach der Einführung eines One-Shot-Modells (zur Verschlüsselung/Entschlüsselung, der Schlüssel kann über Anrufe hinweg beibehalten werden) eine erhöhte Nachfrage nach Streaming gibt, können wir eine Neubewertung vornehmen.

Warum ist die bisher gezeigte Nachfrage auf GitHub unzureichend? Es kommt an den Punkt, an dem es vollkommen skurril erscheint, weniger Arbeit leisten zu müssen, als aufgrund technischer oder kundenbezogener Anforderungen.

@bartonjs Sie ignorieren buchstäblich alle technischen und logischen Analysen und verwenden stattdessen die Daten der Go- und Libsodium-Projekte als schwachen Proxy für die tatsächliche Analyse?

Nein, ich verwende den Rat professioneller Kryptografen, die sagen, dass es außerordentlich gefährlich ist und dass wir das Streamen von AEAD vermeiden sollten. Dann verwende ich Informationen vom CNG-Team von „vielen Leuten sagen, dass sie es theoretisch wollen, aber in der Praxis tut es fast niemand“ (ich weiß nicht, wie viel davon Telemetrie im Vergleich zu anekdotischen Hilfsanfragen ist). Die Tatsache, dass andere Bibliotheken den One-Shot-Weg gegangen sind, _verstärkt_ einfach die Entscheidung.

Warum ist die bisher gezeigte Nachfrage auf GitHub unzureichend?

Einige Szenarien wurden erwähnt. Die Verarbeitung fragmentierter Puffer könnte wahrscheinlich durch das Akzeptieren ReadOnlySequence angegangen werden, wenn es den Anschein hat, dass es genug Szenario gibt, um eine Komplizierung der API zu rechtfertigen, anstatt dass der Aufrufer die Daten wieder zusammensetzt.

Große Dateien sind ein Problem, aber große Dateien sind bereits ein Problem, da GCM eine Grenze von knapp 64 GB hat, was "nicht allzu groß" ist (okay, es ist ziemlich groß, aber es ist nicht das "Wow, das ist groß". ich war). Speicherabgebildete Dateien würden die Verwendung von Spans (von bis zu 2^31-1) ermöglichen, ohne dass 2 GB RAM erforderlich wären. Also haben wir ein paar Bits vom Maximum abgespeckt ... das würde wahrscheinlich sowieso im Laufe der Zeit passieren.

Ihnen ist klar, dass die Entscheidung für eine Nicht-Streaming-Schnittstelle für AEAD alle derartigen Implementierungen in der Zukunft ausschließt, oder?

Ich bin mehr und mehr davon überzeugt, dass @GrabYourPitchforks Recht hatte (https://github.com/dotnet/corefx/issues/23629#issuecomment-334638891), dass es wahrscheinlich keine vernünftige vereinheitlichende Schnittstelle gibt. GCM _requiring_ a nonce/IV und SIV _forbidding_ bedeutet, dass die Initialisierung eines AEAD-Modus/-Algorithmus bereits Wissen darüber erfordert, was passieren wird ... es gibt nicht wirklich eine "abstrahierte" Vorstellung von AEAD. SIV diktiert, wohin "das Tag" geht. GCM/CCM nicht. SIV ist Tag-First, per Spezifikation.

SIV kann nicht mit der Verschlüsselung beginnen, bis es alle Daten hat. Die Streaming-Verschlüsselung wird also entweder werfen (was bedeutet, dass Sie wissen müssen, dass Sie sie nicht aufrufen) oder puffern (was zu n ^ 2-Operationszeit führen kann). CCM kann erst beginnen, wenn die Länge bekannt ist; aber CNG erlaubt keinen Vorverschlüsselungshinweis auf die Länge, also ist es im selben Boot.

Wir sollten keine neue Komponente entwerfen, bei der es einfacher ist, standardmäßig das Falsche als das Richtige zu tun. Die Streaming-Entschlüsselung macht es sehr einfach und verlockend, eine Stream-Klasse zu verdrahten (ähnlich wie Ihr Vorschlag, dies mit CryptoStream zu tun), wodurch es sehr einfach wird, einen Datenvalidierungsfehler zu erhalten, bevor das Tag überprüft wird, was den Vorteil von AE fast vollständig zunichte macht . ( IGcmDecryptor => CryptoStream => StreamReader => XmlReader => "Warte, das ist kein legales XML..." => adaptives Chiffretext-Orakel) .

Es kommt auf den Punkt ... Kundennachfrage.

Wie ich leider schon viel zu oft in meinem Leben gehört habe: Es tut mir leid, aber Sie sind nicht der Kunde, an den wir denken. Ich gebe zu, dass Sie vielleicht wissen, wie man GCM sicher durchführt. Sie wissen, dass Sie bis nach der Tag-Überprüfung nur in eine flüchtige Datei/Puffer/usw. streamen dürfen. Sie wissen, was Nonce-Management bedeutet, und Sie kennen die Risiken, Fehler zu machen. Sie wissen, dass Sie auf die Stream-Größen achten und nach 2 ^ 36-64 Bytes auf ein neues GCM-Segment wechseln müssen. Sie wissen, dass es letztendlich Ihr Fehler ist, wenn Sie diese Dinge falsch machen.

Der Kunde, an den ich hingegen denke, ist jemand, der weiß, „das muss ich verschlüsseln“, weil sein Chef es ihm gesagt hat. Und sie wissen, dass bei der Suche nach der Verschlüsselung in einem Tutorial "immer AE verwenden" und GCM erwähnt wird. Dann finden sie ein Tutorial "Verschlüsselung in .NET", das CryptoStream verwendet. Sie schließen dann die Pipeline an, ohne zu ahnen, dass sie gerade dasselbe getan haben wie SSLv2 gewählt haben ... theoretisch ein Kästchen angekreuzt, aber nicht wirklich in der Praxis. Und wenn sie es tun, gehört _dieser_ Fehler jedem, der es besser wusste, aber es zu einfach war, das Falsche zu tun.

Sie sind nicht der Kunde, an den wir denken [...] Der Kunde, an den ich denke, ist jemand, der weiß "Ich muss das verschlüsseln", weil sein Chef ihm gesagt hat, er soll [...]

@bartonjs Vor Monaten hatten wir bereits entschieden, dass das Ziel darin bestand, zwei Kundenprofile anzusprechen, indem wir eine Low-Level-API (leistungsfähig, aber unter bestimmten Bedingungen unsicher) und eine High-Level-API (narrensicher) haben. Es steht sogar im Titel. Es ist sicherlich ein freies Land, aber es ist unaufrichtig, jetzt den Torpfosten zu verschieben, indem man etwas anderes behauptet.

Der Kunde, an den ich hingegen denke, ist jemand, der weiß, „das muss ich verschlüsseln“, weil sein Chef es ihm gesagt hat. Und sie wissen, dass bei der Suche nach der Verschlüsselung in einem Tutorial "immer AE verwenden" und GCM erwähnt wird. Dann finden sie ein Tutorial "Verschlüsselung in .NET", das CryptoStream verwendet. Sie schließen dann die Pipeline an, ohne zu ahnen, dass sie gerade dasselbe getan haben wie SSLv2 gewählt haben ... theoretisch ein Kästchen angekreuzt, aber nicht wirklich in der Praxis. Und wenn sie es tun, gehört dieser Fehler jedem, der es besser wusste, aber es zu einfach war, das Falsche zu tun.

@bartonjs Warte, was ist mit einem primitiven Element auf niedriger Ebene passiert? Ich dachte, das Ziel dieser speziellen Ausgabe sei Flexibilität gegenüber dem Babysitten. Teilen Sie uns auf jeden Fall mit, wenn sich der Plan geändert hat, damit wir alle über dasselbe sprechen.

Werden auch noch Per-Block-Methoden in Betracht gezogen oder nur One-Shot-Methoden?

Ich bin immer mehr davon überzeugt, dass @GrabYourPitchforks Recht hatte (#23629 (Kommentar)), dass es wahrscheinlich keine vernünftige vereinheitlichende Schnittstelle gibt.

Wenn man sich all die Beispiele ansieht, sieht dies immer sinnloser aus - insbesondere für eine Low-Level-API, bei der die Implementierungen so unterschiedliche Einschränkungen haben. Vielleicht sollten wir mit AES-GCM eher ein solides Beispiel geben als eine vereinheitlichende Schnittstelle. Als Nebenbemerkung könnte letzteres für eine zukünftige High-Level-API noch interessant sein. Seine Eigenschaft, restriktiver zu sein, wird es wahrscheinlich erleichtern, dort eine vereinheitlichende Schnittstelle zu erreichen.

werden noch Per-Block-Methoden in Betracht gezogen oder nur One-Shot-Methoden?

Wie in https://github.com/dotnet/corefx/issues/23629#issuecomment -378605071 erwähnt, sind wir der Meinung, dass Risiko vs. Belohnung vs. ausdrückliche Anwendungsfälle besagen, dass wir nur One-Shot-Versionen von AE zulassen sollten.

Ich habe nicht die gesamte Diskussion gelesen, nur zufällige Teile. Ich weiß nicht, in welche Richtung du gehst. Tut mir leid, wenn das, was ich schreibe, in diesem Zusammenhang keinen Sinn ergibt. Meine 2¢:

  • Streams sind wichtig. Wenn Sie sie nicht direkt unterstützen können, weil dies eine Sicherheitslücke bedeuten würde, stellen Sie, wenn möglich, einen Wrapper auf höherer Ebene bereit, der auf Ihrer Low-Level-API aufbaut und Streams offenlegt (auf ineffiziente, aber sichere Weise).
  • Wenn AES-GCM in keinem Szenario Streams verwenden kann, stellen Sie eine legitime Implementierung von AES-CBC-HMAC bereit, die auf Streams basiert. Oder ein anderer AE-Algorithmus.
  • Je höher das Niveau, desto besser. Je weniger Bereiche vorhanden sind, in denen der Benutzer einen Fehler machen kann, desto besser. Bedeutung – API verfügbar machen, die so viel Zeug wie möglich verbergen würde (zum Beispiel dieses Authentifizierungs-Tag). Natürlich kann (sollte) es auch spezifischere Überladungen geben.
  • IMHO machen Sie sich nicht die Mühe, Schnittstellen mit anderen Verschlüsselungsdiensten zu vereinheitlichen, wenn sie einfach nicht passen. Das hat openssl mit ihrer CLI gemacht und das Ergebnis ist schlecht (z. B. keine Möglichkeit, das Authentifizierungs-Tag bereitzustellen).

Wir (das .NET-Sicherheitsteam) haben uns untereinander und mit dem breiteren Krypto-Team von Microsoft beraten. Wir haben viele der in diesem Thread erwähnten Probleme und Bedenken diskutiert. Letztendlich waren diese Bedenken nicht überzeugend genug, um die Einführung einer Streaming-GCM-API als Kernbaustein innerhalb des Frameworks zu rechtfertigen.

Diese Entscheidung kann bei Bedarf in der Zukunft revidiert werden. Und in der Zwischenzeit haben wir die Dinge nicht schlechter gemacht als heute: Entwickler, die derzeit eine Kryptobibliothek eines Drittanbieters für das Streaming von GCM-Unterstützung verwenden, können dies weiterhin tun, und sie werden nicht durch unsere beabsichtigte Einführung von a Nicht-Streaming-GCM-API.

Wie geht man mit der Verschlüsselung von Daten um, die nicht in den Speicher passen?

@pgolebiowski Sie verwenden High-Level-.NET- Kryptobibliotheken, die speziell für eine sichere Streaming-Verschlüsselung entwickelt wurden.

@sdrapkin das ist leichter gesagt als getan. "sicher" ist viel verlangt. Was ist bewiesen und kann man sich wirklich anvertrauen? du sagst selbst:

Bouncy Castle c#-Bibliothek (eine typische StackOverflow-Empfehlung). Bouncy Castle c# ist ein riesiger (145k LOC), schlecht funktionierender Museumskatalog von Krypto (einige davon uralt), mit alten Java-Implementierungen, die auf das ebenso alte .NET (2.0?) portiert wurden.

ok, was sind die möglichkeiten? vielleicht Ihre eigene Bibliothek? nicht wirklich . hmm... vielleicht libsodium-net? nicht wirklich .

Wenn Sie tatsächlich nach einer geprüften Bibliothek suchen, die aus einer ziemlich vertrauenswürdigen Quelle stammt (wie Microsoft oder ausgiebig von der Community verwendet wird), glaube ich nicht, dass eine solche Bibliothek in der .NET Core-Welt existiert.


  • Kunde: authentifizierte Verschlüsselung von Daten, die nicht in den Speicher passen?
  • Microsoft: Es tut mir leid, aber Sie sind nicht der Kunde, an den wir denken. Verwenden Sie eine Bibliothek, die nicht geprüft wird und deren Sicherheit fraglich ist, nicht unser Problem, wenn Sie einem Seitenkanalangriff ausgesetzt sind.
  • Kunde:

@pgolebiowski Die Optionen bestehen darin, ein etabliertes .NET-Framework zu verwenden - dh. genau das, was sich bewährt hat und dem Sie vertrauen können, wie Sie es wünschen. Andere Bibliotheken (einschließlich meiner) warten darauf, dass Microsoft fehlende Krypto-Primitive wie ECDH in NetStandard hinzufügt.

Sie können sich auch Inferno -Gabeln ansehen. Es gibt mindestens 2 Forks, bei denen ein paar triviale Änderungen NetStandard20 erreichten.

Ihre Bibliothek wurde vor 2 Jahren innerhalb von 2 Tagen von einem Mann geprüft. Dem traue ich nicht, tut mir leid. Bei Microsoft gäbe es dafür ein eigenes Team – aus Leuten, denen ich mehr vertraue als denen von Cure53.

Im Ernst, wir können für viele Dinge über die Unterstützung von Drittanbietern sprechen. Aber alle erforderlichen sicherheitsrelevanten Dinge sollten von der Standardbibliothek bereitgestellt werden.

@pgolebiowski Es liegt mir fern, jemanden davon zu überzeugen, irgendetwas zu vertrauen, aber Ihre Aussage ist nicht korrekt. Inferno wurde von 2 Fachleuten der Organisation "Cure53" geprüft. Die Prüfung dauerte zwei Tage, und die gesamte Bibliothek umfasste etwa 1.000 Codezeilen. Das sind ~250 Codezeilen pro Prüfer/Tag – ziemlich überschaubar.

Tatsächlich gehört die einfache Überprüfbarkeit zu den Hauptmerkmalen von Inferno, gerade für diejenigen, die nicht vertrauen wollen.

Das Ziel dieses Threads ist es, Unterstützung für AES-GCM hinzuzufügen. Ihre Bibliothek unterstützt nicht einmal AES-GCM. Wenn Sie möchten, dass Ihr Code verwendet wird, reichen Sie ihn als Vorschlag für Corefx ein. In einem passenden Thread.

Eine andere Sache, auch wenn es diesen Algorithmus unterstützt – es wurde nicht vom .net Crypto Board überprüft und ist kein Teil des Corefx. Es ist nicht einmal ein Kandidat für eine solche Überprüfung. Das bedeutet das Ende dieser sinnlosen Diskussion und Werbung.

@pgolebiowski Ich habe nichts beworben - lediglich Ihre Frage beantwortet, Alternativen für Ihren Anwendungsfall vorgeschlagen und ungenaue Behauptungen korrigiert. AES-GCM ist nicht für die Streaming-Verschlüsselung geeignet, und das wurde vom .NET-Sicherheitsteam überprüft und vereinbart, sodass Sie darauf vertrauen können .

verteidige ich das Streaming von AES-GCM irgendwo? kann mich nicht erinnern. kann mich aber erinnern, gesagt zu haben:

  • Streams sind wichtig. Wenn Sie sie nicht direkt unterstützen können, weil dies eine Sicherheitslücke bedeuten würde, stellen Sie, wenn möglich, einen Wrapper auf höherer Ebene bereit, der auf Ihrer Low-Level-API aufbaut und Streams offenlegt (auf ineffiziente, aber sichere Weise).
  • Wenn AES-GCM in keinem Szenario Streams verwenden kann, stellen Sie eine legitime Implementierung von AES-CBC-HMAC bereit, die auf Streams basiert. Oder ein anderer AE-Algorithmus.

oder Angabe eines offenen Problems für Corefx:

Wie geht man mit der Verschlüsselung von Daten um, die nicht in den Speicher passen?


Bonus:

Sie können sich auch Inferno-Gabeln ansehen. Es gibt mindestens 2 Forks, bei denen ein paar triviale Änderungen NetStandard20 erreichten. [...] Inferno wurde von 2 Fachleuten der Organisation "Cure53" auditiert [...] einfache Auditierbarkeit gehört zu den Hauptmerkmalen von Inferno, gerade für diejenigen, die nicht vertrauen wollen.

Ich habe nichts beworben

Nur so nebenbei wollte ich nie wirklich "streamen" in dem Sinne, wie die meisten denken. Ich wollte nur eine "Block" -Verarbeitung aus Gründen der Leistung und des Speicherverbrauchs. Ich möchte die Ergebnisse eigentlich nicht "streamen". Dies ist, was die Unterstützung von openssl und cng zu verlieren scheint, und macht die "Primativen" in jedem Szenario, das mir einfällt, im Grunde nutzlos.

@Drawaes Wenn ich darüber nachdenke, könnte der Blockbetrieb viel sicherer sein als die Verwendung von Streams. Ein Laie mag Streams anfassen, aber er wird viel lieber die One-Shot-API verwenden als den Blockbetrieb. Außerdem kann die Blockoperation nicht _einfach_ mit beispielsweise XmlReader kombiniert werden. Viele der besprochenen Gefahren beziehen sich also tatsächlich auf Stream-Objekte, aber nicht auf den Blockbetrieb.

Mit dem Blockbetrieb zu arbeiten, wenn auch eine One-Shot-API verfügbar ist, legt nahe, dass wir wissen, was wir tun, und dass wir speziell Low-Level-Optimierungen benötigen. Wir könnten den Laien schützen _und_ Flexibilität haben.

Was die Vermeidung von RUP betrifft, so denke ich immer noch darüber nach, wie viel Vorteil der Blockbetrieb wirklich für GCM ist. Die Verschlüsselung erntet den vollen Nutzen, während die Entschlüsselung nur etwas davon profitiert. Wir können vermeiden, den vollständigen Chiffretext zu speichern, aber wir müssen immer noch den vollständigen Klartext zwischenspeichern. Ein Entschlüsseler _könnte_ wählen, den Zwischen-Klartext auf der Platte zu speichern. Aber im Gegenzug haben wir mehr Raum für Fehler eingeführt. Haben wir ein überzeugendes Argument, dieses Problem nicht auf einer höheren Ebene zu lösen (z. B. Chunk dort oder Verwendung eines echten Streaming-Algorithmus)?

TLS und Pipelines. Derzeit (und für die absehbare Zukunft) verwenden Pipelines 4k-Blöcke, aber eine tls-Nachricht kann 16k Chiffretext enthalten. Bei einem One-Shot müssen Sie die 16k in einen einzigen fortlaufenden Puffer kopieren, bevor Sie entschlüsseln können. Bei Blöcken haben Sie möglicherweise 4 oder 5, und Sie müssen möglicherweise bis zu 16 Bytes puffern, um sicherzustellen, dass Blöcke konkurrieren.

@Drawaes 16k ist immer noch konstant und nicht riesig. Macht es in diesem Zusammenhang einen großen Unterschied?

Ja, es bedeutet eine weitere Kopie in der Pipeline. Dies hat einen großen und messbaren Einfluss auf die Leistung.

Was ist nötig, um dies zu erreichen? Was sind die nächsten Schritte? @Drawaes

Was AES-GCM betrifft, denke ich, dass seine Lieferung beeinträchtigt ist, weil das entsprechende Problem gesperrt ist: https://github.com/dotnet/corefx/issues/7023. @blowdart , könntest du entsperren? Es ist wirklich schwer, Fortschritte zu erzielen, wenn die Leute nicht diskutieren können. Oder, wenn das keine Option ist, schlagen Sie vielleicht eine alternative Lösung vor, die es ermöglicht, diese Funktion der Öffentlichkeit zugänglich zu machen.

Nein, das schalte ich nicht frei. Eine Entscheidung ist gefallen, das Thema ist erledigt.

@blowdart Danke für die Antwort. Ich verstehe, dass dies vielleicht nicht klar genug war:

Oder, wenn das keine Option ist, schlagen Sie vielleicht eine alternative Lösung vor, die es ermöglicht, diese Funktion der Öffentlichkeit zugänglich zu machen.

Ich schätze, dass es eine Entscheidung zur Unterstützung von AES-GCM gibt. Das ist großartig, ich will diesen Algorithmus auf jeden Fall. Daher wäre es jetzt cool, wenn es tatsächlich unterstützt würde. Möchten Sie, dass die Diskussion über AES-GCM-Design und -Implementierung hier oder in einer neuen Ausgabe stattfindet?

Wenn dieses Thema erledigt ist, warum schließen Sie es nicht? Und machen Sie es deutlicher, indem Sie den Titel dieser Ausgabe ändern, da es derzeit darauf hindeutet, dass die Diskussion über die Implementierung hier stattfinden würde: https://github.com/dotnet/corefx/issues/7023. Vielleicht so etwas wie Decide which AEAD algorithm to support first .

Mit anderen Worten: Ich gebe Feedback, dass in der aktuellen Situation unklar ist, was nötig ist, um AES-GCM voranzubringen.

@Karelz

@pgolebiowski Es gibt bereits eine PR. Es wird wahrscheinlich nächste Woche am Mittwoch im Master verfügbar sein.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen