ausarbeitung

This commit is contained in:
Thomas Florian 2022-07-24 23:18:18 +02:00
parent 7b5722d635
commit cd27abe196
6 changed files with 94 additions and 110 deletions

Binary file not shown.

View file

@ -1,17 +1,12 @@
% Diese Zeile bitte -nicht- aendern. % Diese Zeile bitte -nicht- aendern.
\documentclass[course=erap]{aspdoc} \documentclass[course=erap]{aspdoc}
%\input{Ausarbeitung.bib}
\usepackage{listings} \usepackage{listings}
\usepackage{float} \usepackage{float}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newcommand{\theGroup}{199}
%% TODO: Ersetzen Sie in den folgenden Zeilen die entsprechenden -Texte- \newcommand{\theNumber}{A505}
%% mit den richtigen Werten.
\newcommand{\theGroup}{199} % Beispiel: 42
\newcommand{\theNumber}{A505} % Beispiel: A123
\author{Dorian Zedler \and Finn Dröge \and Thomas Florian} \author{Dorian Zedler \and Finn Dröge \and Thomas Florian}
\date{Sommersemester 2022} % Beispiel: Wintersemester 2019/20 \date{Sommersemester 2022}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Diese Zeile bitte -nicht- aendern. % Diese Zeile bitte -nicht- aendern.
\title{Gruppe \theGroup{} -- Abgabe zu Aufgabe \theNumber} \title{Gruppe \theGroup{} -- Abgabe zu Aufgabe \theNumber}
@ -19,72 +14,73 @@
\maketitle \maketitle
\section{Einleitung} \section{Einleitung}
Der Begriff \glqq Hashing\grqq heißt ursprünglich \glqq zerhacken und mixen\grqq. Das bedeutet, eine Hash-Funktion zerhackt und mischt Informationen und leitet daraus ein Hash-Ergebnis ab \cite{hashing-techniques}. Der Begriff \glqq Hashing\grqq{} bedeutet ursprünglich \glqq zerhacken und mischen\grqq. Das bedeutet, eine Hashfunktion zerhackt und mischt Informationen und leitet daraus ein Hash-Ergebnis ab \cite{hashing-techniques}.
1989 hat Ron Rivest die Hash-Funktion Message-Digest 2 Algorithm (MD2) veröffentlicht. Für eine lange Zeit war der MD2-Algorithmus eine der meist verwendeten Hash-Algorithmen. Die Hauptanwendungsgebiete sind Passwortsicherung, Nachrichtenauthentifizierungscodes, digitale Signaturen und somit auch Zertifikate\cite{notoneway}. 2004 wurde die Hashfunktion im Paper 1989 hat Ron Rivest die Hashfunktion Message-Digest 2 Algorithm (MD2) veröffentlicht. Für eine lange Zeit war der MD2-Funktion eine der meist verwendeten Hashfunktionen. Die Hauptanwendungsgebiete sind Passwortsicherung, Nachrichtenauthentifizierungscodes, digitale Signaturen und somit auch Zertifikate~\cite{notoneway}. 2004 wurde die Hashfunktion im Paper \glqq The MD2 Hash Function is Not One-Way\grqq{} von Frédéric Muller als unsicher erklärt \cite{notoneway}. \\
\glqq The MD2 Hash Function is Not One-Way\grqq von Frédéric Muller als unsicher erklärt \cite{notoneway}.
In dieser Arbeit wird auf die Eigenschaften von Hashfunktionen eingegangen, sowie deren Anwendungsbereich. Schwachpunkte der MD2-Funktion werden aufgezeigt und alternative Algorithmen werden genannt. Insbesondere wird die Berechnung des MD2-Hashes erklärt und die Performance verschiedener Implementierungen analysiert.
\subsection{Die Eigenschaften kryptografischer Hashfunktionen} \subsection{Die Eigenschaften kryptografischer Hashfunktionen}
Sichere Hashfunktionen müssen 3 Kriterien erfüllen: Sichere Hashfunktionen müssen 3 Kriterien erfüllen:
\subsubsection{Preimage resistance} \subsubsection{Preimage-Resistance}
Preimage resistance besagt, dass eine Hashfunktion nur sehr schwer umkehrbar sein darf. Preimage-Resistance besagt, dass eine Hashfunktion nur sehr schwer umkehrbar sein darf.
Das heißt, dass für einen Hash die entsprechende Eingabe nur mit sehr hohem Rechenaufwand bestimmt werden kann.~\cite{notoneway} \\ Das heißt, dass die zu einem Hash gehörige Eingabe nur mit sehr hohem Rechenaufwand bestimmt werden kann.~\cite{notoneway} \\
Mit gegebenem $y = H(x)$ sollte es schwer sein, ein $x' \neq x$ zu finden, sodass gilt: \\ Mit gegebenem $y = H(x)$ sollte es schwer sein, ein $x' \neq x$ zu finden, sodass gilt: \\
$H(x') = y$.~\cite{preimage} $H(x') = y$.~\cite{preimage}
\subsubsection{Second preimage resistance} \subsubsection{Second-Preimage-Resistance}
Second preimage resistance ist dann gewährleistet, wenn es für eine gegebene Ein- und Ausgabe einer Hashfunktion sehr schwierig ist, eine weitere Eingabe zu finden, die dieselbe Ausgabe produziert.~\cite{notoneway} \\ Second-Preimage-Resistance ist dann gewährleistet, wenn es für eine gegebene Ein- und Ausgabe einer Hashfunktion sehr schwierig ist, eine weitere Eingabe zu finden, die dieselbe Ausgabe produziert.~\cite{notoneway} \\
Mit gegebenem $x$ und $y = H(x)$ sollte es schwer sein, ein $x' \neq x$ zu finden, sodass gilt: Mit gegebenem $x$ und $y = H(x)$ sollte es schwer sein, ein $x' \neq x$ zu finden, sodass gilt:
$H(x') = y$.~\cite{preimage} $H(x') = y$.~\cite{preimage}
\subsubsection{Collision resistance} \subsubsection{Collision-Resistance}
Eine collision resistance trifft dann zu, wenn es nur sehr wenige gleiche Hashs für unterschiedliche Eingaben gibt. Die Collision-Resistance ist dann erfüllt, wenn es nur sehr wenige gleiche Hashes für unterschiedliche Eingaben gibt.
Bei einer Hashfunktion mit einer hohen collision resistance ist die second preimage resistance auch hoch. Bei einer Hashfunktion mit einer hohen Collision-Resistance ist die Second-Preimage-Resistance auch hoch.
Wenn es nur wenige Kollisionen des Hashs gibt, ist es folglich auch schwerer eine Eingabe zu finden, die denselben Hash produziert.~\cite{notoneway} \\ Wenn es nur wenige Kollisionen des Hashes gibt, ist es folglich auch schwerer, eine Eingabe zu finden, die denselben Hash produziert.~\cite{notoneway} \\
Es sollte schwer sein ein $x$ und $x'$ zu finden, sodass gilt: $x \neq x'$ und $H(x) = H(x')$.~\cite{preimage} Es sollte schwer sein, ein $x$ und $x'$ zu finden, sodass gilt: $x \neq x'$ und $H(x) = H(x')$.~\cite{preimage}
\subsection{Angriffe auf Hashfunktion} \subsection{Angriffe auf Hashfunktion}
\label{section:geburtstagsparadoxon} \label{section:geburtstagsparadoxon}
Zu jeder der oben genannten Kriterien gibt es einen entsprechenden Angriff. Der Preimage-Angriff und der second Preimage-Angriff kann über eine brute-force Implementierung mit einer Komplexität von $2^n$ durchgeführt werden, wobei n die Anzahl der Bits des Hashs entspricht. Da der MD2-Algorithmus einen 128 Bit großen Hash erstellt, hat dieser eine Komplexität von $2^{128}$. Aufgrund des Geburtstagsparadoxons befindet sich der brute-force Kollisionsangriff in der Komplexität $2^{n/2}$ (für MD2: $2^{64}$). Zu jedem der oben genannten Kriterien gibt es einen entsprechenden Angriff. Der Preimage-Angriff und der Second-Preimage-Angriff kann über eine Brute-Force- \linebreak Implementierung mit einer Komplexität von $2^n$ durchgeführt werden, wobei n der Anzahl an Bits des Hashes entspricht. Da die MD2-Funktion einen 128 Bit großen Hash erstellt, hat dieser eine Komplexität von $2^{128}$. Aufgrund des Geburtstagsparadoxons hat der Brute-Force-Kollisionsangriff die Komplexität $2^{n/2}$ (für MD2: $2^{64}$).
Ein Hash-Algorithmus gilt als sicher, wenn es keine effizienteren Algorithmen als die drei brute-force Algorithmen gibt. Eine Hashfunktion gilt als sicher, wenn es keine effizienteren Angriffe als diese drei Brute-Force-Angriffe gibt.
~\cite{preimage} ~\cite{preimage}
\subsubsection{Preimage-Angriff} \subsubsection{Preimage-Angriff}
Bei einem Preimage-Angriff soll zu einem gegebenen Hash die dazu passende Nachricht gefunden werden. Bei einem Preimage-Angriff soll zu einem gegebenen Hash die dazu passende Nachricht gefunden werden.
Der aktuell schnellste Preimage-Angriff auf MD2 wurde im Jahr 2008 von Søren S. Thomsen veröffentlicht~\cite{preimage}. Der aktuell schnellste Preimage-Angriff auf MD2 wurde im Jahr 2008 von Søren S. Thomsen veröffentlicht~\cite{preimage}.
Dort wird beschrieben, wie anhand eines entdeckten Musters in der Berechnung des Hashes, die vorherigen Zustände der Berechnungstabelle aus einem Hash wiederhergestellt werden können. Es kann durch die bijektive Eigenschaft von XOR aus zwei Elementen auf das dritte geschlossen werden ~\cite{collisionattack}. Dadurch gibt es weniger mögliche Permutationen, welche die Eingangsnachricht annehmen kann. Insgesamt reduziert dieses Verfahren die Komplexität des Preimage-Angriffs von $2^{128}$ auf $2^{73}$. Dort wird beschrieben, wie die vorherigen Zwischenergebnisse, anhand eines entdeckten Musters in der Berechnung, wiederhergestellt werden können. Es kann durch die bijektive Eigenschaft von XOR aus zwei Elementen auf das dritte geschlossen werden ~\cite{collisionattack}. Dadurch gibt es weniger mögliche Permutationen, welche die Eingangsnachricht annehmen kann. Insgesamt reduziert dieses Verfahren die Komplexität des Preimage-Angriffs von $2^{128}$ auf $2^{73}$.
\subsubsection{Kollisionsangriff} \subsubsection{Kollisionsangriff}
Basierend auf ähnlichen Mustererkennungen kann auch ein Kollisionsangriff auf den MD2 Hash durchgeführt werden. Basierend auf ähnlichen Mustererkennungen kann auch ein Kollisionsangriff auf den MD2-Hash durchgeführt werden.
Somit verringert sich die Komplexität des oben genannten Geburtstagsparadoxons von $2^{64}$ auf $2^{54}$ mithilfe einer \glqq Collision Attack with Arbitrary Chaining Input\grqq ~\cite{collisionattack}. Somit verringert sich die Komplexität des oben beschriebenen Kollisionsangriffs von $2^{64}$ auf $2^{54}$ mithilfe einer \glqq Collision Attack with Arbitrary Chaining Input\grqq ~\cite{collisionattack}.
\subsection{Alternativen zu MD2} \subsection{Alternativen zu MD2}
Wegen der oben genannten Angriffen ist der MD2 Hashfunktion seit 2004 unsicher. Mögliche Alternativen wären die Nachfolger von MD2, die auch von Ron Rivest erfunden wurden: MD4 und MD5. MD4 wurde 1991 ~\cite{md4} nur ein Jahr nach Veröffentlichung bereits als unsicher erklärt und gilt daher nicht als Alternative. In 2005 wurde MD2 als unsicher gegen Kollisionsattacken deklariert ~\cite{md5}. Wegen der oben genannten Angriffe gilt die MD2-Hashfunktion seit 2004 als unsicher. Mögliche Alternativen wären die Nachfolger von MD2, die auch von Ron Rivest entwickelt wurden: MD4 und MD5. MD4 wurde 1991, nur ein Jahr nach Veröffentlichung, bereits als unsicher erklärt und gilt daher nicht als Alternative~\cite{md4}. 2005 wurde MD5 als unsicher gegen Kollisionsattacken deklariert~\cite{md5}.
Gängige Alternativen sind die Secure Hashing Algorithms (SHA-1, SHA-2, SHA-256, etc.) ~\cite{sha256}. Insbesondere SHA-256 gilt als noch als secure (stand: August 2015)~\cite{sha265secure} und wird daher als weitverbreitete Alternative genutzt. Gängige Alternativen sind die Secure Hashing Algorithms (SHA-1, SHA-2, SHA-256, etc.)~\cite{sha256}. Insbesondere SHA-256 gilt als sicher (Stand: August 2015)~\cite{sha265secure} und wird daher als weitverbreitete Alternative genutzt.
\subsection{Anwendungen von kryptografischen Hashfunktionen} \subsection{Anwendungen von kryptografischen Hashfunktionen}
Das heutzutage wohl wichtigste Anwendungsgebiet von Hashes ist das Sichern von Passwörtern zur Authentifizierung an computergestützten Systemen \cite{salt-password-hashes}. Das heutzutage wohl wichtigste Anwendungsgebiet von Hashes ist das Sichern von Passwörtern zur Authentifizierung an computergestützten Systemen~\cite{salt-password-hashes}.
Die Passwörter werden gehasht. Falls die Datenbank mit den Passwörter-Hashes in die Hände Dritter gelangt, macht dieses Hashing es unmöglich, den Klartext der Passwörter festzustellen. Die Passwörter werden gehasht für den Fall, dass eine Datenbank mit den Passwörter-Hashes in die Hände Dritter gelangt. Durch das Hashing ist es praktisch unmöglich, den Klartext der Passwörter festzustellen.
Um eine Wörterbuch-Attacke vorzubeugen, wird oft zusätzlich vor dem Hashing noch ein \glqq salt\grqq (zufällige Daten) an das Passwort angehangen, welches zusammen mit dem Hash abgespeichert wird. \\ Um eine Wörterbuch-Attacke vorzubeugen, wird oft zusätzlich vor dem Hashing noch ein \glqq salt\grqq{} (zufällige Daten) an das Passwort angehangen. \\
Eine weitere gängige Anwendung ist die digitale Signierung von Daten. Hierbei wird der aus den Daten gebildete Hash signiert \cite{pgp-message-formats}. So kann sichergestellt werden, dass jene Daten nach der Signierung nicht mehr verändert wurden, da jede kleine Änderung an den Daten den Hash stark verändern würde. \\ Eine weitere gängige Anwendung ist die digitale Signierung von Daten. Hierbei wird der aus den Daten gebildete Hash signiert~\cite{pgp-message-formats}. So kann sichergestellt werden, dass jene Daten nach der Signierung nicht mehr verändert wurden, da jede kleine Änderung an den Daten den Hash stark verändern würde. \\
Auch wenn es darum geht, eine Kette von Ereignissen kryptografisch zu fixieren, ist Hashing ein wichtiges Werkzeug. In einer sogenannten Blockchain enthält jedes Element (z.B. eine Überweisung) den Hash des vorherigen Elementes.\cite{what-is-blockchain} Dadurch ist es möglich, die gesamte Kette an Überweisungen zu verifizieren. Ein praktisches Beispiel dafür sind Kryptowährungen wie Bitcoin. \\ Auch wenn es darum geht, eine Kette von Ereignissen kryptografisch zu fixieren, ist Hashing ein wichtiges Werkzeug. In einer sogenannten Blockchain enthält jedes Element (z.B. eine Überweisung) den Hash des vorherigen Elementes~\cite{what-is-blockchain}. Dadurch ist es möglich, die gesamte Kette an Überweisungen zu verifizieren. Ein praktisches Beispiel dafür sind Kryptowährungen wie Bitcoin. \\
\subsection{Berechnung von Prüfsumme und Hashwert in der MD2 Hashfunktion} \subsection{Berechnung von Prüfsumme und Hashwert in der MD2-Hashfunktion}
\label{section:berechnung} \label{section:berechnung}
Die MD2 Hashfunktion lässt sich in drei Schritte gliedern: Padding hinzufügen, Prüfsumme berechnen und einer finalen Berechnung. Die MD2-Hashfunktion lässt sich in drei Schritte gliedern: Padding hinzufügen, Prüfsumme berechnen und einer finalen Berechnung.
\subsubsection{Padding} \subsubsection{Padding}
\label{section:padding} \label{section:padding}
Damit eine Nachricht mit MD2 gehasht werden kann, muss diese die Länge der Nachricht zunächst ein Vielfaches von 16 sein. Damit eine Nachricht mit MD2 gehasht werden kann, muss die Länge der Nachricht zunächst ein Vielfaches von 16 sein.
MD2 benutzt dafür das Paddingverfahren PKCS\#7. Es fügt an eine Nachricht N Bytes mit dem Wert N an. \\ MD2 benutzt dafür das Paddingverfahren PKCS\#7. Dieses fügt an eine Nachricht N Bytes mit dem Wert N an. \\
Im folgenden Beispiel ist N = 5: Im folgenden Beispiel ist N = 5:
\begin{figure}[H] \begin{figure}[H]
@ -93,35 +89,35 @@ Im folgenden Beispiel ist N = 5:
\caption{Padding Beispiel} \caption{Padding Beispiel}
\end{figure} \end{figure}
Bei dem Verfahren muss mindestens ein Byte eingefügt werden. Somit wird, falls die Nachricht bereits ein Vielfaches von 16 ist, genau 16 Bytes mit dem Wert 16 angefügt. Damit stellt man sicher, dass das letzte Byte eindeutig zum Padding gehört. \\ Bei dem Verfahren muss mindestens ein Byte eingefügt werden. Somit werden genau 16 Bytes mit dem Wert 16 angefügt, falls die Nachricht bereits ein Vielfaches von 16 ist. Damit stellt man sicher, dass das letzte Byte eindeutig zum Padding gehört. \\
Außerdem wird so eine triviale Preimage-Angriff verhindert: \\ Zudem wird ein trivialer Preimage-Angriff verhindert: \\
Zur Vereinfachung wird angenommen, dass das erwähnte Paddingverfahren die Nachrichtenlänge auf ein Vielfaches von 8 bringt. \\ Zur Vereinfachung wird angenommen, dass das erwähnte Paddingverfahren die Nachrichtenlänge auf ein Vielfaches von 8 bringt. \\
Sei $m_1 = [01, 01, 01, 01, 01]$ die Eingabe, dargestellt im Hexadezimalformat, dann wird $m_1$ mit diesem Paddingverfahren zu $p_1 = [01, 01, 01, 01, 01, 03, 03, 03]$ erweitert.\\ Sei $m_1 = [01, 01, 01, 01, 01]$ die Eingabe. Dann wird $m_1$ mit diesem Paddingverfahren zu $p_1 = [01, 01, 01, 01, 01, 03, 03, 03]$ erweitert.\\
Wird nun $m_2 = [01, 01, 01, 01, 01, 03, 03, 03]$ gewählt, so ist das Padding davon $p_2 = [01, 01, 01, 01, 01, 03, 03, 03]$, ohne die oben genannte Bedingung. Dadurch kommt es zu $hash(m_1) = hash(m_2)$, wobei $m_1 \neq m_2$. Existiert die Bedingung der Mindestpaddinggröße nicht, so könnte nun $m_2 = [01, 01, 01,$ \linebreak $01, 01, 03, 03, 03]$ als Nachricht gewählt werden. Dann ist $m_2$ inklusive Padding: \linebreak $m_2 = p_1 = [01, 01, 01, 01, 01, 03, 03, 03]$. Dadurch kommt es zu $hash(m_1) = hash(m_2)$, wobei $m_1 \neq m_2$.
Mit der Bedingung ergibt sich $p'_2 = [01, 01, 01, 01, 01, 03, 03, 03, 08, 08, 08, 08, 08, 08, 08, 08]$. In der Regel gilt dann $hash(m_1) \neq hash(m_2)$ und deswegen ist kein trivialer Preimage-Angriff möglich. \\ Mit der Mindestpaddinggröße ergibt, sich $p'_2 = [01, 01, 01, 01, 01, 03, 03,$ \linebreak $03, 08, 08, 08, 08, 08, 08, 08, 08]$, was den Preimage-Angriff verhindert. In der Regel gilt dann $hash(m_1) \neq hash(m_2)$, weshalb kein trivialer Preimage-Angriff möglich ist. \\
Beim MD2-Hash gilt zusätzlich, dass für den Fall einer leeren Nachricht ein Padding mit 16 Bytes angefügt wird. Die Prüfsumme und der Hash kann damit, ohne eine Fallunterscheidung gebildet werden~\cite{md2man},~\cite{cmsman}. Beim MD2-Hash gilt zusätzlich, dass für den Fall einer leeren Nachricht ein Padding mit 16 Bytes angefügt wird. Die Prüfsumme und der Hash können damit ohne eine Fallunterscheidung gebildet werden~\cite{md2man},~\cite{cmsman}.
\subsubsection{Substitutionsbox} \subsubsection{Substitutionsbox}
\label{section:substitution} \label{section:substitution}
Zur weiteren Berechnung wird eine Substitutionsbox verwendet, die sich aus Nachkommastellen von $\pi$ zusammensetzen lässt. Zur weiteren Berechnung wird eine Substitutionsbox verwendet, die sich aus Nachkommastellen von $\pi$ zusammensetzen lässt.
Hierbei werden die Nachkommastellen mithilfe des Durstenfeld shuffle ausgewählt.~\cite{pidigits} Hierbei werden die Nachkommastellen mithilfe des Durstenfeld-Shuffle ausgewählt.~\cite{pidigits}.
Die Zahlen der Box werden als Zufallszahlen angesehen und werden verwendet um das Ergebnis zu streuen. Die Zahlen der Box werden als Zufallszahlen angesehen und werden verwendet, um das Ergebnis zu streuen.
\subsubsection{Prüfsumme} \subsubsection{Prüfsumme}
An die Padding-Bytes werden 16 weitere Bytes angehängt, die sogenannte Prüfsumme. Diese Bytes werden zunächst mit 0 initialisiert. An die Padding-Bytes werden 16 weitere Bytes angehängt, die sogenannte Prüfsumme. Diese Bytes werden zunächst mit 0 initialisiert.
Für die Berechnung der Prüfsumme wird für jedes n-te Byte der Prüfsumme die XOR-Operation auf das Byte selbst mit einem Wert von der Substitutionsbox $\pi$ ausgeführt: Für die Berechnung der Prüfsumme wird für jedes n-te Byte der Prüfsumme die XOR-Operation auf das Byte selbst mit einem Wert von der Substitutionsbox $\pi$ ausgeführt:
\[ Pr"ufsumme[n] = Pr"ufsumme[n] \oplus \pi [k] \] \[ Pr"ufsumme[n] = Pr"ufsumme[n] \oplus \pi [k] \]
Das Element k der Substitutionsbox $\pi$ in dem i-ten Schleifendurchlauf ist das Ergebnis der XOR-Operation von dem (i*16 + n)-te Byte der Nachricht (inklusive Padding) mit dem vorherigen Byte der Prüfsumme: Der Index k der Substitutionsbox $\pi$ in dem i-ten Schleifendurchlauf ist das Ergebnis der XOR-Operation von dem $(i*16 + n)$-ten Byte der Nachricht (inklusive Padding) mit dem vorherigen Byte der Prüfsumme:
\[ k = Nachricht[i*16 + n] \oplus Pr"ufsumme[n-1 \mod 16] \] \[ k = Nachricht[i*16 + n] \oplus Pr"ufsumme[n-1 \mod 16] \]
Hierbei gilt es zu beachten, dass der Vorgänger vom 0-ten Byte der Prüfsumme das letzte Byte ist. Deswegen ist die Formel für den Vorgänger von n als $n-1 \mod 16$ definiert. \\ Hierbei gilt es zu beachten, dass das erste Byte keinen Vorgänger hat. Hier wird das letzte Byte als Vorgänger verwendet. Deswegen ist die Formel für den Vorgänger von n als $n-1 \mod 16$ definiert. \\
In der folgenden Grafik wird die Berechnung des achten Bytes der Prüfsumme veranschaulicht: In der folgenden Grafik wird die Berechnung des achten Bytes der Prüfsumme veranschaulicht:
\begin{figure}[H] \begin{figure}[H]
\centering \centering
\includegraphics[width=1.0\columnwidth]{MD2Checksum.png} \includegraphics[width=1.0\columnwidth]{MD2Checksum.png}
\caption{MD2 Prüfsumme ~\cite{md2berechnung}} \caption{MD2-Prüfsumme ~\cite{md2berechnung}}
\end{figure} \end{figure}
Beispielsweise wird im dritten Schleifendurchlauf das oben dargestellte achte Byte der Prüfsumme wie folgt berechnet: Beispielsweise wird im dritten Schleifendurchlauf das oben dargestellte achte Byte der Prüfsumme wie folgt berechnet:
@ -136,20 +132,21 @@ Zur finalen Berechnung werden zunächst weitere 48 Byte Speicher alloziert und m
Im Folgenden werden diese 48 Bytes in 3 Blöcke unterteilt: Block A beschreibt die ersten 16 Byte, Block B die mittleren 16 Byte und Block C die letzten 16 Byte. Im Folgenden werden diese 48 Bytes in 3 Blöcke unterteilt: Block A beschreibt die ersten 16 Byte, Block B die mittleren 16 Byte und Block C die letzten 16 Byte.
Die Daten des zuvor erzeugten Buffers aus Nachricht, Padding und Prüfsumme werden mithilfe von weiteren Operationen verarbeitet und in die entsprechenden Blöcke geschrieben. \\ Die Daten des zuvor erzeugten Buffers aus Nachricht, Padding und Prüfsumme werden mithilfe von weiteren Operationen verarbeitet und in die entsprechenden Blöcke geschrieben. \\
Als Erstes wird vom Buffer ein 16-Byte Block in Block B kopiert. Als Erstes wird vom Buffer ein 16-Byte Block in Block B kopiert.
Anschließend wird in Block C das Ergebnis der XOR-Operation auf Block A und Block B geschrieben. Anschließend wird in Block C das Ergebnis der XOR-Operation in Block A und B geschrieben.
Danach werden alle Bytes der drei Blöcken 18 Mal durchlaufen. Danach werden alle Bytes der drei Blöcke 18 Mal durchlaufen.
Zur Berechnung eines Bytes wird die XOR-Operation auf ein Element der Substitutionsbox $\pi$ und das Byte selbst angewandt: Zur Berechnung eines Bytes wird die XOR-Operation auf ein Element der Substitutionsbox $\pi$ und das Byte selbst angewandt:
\[ Byte[k] = Byte[k] \oplus \pi [t] \] \[ Byte[k] = Byte[k] \oplus \pi [t] \]
Das ausgewählte Element der Substitutionsbox wird durch das vorherige Byte und den Schleifendurchlauf j bestimmt: Das ausgewählte Element der Substitutionsbox wird durch das vorherige Byte und den Schleifendurchlauf j bestimmt:
\[ t = (Byte[k-1] + j) \mod 256 \] \[ t = (Byte[k-1] + j) \mod 256 \]
Somit ist jedes Byte von seinem Vorgänger abhängig. Es gilt zu beachten, dass das vorangegangene Byte vom ersten Byte ist das letzte: Somit ist jedes Byte von seinem Vorgänger abhängig. Da das erste Byte keinen Vorgänger hat, wird hier das letzte Byte als Vorgänger verwendet:
\[ Byte[0] = Byte[0] \oplus \pi [t] \] \[ Byte[0] = Byte[0] \oplus \pi [t] \]
mit mit
\[ t = (Byte[47] + j) \mod 256 \] \[ t = (Byte[47] + j) \mod 256 \]
Im ersten der 18 Schleifendurchläufe wird außerdem $t = 0$ gesetzt. im ersten der 18 Schleifendurchläufe wird außerdem $t = 0$ gesetzt.
Nach den 18 Iterationen steht der Hash in Block A. Dieses Verfahren wird für jeden 16-Byte Block des Buffers wiederholt.
Das beschriebene Verfahren lässt sich mittels Abbildung \ref{md2finalehashberechnung} darstellen. Nach allen Iterationen steht der Hash in Block A.
Das beschriebene Verfahren wird in Abbildung \ref{md2finalehashberechnung} dargestellt.
\begin{figure}[H] \begin{figure}[H]
@ -162,27 +159,27 @@ Das beschriebene Verfahren lässt sich mittels Abbildung \ref{md2finalehashberec
\section{Lösungsansatz} \section{Lösungsansatz}
\subsection{V0 - Basis Implementierung} \subsection{V0 - Basis-Implementierung}
Die basis Implementierung wurde mithilfe des Pseudocodes, welcher auf in der RFC1319 Spezifikation \cite{md2man} zu finden ist, erstellt. Sie folgt genau dem oben beschriebenen Schema \ref{section:berechnung}. Die Basis-Implementierung wurde mithilfe des Pseudocodes, welcher auf in der RFC1319 Spezifikation \cite{md2man} zu finden ist, erstellt. Sie folgt genau dem oben beschriebenen Schema (siehe Kapitel \ref{section:berechnung}).
\subsection{V1 - Optimierte Implementierung SIMD} \subsection{V1 - Optimierte Implementierung mit SIMD}
In dieser Implementierung wird der Code mittels SIMD-Operationen optimiert. In dieser Implementierung wird der Code mittels SIMD-Operationen optimiert.
Eine for-Schleife zur Berechnung des Hashes wird durch 5 Intrinsics-Operationen ersetzt. Eine for-Schleife zur Berechnung des Hashes wird durch 5 Intrinsics-Operationen ersetzt.
Beispielsweise wird in der Schleife eine XOR-Operation auf jedes Element eines Buffers ausgeführt. Beispielsweise wird in der Schleife eine XOR-Operation auf jedes Element eines Buffers ausgeführt.
Diese vielen iterativen Berechnungen werden durch den Befehl \_mm\_xor\_si128(x,y) ersetzt. Diese vielen iterativen Berechnungen werden durch den Befehl \texttt{\_mm\_xor\_si128(x,y)} ersetzt.
\\ \\
Das Einsetzen des Paddings wird mithilfe einer LUT gemacht. Hierbei verbessert sich die Laufzeit jedoch nicht, weil man trotzdem ein memcpy() aufrufen muss. Das Einsetzen des Paddings wird mithilfe einer LUT gemacht. Hierbei verbessert sich die Laufzeit jedoch nicht, weil trotzdem ein \texttt{memcpy()} aufrufen wird.
\\ \\
Kleinere Optimierungen werden auch vorgenommen, wie das Ersetzen des modulo-Operators durch ein logisches UND. Kleinere Optimierungen werden auch vorgenommen, wie das Ersetzen des Modulo-Operators durch ein logisches UND.
\\ \\
Weitere SIMD-Optimierungen können nicht gemacht werden, da die Berechnungen von Elementen immer von der Berechnung ihres Vorgängers abhängen. Weitere SIMD-Optimierungen können nicht gemacht werden, da die Berechnungen von Elementen immer von der Berechnung ihres Vorgängers abhängen.
\subsection{V2 - Partielles Einlesen der Datei} \subsection{V2 - Implementierung mit partiellem Einlesen der Datei}
In den anderen Implementierungen wird die gesamte Datei zu Beginn in den RAM geladen. Zusätzlich muss nochmals so viel Speicher alloziert werden, um Platz für das Padding und die Prüfsumme zu schaffen. Daher ist die Dateigröße auf maximal die Hälfte des Hauptspeichers abzüglich Padding und Prüfsumme beschränkt. Der Speicherbedarf ist also in $\mathcal{O}(n)$. \\ In den anderen Implementierungen wird die gesamte Datei zu Beginn in den RAM geladen. Zusätzlich muss nochmals so viel Speicher alloziert werden, um Platz für das Padding und die Prüfsumme zu schaffen. Daher ist die Dateigröße auf maximal die Hälfte des Hauptspeichers abzüglich Padding und Prüfsumme beschränkt. Der Speicherbedarf ist also in $\mathcal{O}(n)$. \\
In dieser Implementierung wird die Datei in 16-Byte Blöcken geladen, welche jeweils einzeln verarbeitet und danach wieder aus dem Hauptspeicher gelöscht werden. Daher ist der Speicherbedarf hier in $O(1)$ und es können beliebig große Dateien verarbeitet werden. In dieser Implementierung wird die Datei in 16-Byte Blöcken geladen, welche jeweils einzeln verarbeitet und danach wieder aus dem Hauptspeicher gelöscht werden. Daher ist der Speicherbedarf hier in $\mathcal{O}(1)$ und es können beliebig große Dateien verarbeitet werden.
\\ \\
Bei der Implementierung dieser Optimierung konnten die Vorgaben aus der Aufgabenstellung nicht eingehalten werden, da sie das vollständige Laden der Datei in den Hauptspeicher voraussetzen. Auch die Funktionen Bei der Implementierung dieser Optimierung konnten die Vorgaben aus der Aufgabenstellung nicht eingehalten werden, da sie das vollständige Laden der Datei in den Hauptspeicher voraussetzen. Auch die Funktionen
\begin{lstlisting} \begin{lstlisting}
@ -199,64 +196,60 @@ void md2_hash(size_t len, const uint8_t buf[len], uint8_t out[16])
\label{fig:impl2} \label{fig:impl2}
\end{figure} \end{figure}
Die Vorteile dieser Implementierung zeigen sich in Abbildung \ref{fig:impl2}. Nachdem die Eingabegröße ca. 6 Gigabyte überschritten hat, funktioniert bei dem verwendeten Rechner nur noch diese Implementierung. Die Vorteile dieser Implementierung zeigen sich in Abbildung \ref{fig:impl2}. Nachdem die Größe der Eingabe ca. 6 Gigabyte überschreitet, funktioniert bei dem verwendeten Rechner nur noch diese Implementierung.
\subsection{V3 - Threading} \subsection{V3 - Optimierte Implementierung mit Threading}
Bei dieser Implementierung soll die Berechnung des Hashes in zwei Threads aufgeteilt werden. Ein Thread soll sich um die Berechnung der Prüfsumme durchführen, während der zweite den Hash für die Nachricht berechnen soll. \\ Bei dieser Implementierung soll die Berechnung des Hashes in zwei Threads aufgeteilt werden. Ein Thread soll sich um die Berechnung der Prüfsumme durchführen, während der zweite den Hash für die Nachricht berechnen soll. \\
Aus dem Main-Thread werden mithilfe der Funktion \texttt{pthread\_create\(\)} diese beiden Threads erstellt. Sobald diese mit ihrer Berechnung fertig sind, werden die Threads wieder mittels \texttt{pthread\_join\(\)} wieder in den Main-Thread zusammengeführt.\\ Aus dem Main-Thread werden mithilfe der Funktion \texttt{pthread\_create()} diese beiden Threads erstellt. Sobald diese mit ihrer Berechnung fertig sind, werden die Threads wieder mittels \texttt{pthread\_join()} wieder in den Main-Thread zusammengeführt.\\
Durch das Aufteilen wurde die Prüfsumme noch nicht mit in den Hash gerechnet. Dies geschieht in einem letzten Durchlauf der Hash-Berechnungs-Funktion. Durch das Aufteilen wurde die Prüfsumme noch nicht mit in den Hash gerechnet.
Deswegen wird die Funktion zur Berechnung des Hashes ein weiteres Mal aufgerufen. \\
Auch in dieser Implementierung konnten die Vorgaben der Aufgabenstellung nicht eingehalten werden. Die Funktionsköpfe mussten so angepasst werden, dass diese für Threads geeignet sind.
\subsection{V4 - Referenzimplementierung} \subsection{V4 - Referenzimplementierung}
Als vierte Implementierung wurde die Referenzimplementierung aus der RFC1319 Spezifikation \cite{md2man} übernommen. Dies dient dazu, eine Referenz für die Geschwindigkeit der anderen Algorithmen zu haben. Außerdem ist es ein wichtiges Werkzeug, um die Korrektheit der anderen Implementierungen zu beurteilen. \label{referenzimplmentierung}
Als vierte Implementierung wurde die Referenzimplementierung aus der RFC1319 Spezifikation \cite{md2man} übernommen. Sie ist ein wichtiges Werkzeug, um die Korrektheit der anderen Implementierungen zu beurteilen.
\section{Korrektheit} \section{Korrektheit}
Um die Korrektheit der Implementierungen zu testen, wurden zwei Strategien angewendet: \\ Um die Korrektheit der Implementierungen zu testen, wurden zwei Strategien angewendet: \\
Zuerst wurden Beispielwerte von der RFC1319 Spezifikation \cite{md2man} getestet. Zusätzlich wurden einige Dateien mit zufälligen Daten getestet und mit den Ergebnissen der Referenzimplementierung aus selbigem Dokument verglichen. \\ Zuerst wurden Beispielwerte von der RFC1319 Spezifikation \cite{md2man} getestet. Zusätzlich wurden einige Dateien mit zufälligen Daten getestet und mit den Ergebnissen der Referenzimplementierung (siehe \ref{referenzimplmentierung}) verglichen. \\
Da schon kleine Fehler im Algorithmus zu sehr großen Abweichungen im Ergebnis führen würden, kann davon ausgegangen werden, dass die Implementierungen korrekt sind. \\ Da schon kleine Fehler im Algorithmus zu sehr großen Abweichungen im Ergebnis führen würden, kann davon ausgegangen werden, dass die Implementierungen korrekt sind. \\
\section{Performance-Analyse} \section{Performance-Analyse}
Getestet wurde auf einem System mit einem [CPU] Prozessor, [Takt], [RAM] Arbeitsspeicher, [OS], [Bit], [Kernel-Version]. Kompiliert wurde mit GCC[Version] mit der Option \texttt{-O3}. Getestet wurde auf einem System mit einem i7-2670QM Prozessor, 2.20GHz, 11GB Arbeitsspeicher, Ubuntu 20.04, 64Bit, Linux-Kernel 5.4. Kompiliert wurde mit GCC 9.3.0 mit der Option \texttt{-O3}.
Die Berechnung wurden mit Eingabegrößen von [Start] bis [Ende] jeweils [Wiederholungen] mal durchgeführt und das arithmetische Mittel für jede Eingabegröße wurde in folgendes Diagramm eingetragen: Die Berechnungen wurden jeweils 3-mal durchgeführt und das arithmetische Mittel für jede Eingabegröße wurde berechnet.
Auch für die Performance-Analyse wurden zwei Strategien gewählt: \\ Um einen groben Überblick über die Performance zu bekommen, wurde zunächst die Laufzeit der gesamten Berechnung des Hashes gemessen. \\
Um einen groben Überblick über die Performance zu bekommen, wurde zunächst die Laufzeit der gesamten Berechnung des Hashes gemessen. \ref{fig:benchmarks1} \\
\begin{figure}[H] \begin{figure}[H]
\begin{minipage}[t]{0.5\linewidth}
\centering \centering
\includegraphics[width=1.0\columnwidth]{Performance5GB.png} \includegraphics[width=0.80\columnwidth]{Performance5GB.png}
\caption{Laufzeitanalyse $\mathcal{O}(n)$} \caption{Laufzeitanalyse}
\label{fig:benchmark1} \label{fig:benchmark1}
\end{minipage}
\begin{minipage}[t]{0.5\linewidth}
\centering
\includegraphics[width=1\columnwidth]{Performance5GB.png}
\caption{Laufzeitanalyse $\mathcal{O}(n)$}
\label{fig:benchmark2}
\end{minipage}
\end{figure} \end{figure}
In Abbildung \ref{benchmark1} ist zu erkennen, dass die asymptotische Laufzeit aller Algorithmen in $\mathcal{O}(n)$ liegt. Dies lässt sich dadurch begründen, dass in verschachtelten for-Schleifen maximal eine der Schleifen über n iteriert. Alle anderen Schleifen iterieren über eine konstante Zahl. \\ In Abbildung \ref{fig:benchmark1} ist zu erkennen, dass die asymptotische Laufzeit aller Algorithmen in $\mathcal{O}(n)$ liegt. Dies lässt sich dadurch begründen, dass in verschachtelten for-Schleifen maximal eine der Schleifen über n iteriert. Alle anderen Schleifen iterieren über eine konstante Zahl. \\
In den Implementierungen lassen sich sehr leichte Unterschiede in der Performanz erkennen (siehe vergrößerte Analyse in Abbildung \ref{fig:benchmark2}). Beispielsweise sind Implementierung 1 und 3 schneller als die Basisimplementierung 0, weil sie Parallelisierung bzw. SIMD ausnutzen. \\
Die größte Schwierigkeit bei der Optimierung des MD2-Algorithmus ist die zweite Schleife der finalen Berechnung. Diese Schleife lässt sich nicht optimieren und benötigt die meiste Zeit bei der Berechnung. Um diese Problematik genauer zu analysieren, wurde die zweite Performance-Analyse entwickelt.
Hierzu wird die Gesamtlaufzeit in drei Teil-Laufzeiten gegliedert: die Berechnung der Prüfsumme, der ersten sowie der zweiten Schleife im finalen Schritt.\\
Das Verhältnis dieser verschiedenen Laufzeiten ist in Abbildung \ref{fig:verhaeltnis} dargestellt.
Die größte Schwierigkeit bei der Optimierung des MD2-Algorithmus ist die zweite Schleife der finalen Berechnung. Diese Schleife lässt sich nicht optimieren und benötigt die meiste Zeit bei der Berechnung. Um diese Problematik genauer zu analysieren, wurde die zweite Performance-Analyse entwickelt. Hierbei wird die Laufzeit der Berechnung der Prüfsumme, der ersten Schleife im finalen Schritt und der zweiten Schleife im finalen Schritt, separat gemessen. \\
Mit diesem Test ist feststellbar, dass die zweite Schleife im finalen Schritt den Großteil der gesamten Laufzeit ausmacht. Das erklärt, weshalb der Effekt der oben beschriebenen Optimierungen minimal ist. Das Verhältnis dieser verschiedenen Laufzeiten ist im folgenden Diagramm dargestellt:
\begin{figure}[H] \begin{figure}[H]
\centering \centering
\includegraphics[width=1.0\columnwidth]{PiechartRuntime.png} \includegraphics[width=0.8\columnwidth]{PiechartRuntime.png}
\caption{Verhältnis der Teil-Laufzeiten (in Prozent) ~\cite{md2berechnung}} \caption{Verhältnis der Teil-Laufzeiten (in Prozent) ~\cite{md2berechnung}}
\label{fig:verhaeltnis}
\end{figure} \end{figure}
Gemessen wurde mit 100 Megabyte Eingabedaten und dem Durchschnitt über zehn Wiederholungen. Gemessen wurde mit 100 Megabyte Eingabedaten und dem Durchschnitt über zehn Wiederholungen.\\
Mit diesem Test ist feststellbar, dass die zweite Schleife im finalen Schritt den Großteil der gesamten Laufzeit ausmacht. Das erklärt, weshalb der Effekt der oben beschriebenen Optimierungen minimal ist.
In der dritten Implementierung (Threads) kann also lediglich ein Speed-Up durch die Parallelisierung der Prüfsumme erreicht werden. Somit werden bei 100 Megabyte maximal 2,31\% der Geschwindigkeit des Programms optimiert. \\
In der ersten Implementierung wird die erste Schleife mithilfe von SIMD Operationen optimiert. Hier kann der Speed-Up maximal 1.56\% betragen. In der dritten Implementierung (Threading) kann lediglich ein Speed-Up durch die Parallelisierung der Prüfsumme erreicht werden. Somit werden bei 100 Megabyte \linebreak maximal 2,31\% der Geschwindigkeit des Programms optimiert.
In der ersten Implementierung (SIMD) wird die erste Schleife mithilfe von SIMD-Operationen optimiert. Hier kann der Speed-Up maximal 1,56\% betragen.
Diese beiden Erkenntnisse lassen auch aus den Abbildungen \ref{fig:barchart1} und \ref{fig:barchart2} ablesen. Diese beiden Erkenntnisse lassen auch aus den Abbildungen \ref{fig:barchart1} und \ref{fig:barchart2} ablesen.
\begin{figure} [H] \begin{figure} [H]
@ -274,28 +267,19 @@ Diese beiden Erkenntnisse lassen auch aus den Abbildungen \ref{fig:barchart1} un
\end{minipage} \end{minipage}
\end{figure} \end{figure}
Im ersten Balkendiagramm ist der Speed-Up kaum zu erkennen. Dies lässt sich mit der unoptimierten zweiten Schleife begründen. Beim Vergrößern des Diagramms (siehe Abbildung \ref{fig:barchart2}) wird der oben beschriebene, geringe Speed-Up in Implementierung 3 erkennbar. Implementierung 1 hat einen noch kleineren Speed-Up aufgrund der Tatsache, dass nicht die vollen 1.56\% optimiert werden können. \newpage
Im ersten Balkendiagramm ist der Speed-Up kaum zu erkennen. Dies lässt sich mit der nicht optimierbaren zweiten Schleife begründen. Beim Vergrößern des Diagramms (siehe Abbildung \ref{fig:barchart2}) wird der oben beschriebene, geringe Speed-Up in Implementierung 3 erkennbar. Implementierung 1 hat einen noch kleineren Speed-Up, da mit SIMD nicht die vollen 1,56\% wegoptimiert werden können.
\\
%Die Referenz-Implementierung hatte ab ca 4000MB eine deutlich kürzere Laufzeit als alle anderen Implementierungen. Das ließ sich darauf zurückführen, dass in dieser Implementierung für die Länge der Daten der Typ \code{unsigned int} verwendet wurde. Daher kam es bei rund 4,3 Gigabyte zu einem Überlauf und es wurden nicht mehr alle Daten verarbeitet. Nach dem Lösen dieses Problems entsprach das Ergebnis unseren Erwartungen \ref{fig:benchmarks}.
%%
%In Abbildung \ref{fig:benchmarks} ist zu erkennen, dass die korrigierte Referenzimplementierung am langsamsten ist. Das lässt sich damit begründen, dass nicht in-place direkt auf dem Array gearbeitet wird. Stattdessen wird in jeder Iteration der Hauptschleife ein neuer Zwischenspeicher angelegt, welcher am Ende der Iteration in den Buffer kopiert wird. Durch diese vielen Kopiervorgänge ändert sich die Laufzeitklasse nicht, da sie eine konstante Laufzeit haben. Allerdings erhöht sich durch diesen Overhead trotzdem die tatsächliche Laufzeit. DAS MÜSSEN WIR STREICHEN... ES GEHT NICH MEHR AUS DEN BENCHMARKS HERVOR :'(
\section{Zusammenfassung und Ausblick} \section{Zusammenfassung und Ausblick}
Der MD2-Algorithmus wurde im Jahr 1989 entwickelt und war lange ein Standard-Hashverfahren für kryptografische Anwendungsbereiche. Er wurde 2004 als aufgrund von Attacken als unsicher deklariert. Der MD2-Algorithmus wurde im Jahr 1989 entwickelt und war lange ein Standard-Hashverfahren für kryptografische Anwendungsbereiche. Er wurde 2004 aufgrund von Attacken als unsicher deklariert.
Im Projekt hat sich herausgestellt, dass sich der Algorithmus nur schwer optimieren lässt. Kleine Optimierungen können bei sehr großen Datenmengen mittels Threads umgesetzt werden. Diese Schwerfälligkeit des Optimierens bringt aber auch Vorteile für den Algorithmus, denn alle Angriffe müssen Permutationen ausprobieren, bis das gewünschte Ergebnis erreicht wird. Ist es zeitintensiv, den Hash zu berechnen, werden auch Angriffe sehr aufwändig.\\ Während dem Implementieren hat sich herausgestellt, dass sich der Algorithmus nur schwer optimieren lässt. Kleine Optimierungen können bei großen Datenmengen mittels Threading umgesetzt werden.
Weitere Optimierungsmöglichkeiten, welche in diesem Projekt nicht umgesetzt wurden, könnten möglicherweise aus dem Vorgehen der Angriffe resultieren. Vor allem wäre es möglich das, in den Angriffen erkannte, Muster als Anlaufstelle für weitere Parallelisierungen im Code zu benutzen.\\ Diese Schwerfälligkeit des Optimierens bringt aber auch Vorteile für den Algorithmus, denn Brute-Force-Angriffe müssen Permutationen ausprobieren, bis das gewünschte Ergebnis erreicht wird. Je zeitintensiver die Berechnung von Hashes sind, desto aufwendiger werden auch die entsprechenden Angriffe.\\
Durch Moore's Law kann davon ausgegangen werden, dass sich die Kosten eines Angriffes auf ein Kryptosystem alle 18 Monate halbieren~\cite{mooreslaw}. Deswegen ist es nötig immer komplexere und aufwändigere Hash-Algorithmen zu entwickeln, welche veraltete Algorithmen, wie MD2 und in der Zukunft auch die genannten Alternativen, ablösen werden. Weitere Optimierungsmöglichkeiten, welche in diesem Projekt nicht umgesetzt wurden, könnten möglicherweise aus dem Vorgehen der genannten Angriffe resultieren. Dabei könnte es möglich sein, das in den Angriffen erkannte Muster als Anlaufstelle für weitere Parallelisierungen im Code zu benutzen.\\
Durch Moore's Law kann davon ausgegangen werden, dass sich die Kosten eines Angriffes auf ein Kryptosystem alle 18 Monate halbieren~\cite{mooreslaw}. Deswegen ist es nötig immer komplexere und aufwändigere Hashfunktionen zu entwickeln. In der Zukunft müssen auch die genannten Alternativen zu MD2, so wie MD2 heute, durch solche neuen Hashfunktionen abgelöst werden.
% TODO: Fuegen Sie Ihre Quellen der Datei Ausarbeitung.bib hinzu
% Referenzieren Sie diese dann mit \cite{}.
% Beispiel: CR2 ist ein Register der x86-Architektur~\cite{intel2017man}.
\bibliographystyle{plain} \bibliographystyle{plain}
\bibliography{Ausarbeitung}{} \bibliography{Ausarbeitung}{}
\end{document}% \end{document}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB