Stan Drapkin ist ein Sicherheits- und Compliance-Experte mit über 16 Jahren Erfahrung mit .NET Framework (beginnend mit .NET 1.0-Beta im Jahr 2001). Leider schreibt er selbst keine Artikel auf Russisch, daher haben wir uns mit ihm darauf geeinigt, eine Übersetzung
seines Berichts mit DotNext Piter zu veröffentlichen . Dieser Bericht
gewann den ersten Platz auf der Konferenz!
Symmetrische Kryptographie, asymmetrische, hybride, High-Level-, Low-Level-, Stream- und moderne elliptische Kryptographie. Sechsundfünfzig Minuten Video über Kryptographie und viel schneller - in Form von Text.

Unter dem Schnitt - Videos, Folien und Übersetzung. Viel Spaß beim Lesen!
FolienMein Name ist Stan Drapkin, ich bin der technische Direktor eines Unternehmens, das sich auf Informationssicherheit und Einhaltung gesetzlicher Vorschriften spezialisiert hat. Außerdem bin ich Autor mehrerer Open-Source-Bibliotheken, die von der Community sehr gut angenommen werden. Wie viele haben von
Inferno gehört ? Diese Bibliothek demonstriert den korrekten Ansatz zur Kryptografie in .NET, und
TinyORM implementiert Micro-ORM für .NET. Darüber hinaus habe ich mehrere Bücher geschrieben, die für das Thema des heutigen Artikels relevant sein könnten. Eine davon, die Ausgabe 2014, ist "Security Driven .NET", die andere von 2017 ist "Application Security in .NET, prägnant".
Zuerst werden wir über die vier Stufen der Kryptoaufklärung sprechen. Dann folgen zwei Hauptthemen, im ersten über symmetrische Kryptographie, im zweiten über asymmetrische und hybride. Im ersten Teil vergleichen wir die Kryptografie auf hoher und niedriger Ebene und sehen uns ein Beispiel für die Streaming-Kryptografie an. Im zweiten Teil werden wir viele „Abenteuer“ mit RSA erleben, wonach wir uns mit der modernen elliptischen Kryptographie vertraut machen werden.
Wie sehen diese Stadien der Kryptoaufklärung aus? Die erste Phase - "XOR ist so cool, schau, Mama, wie kann ich!" Sicherlich sind viele von Ihnen mit dieser Phase vertraut und kennen die Wunder der XOR-Funktion. Ich hoffe jedoch, dass der größte Teil dieser Phase gewachsen ist und zur nächsten übergegangen ist, dh gelernt hat, Verschlüsselung und Entschlüsselung mit AES (Advanced Encryption Standard) durchzuführen, einem bekannten und hoch angesehenen Algorithmus. Die meisten Entwickler, die DotNext nicht besuchen, befinden sich in dieser Phase. Da Sie DotNext folgen und mit Berichten über die Gefahren von Low-Level-APIs vertraut sind, befinden Sie sich höchstwahrscheinlich in der nächsten Phase: „Ich habe alles (a) falsch gemacht, ich muss zu High-Level-APIs wechseln“. Um das Bild zu vervollständigen, möchte ich auch die letzte Phase erwähnen - das Verständnis, dass bei der besten Lösung des Problems Kryptografie möglicherweise überhaupt nicht benötigt wird. Diese Phase ist am schwierigsten zu erreichen, und es gibt nur wenige Menschen. Ein Beispiel ist Peter G. Neumann, der Folgendes sagte: "Wenn Sie der Meinung sind, dass die Lösung Ihres Problems in der Kryptographie liegt, verstehen Sie nicht genau, was Ihr Problem ist."
Die Tatsache, dass Kryptografie auf niedriger Ebene gefährlich ist, wurde in vielen Berichten über .NET diskutiert. Sie können auf den Bericht von Vladimir Kochetkov aus dem Jahr 2015
"Fallstricke von System.Security.Cryptography" verweisen . Seine Hauptidee ist, dass wir in jeder Phase der Arbeit mit kryptografischen APIs auf niedriger Ebene, ohne es zu wissen, viele Entscheidungen treffen, für die wir einfach nicht über das entsprechende Wissen verfügen. Die Hauptschlussfolgerung ist, dass im Idealfall eine Kryptographie auf hoher Ebene anstelle einer Kryptographie auf niedriger Ebene verwendet werden sollte. Dies ist eine wunderbare Schlussfolgerung, führt uns jedoch zu einem anderen Problem: Wissen wir genau, wie Kryptografie auf hoher Ebene aussehen sollte? Reden wir ein wenig darüber.
Definieren Sie die Attribute einer
nicht übergeordneten kryptografischen API. Zunächst vermittelt eine solche API nicht den Eindruck, in .NET nativ zu sein, sondern sieht aus wie eine Shell auf niedriger Ebene. Ferner ist eine solche API leicht falsch zu verwenden, d.h. nicht wie es sollte. Darüber hinaus werden Sie gezwungen, viele seltsame Dinge auf niedriger Ebene zu generieren - Nonce, Initialisierungsvektoren und dergleichen. Eine solche API zwingt Sie dazu, unangenehme Entscheidungen zu treffen, auf die Sie möglicherweise nicht vorbereitet sind - wählen Sie Algorithmen, Auffüllmodi, Schlüsselgrößen, Nonce usw. aus. Es wird auch nicht die richtige API für das Streaming geben (Streaming-API) - wir werden darüber sprechen, wie letztere aussehen sollte.
Wie sollte dagegen eine kryptografische API auf hoher Ebene aussehen? Ich glaube, dass es sowohl für den Leser des Codes als auch für den Verfasser zunächst intuitiv und prägnant sein sollte. Darüber hinaus sollte eine solche API leicht zu erlernen und zu verwenden sein und es sollte äußerst schwierig sein, sie falsch anzuwenden. Es muss auch mächtig sein, das heißt, es muss uns ermöglichen, unser Ziel mit ein wenig Aufwand, einer kleinen Menge Code, zu erreichen. Schließlich sollte eine solche API im Allgemeinen keine lange Liste von Einschränkungen, Vorsichtsmaßnahmen und Sonderfällen enthalten - es sollte ein Minimum an Dingen geben, die bei der Arbeit beachtet werden müssen, mit anderen Worten - sie sollte durch ein geringes Maß an Interferenz (geringe Reibung) gekennzeichnet sein arbeiten Sie einfach ohne Vorbehalte.
Wie können wir die Anforderungen für eine kryptografische API auf hoher Ebene für .NET jetzt finden? Sie können einfach Google ausprobieren, aber das wäre zu primitiv - wir sind professionelle Entwickler, und dies ist nicht unsere Methode. Daher untersuchen wir dieses Problem und testen verschiedene Alternativen. Dazu müssen wir uns zunächst die richtige Vorstellung davon machen, was authentifizierte Verschlüsselung ist, und dafür müssen wir die Grundkonzepte verstehen. Sie sind wie folgt: der Klartext P (Klartext), den wir mit einem geheimen Schlüssel K (Schlüssel) in Chiffretext C (Chiffretext) gleicher Länge konvertieren. Wie Sie sehen, arbeiten wir bisher mit einem sehr einfachen Schema. Zusätzlich haben wir auch ein Authentifizierungs-Tag T und Nonce N. Ein wichtiger Parameter ist N̅, dh die Wiederverwendung von Nonce mit einem Schlüssel. Wie viele von Ihnen wahrscheinlich wissen, führt dies zu einer Verletzung der Vertraulichkeit des Textes, was offensichtlich unerwünscht ist. Ein weiteres wichtiges Konzept sind AD (Associated Data), dh zugehörige Daten. Dies sind optionale Daten, die authentifiziert sind, aber nicht an der Ver- und Entschlüsselung teilnehmen.

Nachdem wir die grundlegenden Konzepte verstanden haben, werfen wir einen Blick auf die verschiedenen Optionen kryptografischer Bibliotheken für .NET. Beginnen wir mit der Analyse von
Libsodium.NET. Wie viele von euch kennen sie? Wie ich sehe, sind einige vertraut.
nonce = SecretAeadAes.GenerateNonce(); c = SecretAeadAes.Encrypt(p, nonce, key, ad); d = SecretAeadAes.Decrypt(c, nonce, key, ad);
Hier ist der C # -Code, mit dem die Verschlüsselung mit
Libsodium.NET durchgeführt wird . Auf den ersten Blick ist es recht einfach und prägnant: In der ersten Zeile wird Nonce generiert, das dann in der zweiten Zeile verwendet wird, in der die Verschlüsselung selbst stattfindet, und in der dritten Zeile, in der der Text entschlüsselt wird. Es schien - welche Schwierigkeiten könnte es geben? Zunächst bietet Libsodium.NET nicht eine, sondern drei verschiedene symmetrische Verschlüsselungsmethoden:
Zeiten
nonce = SecretAeadAes.GenerateNonce(); c = SecretAeadAes.Encrypt(p, nonce, key, ad); d = SecretAeadAes.Decrypt(c, nonce, key, ad);
Zwei
nonce = SecretAead.GenerateNonce(); c = SecretAead.Encrypt(p, nonce, key, ad); d = SecretAead.Decrypt(c, nonce, key. ad);
Drei
nonce = SecretBox.GenerateNonce(); c = SecretBox.Create(p, nonce, key); d = SecretBox.Open(c, nonce, key);
Offensichtlich stellt sich die Frage: Welcher von ihnen ist in Ihrer spezifischen Situation besser? Um darauf zu antworten, müssen Sie sich mit diesen Methoden befassen, die wir jetzt ausführen werden.
Die erste Methode,
SecretAeadAes
, verwendet AES-GCM mit einer 96-Bit-Nonce. Es ist wichtig, dass er eine ziemlich lange Liste von Einschränkungen hat. Wenn Sie es beispielsweise verwenden, sollten Sie nicht mehr als 550 Gigabyte mit einem Schlüssel verschlüsseln, und eine Nachricht mit maximal 2
32 Nachrichten sollte nicht mehr als 64 Gigabyte enthalten. Darüber hinaus warnt die Bibliothek nicht davor, sich diesen Einschränkungen zu nähern. Sie müssen sie selbst nachverfolgen, was Sie als Entwickler zusätzlich belastet.
Die zweite Methode,
SecretAead
verwendet eine andere Verschlüsselungssuite,
ChaCha20/Poly1305
mit einer deutlich kleineren 64-Bit-Nonce. Solch eine kleine Nonce macht Kollisionen äußerst wahrscheinlich, und allein aus diesem Grund sollten Sie diese Methode nicht anwenden - außer in recht seltenen Fällen und vorausgesetzt, Sie sind mit dem Thema sehr gut vertraut.
Schließlich die dritte Methode,
SecretBox
. Es sollte sofort beachtet werden, dass die Argumente für diese API keine zugeordneten Daten enthalten. Wenn Sie eine authentifizierte Verschlüsselung mit AD benötigen, ist diese Methode nicht für Sie geeignet. Der hier verwendete Verschlüsselungsalgorithmus heißt
xSalsa20/Poly1305
, nonce ist groß genug - 192 Bit. Das Fehlen von AD ist jedoch eine signifikante Einschränkung.
Bei der Verwendung von
Libsodium.NET treten einige Fragen auf. Was genau sollen wir zum Beispiel mit der Nonce tun, die in den obigen Beispielen durch die erste Codezeile generiert wird? Die Bibliothek sagt uns nichts darüber, wir müssen es selbst herausfinden. Höchstwahrscheinlich werden wir diese Nonce manuell am Anfang oder Ende des Chiffretextes hinzufügen. Ferner könnten wir den Eindruck haben, dass AD in den ersten beiden Methoden beliebig lang sein kann. Tatsächlich unterstützt die Bibliothek AD jedoch nicht länger als 16 Bytes - schließlich reichen 16 Bytes für alle, oder? Lass uns weitermachen. Was passiert mit Entschlüsselungsfehlern? In dieser Bibliothek wurde in diesen Fällen beschlossen, Ausnahmen auszulösen. Wenn in Ihrer Umgebung während der Entschlüsselung die Datenintegrität verletzt wird, müssen Sie viele Ausnahmen behandeln. Was ist, wenn Ihre Schlüsselgröße nicht genau 32 Byte beträgt? Die Bibliothek sagt uns nichts darüber, das sind Ihre Probleme, die es nicht interessieren. Ein weiteres wichtiges Thema ist die Wiederverwendung von Byte-Arrays, um die Belastung des Garbage Collector in intensiven Szenarien zu verringern. Zum Beispiel haben wir im Code ein Array gesehen, das der Nonce-Generator an uns zurückgegeben hat. Ich möchte nicht jedes Mal einen neuen Puffer erstellen, sondern den vorhandenen wiederverwenden. Dies ist in dieser Bibliothek nicht möglich. Jedes Mal wird ein Array von Bytes neu generiert.
Mit dem bereits gesehenen Schema werden wir versuchen, verschiedene
Libsodium.NET- Algorithmen zu vergleichen.

Der erste Algorithmus, AES-GCM, verwendet eine Länge von 96 Bit (gelbe Spalte im Bild). Es sind weniger als 128 Bit, was einige Unannehmlichkeiten verursacht, aber nicht zu bedeutend ist. Die nächste Spalte ist blau. Dies ist die Stelle, an der sich das Authentifizierungs-Tag befindet. Bei AES-GCM sind es 16 Byte oder 128 Bit. Die zweite blaue Ziffer in Klammern bedeutet die Entropie oder Zufälligkeit, die in diesem Tag enthalten ist - weniger als 128 Bit. Wie viel weniger - bei diesem Algorithmus hängt davon ab, wie viele Daten verschlüsselt sind. Je verschlüsselter, desto schwächer das Tag. Dies allein sollte Zweifel an diesem Algorithmus aufkommen lassen, die nur zunehmen werden, wenn wir uns die weiße Spalte ansehen. Es heißt, dass Wiederholungen (Kollisionen) von Nonce zur Verfälschung aller Chiffretexte führen, die mit demselben Schlüssel erstellt wurden. Wenn beispielsweise von 100 Ihrer Chiffretexte, die von einem gemeinsamen Schlüssel in zwei erstellt wurden, eine Nonce-Kollision auftritt, führt diese Nonce zu einem internen Leck des Authentifizierungsschlüssels und ermöglicht es einem Angreifer, jeden anderen von diesem Schlüssel erstellten Chiffretext zu fälschen. Dies ist eine sehr bedeutende Einschränkung.
Fahren wir mit der zweiten
Libsodium.NET- Methode fort. Wie gesagt, hier wird für Nonce zu wenig Speicherplatz verwendet, nur 64 Bit. Das Tag belegt 128 Bit, enthält jedoch nur 106 Bit Entropie oder weniger, dh es ist deutlich niedriger als die Sicherheitsstufe von 128 Bit, die in den meisten Fällen erreicht werden soll. Bei Fälschungen ist die Situation hier etwas besser als bei AES-GCM. Die Kollision von Nonce führt zur Verfälschung von Chiffretexten, jedoch nur für diejenigen Blöcke, in denen Kollisionen aufgetreten sind. Im vorherigen Beispiel hätten wir 2 Chiffretexte gefälscht, nicht 100.
Schließlich haben wir im Fall des xSalsa / Poly-Algorithmus eine sehr große Nonce von 192 Bits, was Kollisionen äußerst unwahrscheinlich macht. Die Authentifizierungsmethode ist dieselbe wie bei der vorherigen Methode, sodass das Tag erneut 128 Bit benötigt und 106 Bit Entropie oder weniger aufweist.
Vergleichen Sie alle diese Zahlen mit den entsprechenden Indikatoren der
Inferno- Bibliothek. Nonce nimmt darin einen kolossalen Raum von 320 Bit ein, was Kollisionen fast unmöglich macht. Was das Tag betrifft, ist alles einfach: Es belegt genau 128 Bit und hat genau 128 Bit Entropie, nicht weniger. Dies ist ein Beispiel für einen zuverlässigen und sicheren Ansatz.
Bevor wir
Libsodium.NET genauer kennenlernen, müssen wir dessen Zweck verstehen - leider ist dies nicht jedem bekannt, der diese Bibliothek verwendet.
Informationen hierzu finden Sie in der Dokumentation, in der angegeben ist, dass
Libsodium.NET ein C #
-Wrapper für
libsodium ist . Dies ist ein weiteres Open-Source-Projekt, dessen Dokumentation besagt, dass es sich um eine
NaCl-Verzweigung mit einer kompatiblen API handelt. Wenden Sie sich der Dokumentation von
NaCl zu , einem weiteren Open-Source-Projekt. Als Ziel
wird postuliert, dass
NaCl alle erforderlichen Operationen zum Erstellen von kryptografischen Tools auf hoher Ebene bereitstellt. Hier ist der Hund begraben: Die Aufgabe von
NaCl und all seinen Schalen besteht darin, Elemente auf niedriger Ebene bereitzustellen, aus denen dann bereits jemand anderes kryptografische APIs auf hoher Ebene zusammenstellen kann. Diese Shells selbst als hochrangige Bibliotheken wurden nicht konzipiert. Daher die Moral: Wenn Sie eine kryptografische API auf hoher Ebene benötigen, müssen Sie eine Bibliothek auf hoher Ebene finden und keinen Wrapper auf niedriger Ebene verwenden und so tun, als würden Sie mit einer auf hoher Ebene arbeiten.
Mal sehen, wie die Verschlüsselung in
Inferno funktioniert.

Hier ist ein Beispielcode, in dem wie im Fall von
Libsodium jede Ver- und Entschlüsselung nur eine Zeile dauert. Die Argumente sind Schlüssel, Text und optionale zugehörige Daten. Es sollte beachtet werden, dass es keine Nonce gibt, keine Notwendigkeit besteht, Entscheidungen zu treffen. Im Falle eines Entschlüsselungsfehlers wird einfach null zurückgegeben, ohne Ausnahmen auszulösen. Da das Erstellen von Ausnahmen die Belastung des Garbage Collectors erheblich erhöht, ist ihre Abwesenheit für Skripts, die große Datenströme verarbeiten, sehr wichtig. Ich hoffe, ich konnte Sie davon überzeugen, dass dieser Ansatz optimal ist.
Versuchen wir aus Interesse, eine Zeichenfolge zu verschlüsseln. Dies sollte das einfachste Szenario sein, das jeder implementieren kann. Angenommen, wir haben nur zwei verschiedene mögliche Zeichenfolgenwerte: "LINKS" und "RECHTS".

Auf dem Bild sehen Sie die Verschlüsselung dieser Zeilen mit
Inferno (obwohl es für dieses Beispiel keine Rolle spielt, welche Bibliothek verwendet wird). Wir verschlüsseln zwei Zeilen mit einem Schlüssel und erhalten zwei Chiffretexte,
c1
und
c2
. Ist alles in diesem Code korrekt? Ist er bereit für die Produktion? Jemand mag sagen, dass das Problem auf kurze Weise möglich ist, aber es ist weit vom Hauptproblem entfernt, sodass wir davon ausgehen, dass der Schlüssel gleich verwendet wird und eine ausreichende Länge hat. Ich meine noch etwas anderes: Bei herkömmlichen kryptografischen Ansätzen ist
c1
in unserem Beispiel kürzer als
c2
. Dies wird als Längenleck bezeichnet -
c2
in vielen Fällen ein Byte länger als
c1
. Dadurch kann ein Angreifer möglicherweise verstehen, welche Zeichenfolge durch diesen Chiffretext "LINKS" oder "RECHTS" dargestellt wird. Der einfachste Weg, um dieses Problem zu lösen, besteht darin, beide Zeilen gleich lang zu machen. Fügen Sie beispielsweise am Ende der Zeile „LINKS“ ein Zeichen hinzu.
Auf den ersten Blick wird Längenleckage als ein etwas weit hergeholtes Problem wahrgenommen, das in realen Anwendungen nicht auftreten kann. Im Januar 2018 wurde im Wired-Magazin ein Artikel mit einer Studie des israelischen Unternehmens Checkmarx unter der Überschrift „Die fehlende Verschlüsselung in Tinder ermöglicht es Außenstehenden, zu verfolgen, wann Sie über den Bildschirm streichen“ veröffentlicht. Ich werde kurz auf den Inhalt eingehen, aber zuerst eine grobe Beschreibung der Tinder-Funktionalität. Tinder ist eine Anwendung, die einen Stream mit Fotos empfängt. Anschließend wischt der Benutzer den Bildschirm nach rechts oder links, je nachdem, ob ihm das Foto gefällt oder nicht. Die Forscher fanden heraus, dass die Daten für den rechten Befehl eine andere Anzahl von Bytes benötigten als die Daten für den linken, obwohl die Befehle selbst mit TLS und HTTPS korrekt verschlüsselt waren. Dies ist natürlich eine Sicherheitslücke, aber an sich ist sie nicht zu bedeutend. Wichtiger für Tinder war die Tatsache, dass die Streams mit Fotos über normales HTTP ohne Verschlüsselung gesendet wurden. So kann der Angreifer nicht nur auf Benutzerreaktionen auf Fotos zugreifen, sondern auch auf die Fotos selbst. Wie Sie sehen können, ist Längenleckage ein sehr reales Problem.
Versuchen wir nun, die Datei zu verschlüsseln. Ich muss sofort sagen, dass in
Libsodium.NET die Dateiverschlüsselung oder allgemeiner die Stream-Verschlüsselung nicht standardmäßig implementiert ist, sondern manuell durchgeführt werden muss - was meiner Meinung nach sehr schwierig ist, dies richtig zu tun. Im
Inferno sieht es damit viel besser aus.

Oben sehen Sie ein Beispiel, das praktisch ohne Änderung von MSDN aufgenommen wurde. Es ist sehr einfach, hier sehen wir einen Stream für die Quelldatei und einen anderen für die Zieldatei sowie einen Krypto-Stream, der den ersten in den zweiten konvertiert. In diesem Code wird
Inferno nur in einer Zeile verwendet - in der Zeile, in der die Konvertierung stattfindet. Vor uns liegt also eine einfache und gleichzeitig voll funktionsfähige und getestete Lösung für die Stream-Verschlüsselung.
Es ist zu beachten, dass beim Verschlüsseln mit demselben Schlüssel die Anzahl der Nachrichten begrenzt ist. Sie existieren im
Inferno und sind in dieser Bibliothek deutlich auf dem Bildschirm geschrieben. Gleichzeitig sind sie im
Inferno so groß, dass Sie sie in der Praxis niemals erreichen werden. In
Libsodium.NET sind die Einschränkungen für verschiedene Algorithmen unterschiedlich, aber in allen Fällen niedrig genug, um überschritten zu werden. Sie müssen also prüfen, ob sie in jedem einzelnen Szenario erreicht werden.
Wir sollten auch über die Authentifizierung zugehöriger Daten sprechen, da dies ein Thema ist, das nicht oft behandelt wird. ADs können „schwach“ sein: Dies bedeutet, dass sie authentifiziert sind, aber nicht am Ver- und Entschlüsselungsprozess beteiligt sind. Im Gegensatz dazu verändern „starke“ ADs diesen Prozess selbst. Die meisten mir bekannten AD-Bibliotheken sind schwach, während
Inferno den zweiten Ansatz verwendet, bei dem ADs im Verschlüsselungs- / Entschlüsselungsprozess selbst verwendet werden ...
Es sollte auch darüber nachgedacht werden, welche Sicherheitsstufe für eine Kryptografie auf hoher Ebene angestrebt werden sollte. Kurz gesagt, meine Antwort lautet: 256-Bit-Verschlüsselung mit einem 128-Bit-Authentifizierungs-Tag. Warum ist ein Schlüssel so groß? Es gibt viele Gründe dafür, von denen jeder für sich von Bedeutung ist, aber jetzt möchte ich Sie bitten, sich an eines zu erinnern: Wir müssen uns vor möglichen Verzerrungen schützen, wenn wir kryptografische Schlüssel generieren. Lassen Sie mich erklären, was unter Voreingenommenheit zu verstehen ist. Für einen Zufallsbitgenerator ohne Vorspannung sind für jedes Bit die Wahrscheinlichkeiten für das Akzeptieren des Werts 0 oder 1 gleich. Nehmen wir jedoch an, dass das Bit in unserem Generator den Wert 1 mit einer Wahrscheinlichkeit von 56% und nicht von 50% annimmt. Auf den ersten Blick ist diese Tendenz gering, aber tatsächlich ist sie signifikant: 25%. Versuchen wir nun zu berechnen, wie viel Entropie wir erhalten, wenn wir mit unserem Generator eine bestimmte Anzahl von Bits erzeugen.

Im Bild sehen Sie die Formel, nach der diese Berechnung durchgeführt wird. Es ist wichtig, dass nur zwei Variablen enthalten sind: die Verzerrung, über die wir bereits gesprochen haben (Verzerrung), und die Anzahl der vom Generator erzeugten Bits. Wir gehen davon aus, dass die Abweichung 25% beträgt - dies ist ein ziemlich extremer Fall. In der Praxis werden Sie höchstwahrscheinlich nicht in Systemen mit einem derart verzerrten Zufallszahlengenerator arbeiten. Mit 25% Bias und einem 128-Bit-Schlüssel erhalten wir jedenfalls nur 53 Bit Entropie. Erstens sind es deutlich weniger als 128 Bit, die normalerweise von einem Zufallszahlengenerator erwartet werden, und zweitens kann ein solcher Schlüssel mit modernen Technologien einfach Brute Force sein. Wenn wir jedoch anstelle des 128-Bit-Schlüssels 256-Bit verwenden, erhalten wir 106 Bit Entropie. Dies ist bereits recht gut, wenn auch weniger als die erwarteten 256. Mit modernen Technologien ist es fast unmöglich, einen solchen Schlüssel zu knacken.
Am Ende des ersten Teils des Berichts werde ich die Zwischenergebnisse zusammenfassen. Ich empfehle jedem, gut geschriebene kryptografische APIs zu verwenden. Finden Sie diejenige, die zu Ihnen passt, oder senden Sie eine Petition an Microsoft, um Ihnen zu schreiben. Darüber hinaus sollten Sie bei der Auswahl einer API auf die Verfügbarkeit von Unterstützung für die Arbeit mit Threads achten. Aus den bereits erläuterten Gründen sollte die minimale Schlüssellänge 256 Bit betragen. Schließlich sollte bedacht werden, dass Kryptographie auf hoher Ebene wie jede andere nicht ideal ist. Es können Undichtigkeiten auftreten, und in den meisten Szenarien müssen deren Fähigkeiten berücksichtigt werden.
Lassen Sie uns über asymmetrische oder hybride Kryptographie sprechen. Ich stelle eine Trickfrage: Können Sie RSA in .NET verwenden? Beeilen Sie sich nicht, dies zu bejahen, wie es viele tun - lassen Sie uns zunächst Ihr Wissen in diesem Bereich testen. Die folgenden Folien wurden speziell für Personen entwickelt, die bereits mit diesem Thema vertraut sind. Aber zuerst schauen wir uns Wikipedia an und erinnern uns, was RSA genau ist, falls jemand diesen Algorithmus vergessen hat oder ihn lange nicht benutzt hat.

Angenommen, es gibt einige Alice, die mithilfe eines Zufallszahlengenerators ein Schlüsselpaar erstellt, das ein privates und ein öffentliches enthält. Als nächstes gibt es einige Bob, die eine Nachricht für Alice verschlüsseln möchten: "Hallo Alice!" Mit ihrem öffentlichen Schlüssel generiert er einen Chiffretext, den er dann an sie sendet. Sie entschlüsselt diesen Chiffretext mit dem privaten Teil ihres Schlüssels.
Versuchen wir, dieses Szenario in der Praxis zu reproduzieren.

Wie Sie oben sehen können, erstellen wir eine Instanz von RSA und verschlüsseln Text. Achten Sie sofort darauf, dass .NET uns zwingt, den Auffüllmodus zu wählen. Es gibt fünf von ihnen, alle mit obskuren Namen. Wenn wir sie alle nacheinander ausprobieren, werden wir feststellen, dass die letzten drei einfach eine Ausnahme auslösen und nicht funktionieren. Wir werden eine der beiden verbleibenden verwenden -
OaepSHA1
. Hier hat der Schlüssel eine Größe von 1 Kilobit, was für RSA zu klein ist. Es handelt sich praktisch um einen gehackten Schlüssel. Daher müssen wir die Schlüsselgröße manuell einstellen. Aus der Dokumentation erfahren wir, dass es eine spezielle Eigenschaft
.KeySize
, die die Schlüsselgröße empfängt oder festlegt.

Auf den ersten Blick ist dies genau das, was wir brauchen, also schreiben wir:
rsa.KeySize = 3072
. Wenn wir jedoch nach vage Vermutung überprüfen, wie groß die Schlüsselgröße jetzt ist, werden wir feststellen, dass es immer noch 1 Kilobit dauert. Es spielt keine Rolle, wir überprüfen diesen Parameter mit der
WriteLine(rsa.KeySize)
Methode
WriteLine(rsa.KeySize)
oder
rsa.ExportParameters(false).Modulus.Length * 8
- im letzteren Fall wird die öffentliche Komponente des RSA-Schlüssels exportiert. Dazu benötigen wir das Argument "false". Der Modul dieses Schlüssels ist ein Array, das wir mit 8 multiplizieren und die Größe in Bits erhalten - was wiederum 1 Kilobit beträgt. Wie Sie sehen, ist dieser Algorithmus noch zu früh, um an die Produktion gesendet zu werden.
Wir werden keine Zeit damit verschwenden, herauszufinden, warum diese API nicht funktioniert. Versuchen Sie stattdessen eine andere RSA-Implementierung, die von Microsoft in .NET 4.6 bereitgestellt wird, dh eine völlig neue. Es heißt
RSACng und
Cng steht für Cryptography Next Generation. Großartig, wer möchte nicht mit Tools der nächsten Generation arbeiten? Hier finden wir sicherlich eine magische Lösung für all unsere Probleme.

Wir fordern eine Instanz von RSACng an, setzen die Schlüsselgröße erneut auf 3 Kilobit, überprüfen erneut die Schlüsselgröße über
WriteLine(rsa.KeySize)
- und stellen erneut fest, dass die Schlüsselgröße immer noch einem Kilobit entspricht. Wenn wir außerdem den Typ des Objekts anfordern, das den Schlüssel generiert hat - wie wir uns erinnern, haben wir eine Instanz von RSACng angefordert -, stellen wir fest, dass es sich um RSACryptoServiceProvider handelt. Ich möchte hier nur mein persönliches Gefühl der Verzweiflung teilen und schreien: "Warum, Microsoft ?!"
Nach längerer Qual und Qual stellen wir fest, dass Sie tatsächlich den Designer verwenden müssen, nicht die Fabrik.

Hier beträgt der Standardwert für die Schlüsselgröße 2048 Bit, was bereits viel besser ist. Was noch besser ist - hier schaffen wir es endlich, die Schlüsselgröße auf 3 Kilobit einzustellen. Wie sie sagen, Leistung freigeschaltet.
Ich möchte Sie daran erinnern, dass alle unsere bisherigen Bemühungen nur auf die Schaffung von RSA beschränkt waren. Wir haben noch nicht einmal mit der Verschlüsselung begonnen. Es gibt noch Fragen, die wir zuerst beantworten müssen. Inwieweit können Sie sich für den Anfang auf Standardschlüsselgrößen verlassen? Die Implementierung der RSA-Factory kann überschrieben werden machine.config
, daher kann sie sich ohne Ihr Wissen ändern (z. B. kann ein Systemadministrator sie ändern). Dies bedeutet, dass sich auch die Standardschlüsselgröße ändern kann. Daher sollten Sie den standardmäßig angegebenen Werten niemals vertrauen. Die Schlüsselgröße sollte immer unabhängig festgelegt werden. Wie gut sind die Standard-RSA-Schlüsselgrößen? In .NET gibt es zwei RSA-Implementierungen, eine basierend RSACryptoServiceProvider
und eine basierendRSACng
. In der ersten ist die Standardgröße 1 Kilobit, in den zweiten beiden. Vergleichen wir diese Werte zum Spaß mit denen im Bitcoin-Netzwerk (BCN). Ich entschuldige mich im Voraus dafür, dass ich ein schmerzhaftes Thema angesprochen habe, aber wir werden weder Bitcoin noch Kryptowährung diskutieren, sondern nur über das Netzwerk selbst sprechen. Sie hat eine veröffentlichte Hashrate, die jeden Monat wächst und heute 2 64 Hashes pro Sekunde entspricht. Dies entspricht 2 90 Hashes pro Jahr. Nehmen wir der Einfachheit halber an, dass ein Hash einer grundlegenden Operation entspricht - obwohl dies nicht ganz zutrifft, ist es komplexer. Wenn Sie Bücher über Kryptographie lesen, die von echten Fachleuten geschrieben wurden, und nicht von Leuten wie mir, dann wissen Sie, dass 2 70 Operationen (dh eine Minute BCN) ausreichen, um einen 1-Kilobit-RSA-Schlüssel zu knacken, und 2 90(ein Jahr BCN) - um einen 2-Kilobit-Schlüssel zu knacken. Beide Werte sollten uns Angst machen - das kann mit vorhandenen Technologien erreicht werden. Aus diesem Grund empfehle ich dringend, dass Sie die Schlüsselgröße immer selbst festlegen und mindestens 3 Kilobit groß machen. Wenn die Leistung dies zulässt, ist 4.In .NET ist es nicht so einfach, herauszufinden, wie öffentliche und private Schlüssel exportiert werden.
Oben auf der Folie sehen Sie zwei Instanzen des RSA-Schlüssels, die erste von RSACryptoServiceProvider
und die zweite vonRSACng
jeweils 4 Kilobit. Der folgende Code wird verwendet, um den öffentlichen und den privaten Schlüssel aus beiden Instanzen zu extrahieren. Es sollte beachtet werden, dass beide APIs sehr unterschiedlich sind - unterschiedlicher Code, unterschiedliche Methoden, unterschiedliche Parameter. Wenn wir außerdem die Größen der öffentlichen Schlüssel der ersten und zweiten Kopie vergleichen, werden wir feststellen, dass sie vergleichbar sind, jeweils ungefähr ein halbes Kilobyte. Der private Schlüssel für die neue RSA-Implementierung ist jedoch viel kleiner als der alte. Es ist notwendig, dies zu berücksichtigen und die Einheitlichkeit zu beachten, um diese beiden APIs nicht miteinander zu stören.Alles, was wir bisher mit RSA gemacht haben, ist darauf hinausgegangen, eine Arbeitskopie zu erhalten. Versuchen Sie nun, etwas zu verschlüsseln.
Erstellen Sie ein Array von Bytes, das unser Klartext sein wird (data
), und dann werden wir es mit einem dieser Additionsmodi verschlüsseln, die keine Ausnahme ausgelöst haben. Aber diesmal haben wir eine Ausnahme. Dies ist eine Ausnahme von einem ungültigen Parameter. aber über welchen Parameter sprechen wir? Ich habe keine Ahnung - und Microsoft höchstwahrscheinlich auch. Wenn wir versuchen, dieselbe Methode mit anderen Ergänzungsmodi auszuführen, erhalten wir jeweils dieselbe Ausnahme. Der Punkt befindet sich also nicht im Ergänzungsmodus. Das Problem liegt also im Quellcode selbst. Es ist schwer zu sagen, was mit ihm los ist, also versuchen wir es für alle Fälle zu halbieren. Diesmal ist die Verschlüsselung erfolgreich. Wir sind ratlos.Vielleicht ist der springende Punkt, dass wir die SHA-1-Ergänzung verwendet haben? Wie wir wissen, ist SHA-1 keine kryptografisch starke Funktion mehr, daher bestehen unsere Prüfer und die Compliance-Abteilung darauf, dass wir es loswerden. Ersetzen Sie OaepSHA1
durch OaepSHA256
, zumindest wird es die Auditoren beruhigen.
Wenn wir jedoch versuchen zu verschlüsseln, erhalten wir erneut die Ausnahme des falschen Parameters. Diese ganze Situation wird durch die Tatsache verursacht, dass die Beschränkung der Größe des Textes, der auf die kryptografische Funktion übertragen werden kann, nicht nur vom Ergänzungsmodus, sondern auch von der Größe des Schlüssels abhängt.Versuchen wir herauszufinden, wie genau diese Zauberformel aussieht, die die maximale Menge an verschlüsselten Daten bestimmt. Es muss in der Methode seinint GetMaxDataSizeForEnc(RSAEncryptionPadding pad)
, der dieses Volumen berechnet, nachdem er den Ergänzungsmodus am Eingang erhalten hat. Der Hauptnachteil dieser Methode ist, dass sie nicht existiert, ich habe sie erfunden. Ich versuche zu vermitteln, dass uns nicht einmal die grundlegendsten Informationen zur Verfügung stehen, die ein Entwickler benötigt, um RSA korrekt zu verwenden. Vielen Dank, Microsoft.Hier sind die Gründe, warum RSA auch zur Unterschrift vermieden werden sollte. Wie ich hoffentlich zeigen konnte, sind die APIs für RSA in .NET äußerst unbefriedigend. Sie sind gezwungen, viele Entscheidungen bezüglich des Ergänzungsmodus, der Datengröße und dergleichen zu treffen, was unerwünscht ist. Für eine 128-Bit-Sicherheitsstufe benötigen Sie außerdem mindestens einen sehr sperrigen 4-Kilobyte-Schlüssel. Sie erhalten einen privaten Kilobyte-Schlüssel, einen öffentlichen Schlüssel mit einem halben Kilobyte und eine Signatur mit einem halben Kilobyte. In vielen Szenarien sind solche Werte möglicherweise nicht wünschenswert. Und wenn Sie versuchen, eine 256-Bit-Sicherheitsstufe zu erreichen, benötigen Sie überhaupt einen riesigen Schlüssel - 15360 Bit. In RSA ist die Verwendung eines solchen Schlüssels fast unmöglich. Auf meinem Laptop wird eine solche Taste eineinhalb Minuten lang generiert.Darüber hinaus implementiert der RSA auf einer fundamentalen Ebene als Algorithmus unabhängig von der Implementierung sehr langsam eine Signatur. Warum ist uns die Signaturgeschwindigkeit wichtig? Wenn Sie TLS mit RSA-Zertifikaten verwenden, erfolgt die Signatur auf dem Server. Und wir als Entwickler sind am meisten davon betroffen, was genau auf dem Server passiert. Wir sind dafür verantwortlich. Der Durchsatz ist für uns wichtig. Zusammenfassend möchte ich noch einmal empfehlen, RSA nicht zu verwenden.Ich möchte noch einmal empfehlen, RSA nicht zu verwenden.Ich möchte noch einmal empfehlen, RSA nicht zu verwenden.Was kann in diesem Fall den RSA ersetzen? Ich möchte Ihnen moderne elliptische kryptografische Grundelemente vorstellen. Beachten Sie zunächst den ECDSA (Digital Signature Algorithm), der anstelle von RSA für Signaturen verwendet werden kann. In dieser und den folgenden Abkürzungen ist EC ein generisches Präfix, das für Elliptic-Curve („elliptical“) steht. Unter securitydriven.net/inferno/#DSA Signatures finden Sie Beispiel-ECDSA-Code, der übrigens in .NET enthalten ist. Ein weiterer wichtiger Algorithmus ist ECIES (Integrated Encryption Scheme, „elliptisches integriertes Verschlüsselungsschema“). Dieser Algorithmus kann anstelle von RSA eine Hybridverschlüsselung durchführen, dh Sie generieren einen symmetrischen Schlüssel, verschlüsseln die Daten damit und verschlüsseln dann den Schlüssel selbst.Beispielcode finden Sie unter securitydriven.net/inferno/#ECIES Beispiel. Ein weiterer sehr wichtiger Algorithmus ist ECDH (Diffie-Hellman-Schlüsselaustausch, „Diffie-Hellman-Schlüsselaustausch“). Sie können damit Schlüssel für die symmetrische Verschlüsselung zwischen zwei Parteien mit bekannten öffentlichen Schlüsseln erstellen. In einigen Situationen und Verwendungsmethoden ermöglicht es direkte Geheimhaltung (Vorwärtsgeheimnis ). Der Link securitydriven.net/inferno/#DHM Key Exchange verfügbar Beispielcode.Um das Gespräch über asymmetrische Verschlüsselung zusammenzufassen. Sie sollten immer APIs auf hoher Ebene verwenden, die Sie nicht zwingen, Entscheidungen zu treffen, für die Sie nicht bereit sind. Ich würde auch empfehlen, RSA nicht mehr zu verwenden. Dies ist natürlich leichter gesagt als getan, da wir alle mit großen, bereits erstellten Anwendungen arbeiten, die möglicherweise nicht vollständig überarbeitet werden. In diesem Fall müssen Sie zumindest lernen, wie Sie RSA richtig verwenden. Außerdem rate ich Ihnen, sich mit modernen elliptischen kryptografischen Algorithmen (ECDSA, ECDH, ECIES) vertraut zu machen. Schließlich ist es wichtig, dass die Kryptografie auf hoher Ebene nicht alle Probleme auf magische Weise löst. Sie müssen sich daher an die Ziele erinnern, die Sie verfolgen. Ich zitiere aus StackOverflow, dem ich voll und ganz zustimme: „Kryptographie allein löst keine Probleme.Durch die symmetrische Verschlüsselung wird der Datenschutz nur zu einem Schlüsselverwaltungsproblem. “Ich werde ein paar Worte zu Ressourcen sagen, die für Sie nützlich sein können. Es gibt eine relativ akzeptable hochrangige SecurityDriven.Inferno- Bibliothek mit guter Dokumentation. Es gibt ein wundervolles Buch, Serious Cryptography von Jean-Philippe Aumasson, Serious Cryptography. Es bietet einen Überblick über den aktuellen Stand der Kryptographie unter Berücksichtigung der neuesten Innovationen. Außerdem habe ich das bereits erwähnte Buch Application Security in .NET, kurz und bündig, geschrieben, das gemeinfrei ist. Es enthält noch mehr Informationen zu .NET-Sicherheitsfallen. Schließlich hat Slideshare eine hervorragende Präsentation von Vladimir Kochetkov, die die Grundlagen der Anwendungssicherheitstheorie auf eine etwas vereinfachte, aber sehr solide Weise umreißt und verschiedene Gefahrenquellen erklärt.Lassen Sie uns abschließend einige zusätzliche Beispiele sehen, die ich vorbereitet habe. Ganz am Anfang habe ich über die vierte Stufe der Kryptoaufklärung gesprochen, in der erkannt wird, dass die beste Lösung möglicherweise überhaupt keine Kryptographie benötigt. Schauen wir uns ein Beispiel für eine solche Lösung an. Werfen wir einen Blick auf den klassischen .NET-Mechanismus - CSRF (Cross-Site Request Forgery, „Cross-Site Request Forgery“), der zum Schutz vor einer Klasse von Angriffen, einschließlich Cross-Site Request Forgery, entwickelt wurde. In diesem Modell haben wir einen Benutzeragenten - normalerweise einen Browser. Es wird versucht, eine Verbindung zum Server herzustellen, indem eine GET-Anforderung gesendet wird. Als Antwort sendet der Server ein CSRF-Token, das im HTML-Feld "versteckt" versteckt ist. Darüber hinaus wird der Antwort dasselbe Token als Cookie und als Header zugeordnet.Der Benutzer verarbeitet ein Formular und führt einen POST durch, der mit beiden Token zum Server zurückkehrt. Der Server prüft zum einen, ob beide Token gesendet wurden, und zum anderen, ob sie übereinstimmen. Dieser Identitätsvergleich ermöglicht es dem Server, sich vor einem Angreifer zu schützen. Dies ist ein klassischer Mechanismus, der in ASP.NET und ASP.NET Core integriert ist. Mikhail Shcherbakov machte einen ausgezeichneten Bericht, in dem die Arbeit von CSRF im Detail untersucht wurde., CSRF . , — , , , . . , (injection) , . — , AJAX, — . , , , .

,
— . , , . , .
. DotNext. DotNext 2018 Moscow — 22-23 2018 - « ».
. , , . ! .