Das Knapsack-Kryptosystem Frank Hellweg 21. Februar 2006 1 Einleitung Das Knapsack-Kryptosystem wurde 1978 von den amerikanischen Kryptologen Martin Hellman und Ralph Merkle entwickelt [MH78] und war eines der ersten Public-Key-Kryptosysteme. Wegen seiner Unkompliziertheit (z.B. müssen im Gegensatz zum RSA-System keine großen Primzahlen gesucht werden) erfreute es sich anfangs großer Beliebtheit. Andere Kryptologen wiesen allerdings schon von Beginn an darauf hin, dass das Verfahren möglicherweise nicht sicher sei; beweisen konnte dies jedoch erst Adi Shamir, der 1982 das originale System von Merkle und Hellman brechen konnte [S82]. In der Folge gab es eine Vielzahl von ähnlichen Kryptosystemen, jedoch konnten bis auf eine, das Chor-Rivest-Kryptosystem [CR88], alle Varianten der Rucksackkryptographie gebrochen werden. Diese Ausarbeitung befasst sich mit dem von Merkle und Hellman stammende Originalsystem, der vorgestellte L3 Angriff funktioniert aber auch mit anderen Knapsack-Systemen. 2 2.1 Grundlagen Das Subset-Sum-Problem Das Knapsack-Kryptosystem beruht auf dem mathematischen Problem SubsetSum, das einen Spezialfall des Rucksackproblems (engl. Knapsack, daher auch der Name des Kryptosystems) darstellt: Definition 1 (Subset-Sum). Seien S, n ∈ N, a ∈ Nn . Gesucht ist ein Vektor x ∈ {0, 1}n , so dass a · x = S. Mit anderen Worten: Wir suchen für eine gegebene Summe S und einen Vektor a aus positiven ganzen Zahlen eine Auswahl dieser Zahlen, die aufsummiert genau S ergibt. Eine 1 im i-ten Element des Vektors x bedeutet dabei, dass die i-te Zahl in die Summe einbezogen wird, eine 0, dass sie in der Summe nicht enthalten ist. Vom Subset-Sum-Problem wird angenommen, dass es sich deterministisch im Allgemeinen nicht in polynomieller Zeit lösen lässt. Eine Reduktion von 3-SAT 1 auf Subset-Sum wird in [CLRS01] angegeben. Wie wir gleich sehen werden, betrachten wir jedoch nur eine bestimmte Unterklasse von Problemen. Im folgenden werde ich ein Tupel (a, S) aus Vektor a und Summe S kurz Knapsack nennen. Definition 2. Sei a ∈ Nn . Dann heißt a extra simpel, falls für alle 1 ≤ i ≤ n gilt: X aj < ai 1≤j<i Wir setzen also voraus, dass jedes Element des Vektors echt größer ist als die Summe aller vorhergehenden Elemente. Beispiel. Der Vektor a = (1, 2, 4, 8) ist extra simpel, da 2 > 1, 4 > 2 + 1 = 3, 8 > 4 + 2 + 1 = 7. Nebenbei sei erwähnt, dass das Lösen des Knapsacks (a, S) für ein beliebiges S dem Finden einer vierstelligen Binärdarstellung von S entspricht. Außerdem fällt auf, dass es sich bei a um den kleinsten extra simplen P Vektor mit vier Stellen handelt, denn für 1 ≤ i ≤ n gilt: 1≤j<i aj = ai − 1. n Satz 1. Ein Knapsack (a, S) ∈ {(c, K) | c ∈ Z>0 , S ∈ Z>0 } lässt sich in polynomieller Zeit lösen, falls a extra simpel ist. Beweis: Betrachte Algorithmus 1. Algorithmus 1 1: function KnapsackGreedy(a, S) 2: for i ← n downto 1 do 3: if a[i] ≤ S then 4: x[i] ← 1 5: S ← S − a[i] 6: else 7: x[i] ← 0 8: return x Der Algorithmus durchläuft den übergebenen Vektor a von hinten nach vorne, d.h. vom größten zum kleinsten Element. Ist das aktuelle Element ai kleiner oder gleich der Restsumme S, dann wird das entsprechende Bit xi im Ergebnisvektor x auf 1 gesetzt und die Restsumme um ai verringert. Andernfalls wird das entsprechende Ergebnisbit auf 0 gesetzt. Lemma 1. Algorithmus 1 hat polynomielle Laufzeit. Beweis: Der Algorithmus beinhaltet nur eine Schleife, die n mal durchlaufen wird. Im Inneren dieser Schleife werden nur Vergleiche und Divisionen ausgeführt, weswegen wir bei kleinen Zahlen eine worst-case-Laufzeit von O(n) erhalten, bei großen Zahlen, für die Vergleiche und Divisionen nicht als einfache Prozessoroperationen durchführbar sind, eine worst-case-Laufzeit von O(n · 2 log m), in Abhängigkeit von der größten beteiligten Zahl m. Die ermittelte Laufzeit ist polynomiell. 2 Lemma 2. Falls a extra simpel ist, kann der Knapsack (a, S) höchstens eine Lösung haben. Gibt es eine solche, so ermittelt Algorithmus 1 sie. Beweis: Sei x die Ausgabe des Algorithmus. Wir nehmen an, x sei keine korrekte Lösung des Knapsacks (a, S). Vorausgesetzt, (a, S) hat eine Lösung, gibt es y ∈ {0, 1}n mit a · y = S, d.h. y ist Lösung von (a, S) und y 6= x. Betrachte nun i = max{j | xj 6= yj }. Es gilt X X Ki := aj · xj = aj · yj , i<j≤n i<j≤n da i die am weitesten rechts stehende Stelle ist, an der sich x und y unterscheiden. Außerdem ist S − Ki gleich dem Rest der Summe S im Iterationsschritt i. Wir unterscheiden zwei Fälle: Fall 1: xi = 1, yi = 0. Da xi = 1, muss laut Algorithmus S − Ki ≥ ai gelten. Da a extra simpel ist, gilt: X aj < ai ≤ S − Ki 1≤j<i X =⇒ aj · yj + 0 · ai < S − Ki 1≤j<i =⇒ X aj · yj + 0 · ai + Ki = y · a < S 1≤j<i =⇒ y ist keine Lösung von (a, S) Fall 2: xi = 0, yi = 1. Da xi = 0, muss laut Algorithmus S − Ki < ai gelten. =⇒ 1 · ai + Ki > S =⇒ X aj · yj + 1 · ai + Ki = y · a > S 1≤j<i =⇒ y ist keine Lösung von (a, S) Aus Fall 1 und 2 folgt, dass x Lösung von (a, S) ist. Der Beweis, dass x die einzige Lösung von (a, S) ist, folgt analog. Korrolar: Aus Lemma 1 und Lemma 2 folgt die Korrektheit von Satz 1. 3 2 3 Funktionsweise Wir kennen nun ein NP-schweres Problem, Subset-Sum, und eine Teilmenge von Problemen, die wir in polynomieller Zeit lösen können, nämlich die auf extra simplen Vektoren basierenden Subset-Sum-Probleme. Diesen Umstand können wir für ein asymmetrisches Kryptosystem nutzen, indem wir privat einen extra simplen Knapsack nutzen, diesen aber nur nach Verstecken seiner Eigenschaften als öffentlichen Schlüssel freigeben. Dieses Verstecken werden wir durch eine modulare Multiplikation des extra simplen Vektors erreichen. 3.1 Initialisierung Zum Initialisieren des Knapsack-Systems sind folgende Schritte nötig: 1. Wähle extra simplen Vektor a ∈ Zn>0 P 2. Wähle Modul m mit m > 1≤i≤n ai 3. Wähle w ∈ Zm mit ggT (m, w) = 1 4. Berechne w−1 mod m 5. Berechne a0 = a · w mod m 6. Veröffentliche a0 als public key; behalte a, w, w−1 und m als private key. Beispiel. Wähle a = (1, 4, 6, 15), m = 30. Wähle außerdem w = 7 mit w−1 ≡ 13 mod m. Dann ist a0 = w · a ≡ (7, 28, 12, 15). Laufzeit: Zum Berechnen von w−1 kann der erweiterte euklidische Algorithmus angewendet werden. Dieser hat eine Laufzeit von O(log 2 m), d.h. quadratisch zur Länge der eingegebenen Zahlen. Für das Ermitteln der restlichen Werte ist eine Laufzeit von höchstens O(n · log 2 m) anzusetzen. Da n ≤ log2 m, ergibt sich eine Gesamtlaufzeit von O(log 3 m). 3.2 Chiffrierung Sobald der öffentliche Schlüssel bekannt ist, können wir mit seiner Hilfe eine Nachricht verschlüsseln: 1. Teile die zu verschlüsselnde Nachricht in Blöcke der Größe n Bits auf 2. Berechne für jeden b ∈ {0, 1}n die Summe P Block 0 0 S = a · b = 1≤i≤n ai · bi . 3. Gib S weiter. Beispiel(Fortsetzung). Wir wollen die Nachricht b = (1, 1, 0, 1) verschlüsseln. Dazu berechnen wir S = a0 · b = 1 · 7 + 1 · 28 + 0 · 12 + 1 · 15 = 50. Laufzeit: Bei der Chiffrierung müssen wir für Pakete der Größe n jeweils O(n) Additionen durchführen, die jeweils eine Laufzeit von O(log m) haben. Es ergibt sich also eine Laufzeit von O(n · log m) = O(log 2 m). 4 3.3 Dechiffrierung Erhält der Besitzer des privaten Schlüssels eine wie oben beschrieben chiffrierte Nachricht S, ergibt sich der zugehörige Klartext wie folgt: 1. Berechne S 0 = S · w−1 mod m. 2. Löse den Knapsack (a, S 0 ). Da a extra simpel ist, können wir zum Lösen des Knapsacks (a, S) die Funktion KnapsackGreedy (siehe Algorithmus 1) verwenden. Beispiel(Fortsetzung). Um S 0 zu dechiffrieren, berechnen wir S 0 · w−1 = 50 · 13 = 650 ≡ 20 mod 30. Für den so erhaltenen Knapsack (a, 20) mit a = (1, 4, 6, 15) liefert uns KnapsackGreedy den Vektor x = (1, 0, 1, 1) als Lösung. Das entspricht dem oben chiffrierten Klartextvektor b. Laufzeit: Im ersten Schritt führen wir eine Multiplikation und eine Division durch, was eine Laufzeit von O(log 2 m) ergibt. Der KnapsackGreedy-Algorithmus hat hier eine Laufzeit von O(n · log m), da m die größte Zahl der Eingabe ist. Das führt zu einer Gesamtlaufzeit von O(n · log m + log 2 m) = O(log 2 m). Satz 2. Der Algorithmus arbeitet korrekt, d.h. als Lösung des Knapsacks (a, S 0 ) erhält man b. Beweis: Zu zeigen ist: a0 · b = S =⇒ a · b = S 0 und b ist die einzige Lösung von (a, S 0 ). a0 · b = S =⇒ a0 · b ≡ S mod m =⇒ a · w−1 · b ≡ S 0 · w−1 mod m =⇒ a · b ≡ S 0 mod m. P 0 Da m > 1≤i≤n ai , gilt auch a · b = S . Dass b die einzige Lösung ist, folgt aus Lemma 2. 2 4 Sicherheit Bevor wir mit dem L3 -Angriff eine konkrete Angriffsmethode kennen lernen werden, folgen einige allgemeine Sicherheitsaspekte des Knapsack-Kryptosystems. Zum Ersten ist darauf zu achten, dass der öffentliche Schlüssel kein extra simpler Vektor ist. Würden wir im obigen Beispiel m = 31 und w = 2 wählen, dann wäre a0 = (2, 8, 12, 30) selbst extra simpel. Dadurch könnte natürlich auch der Knapsack (a0 , S) einfach gelöst werden. Auch wenn der Angreifer durch irgendeine modulare Vervielfachung von a0 einen extra simplen Vektor erhält, kann er damit die Chiffre brechen. 5 Ein Angreifer könnte sich zudem einige Eigenschaften von a0 (Teilbarkeit, Reihenfolge) zunutze machen, um a und m zu bestimmen. Hellman und Merkle empfehlen deswegen, zusätzliche modulare Vervielfachungen zu anderen Moduln durchzuführen und die Elemente von a0 zu permutieren. Zu guter Letzt ist nie bewiesen worden, dass die Unterklasse von Subset-Sum, die aus den auf modularen Vielfachen von extra simplen Vektoren basierenden Probleme besteht, selbst NP-vollständig ist. Auch hier liegt also ein Risiko, da es gelingen könnte, einen polynomiellen Algorithmus zum Lösen dieser Probleme zu finden. 4.1 Der L3 -Angriff Wie oben schon erwähnt, sind bis auf eins, nämlich das Chor-Rivest-Kryptosystem [CR88], alle Knapsack-Verschlüsselungssysteme gebrochen worden, das von Merkle und Hellman vorgestellte Originalsystem 1982 von Shamir [S82]. Auch für das Chor-Rivest-Kryptosystem existiert ein Angriff bei bestimmten Parametern [SH95]. Ein sehr allgemeiner Angriff auf Knapsack-Systeme ist der L3 -Angriff, der 1985 von Lagarias und Odlyzko entdeckt wurde und der bei der Lösung des Subset-Sum-Problems ansetzt [LO85]. Die Idee hinter diesem Angriff ist, das Knapsackproblem (a0 , S), das sich aus dem öffentlichen Schlüssel a0 und dem Geheimtext S ergibt, als Problem der linearen Algebra darzustellen. Man definiert dazu folgende Vektoren: v1 v2 vn−1 vn vn+1 = (1 0 . . . 0 0 a1 ) = (0 1 . . . 0 0 a2 ) .. .. . . = (0 0 . . . 1 0 an−1 ) = (0 0 . . . 0 1 an ) = (0 0 . . . 0 0 −S) Definition 3. Gegeben ist eine Menge von linear unabhängigen Vektoren {x1 , . . . , xk }. Die Menge aller Punkte, die sich als ganzzahlige Linearkombinationen von x1 , . . . xk darstellen lassen, wird Gitter genannt. Die Vektoren x1 , . . . , xk heißen Basis dieses Gitters. Die Vektoren v1 , . . . , vn+1 sind offensichtlich linear unabhängig, sie bilden zusammen also die Basis für ein Gitter. Es fällt auf, dass sich die korrekte Lösung unseres Knapsacks als Linearkombination der Vektoren v1 . . . vn+1 darstellen lässt, nämlich v1 · x1 + v2 · x2 + . . . + vn · xn − vn+1 = (x1 , x2 , . . . , xn , 0) mit xi ∈ {0, 1} für 1 ≤ i ≤ n. Der Vektor (x1 , x2 , . . . , xn ) ist dann die Lösung des Knapsacks. Es stellt sich jedoch die Frage, wie wir eine solche Linearkombination erhalten können. Der naive“ Ansatz, alle in Frage kommenden Linearkombina” tionen durchzuprobieren, hat natürlich genausowenig polynomielle Laufzeit wie das Lösen des Knapsacks. 6 Wenn wir allerdings die Vektoren v1 . . . vn+1 betrachten, stellen wir fest, dass sie für große n sehr lang werden, da der Vektor a ja extra simpel ist, d.h. die ai für steigendes i exponentiell größer werden. Unsere gesuchte Linearkombination ist hingegen sehr kurz. Wenn wir für unser Gitter also eine reduzierte Basis berechnen, könnte unser Lösungsvektor dabei sein. Ein effizienter Algorithmus hierfür ist der L3 -Algorithmus. Dieser liefert für ein Gitter eine reduzierte Basis. Eine genaue Definition der Eigenschaften dieser Basis findet sich in [MOV96]; für uns ist an dieser Stelle wichtig, dass die Vektoren der so erzeugten Basis signifikant kürzer sind als die unserer Eingabebasis und dass wir tatsächlich mit einer hohen Wahrscheinlichkeit unseren Lösungsvektor darunter finden. Natürlich liegt die reduzierte Basis selbst auch wieder im Gitter, so dass der gefundene Vektor eine Linearkombination der Ausgangsvektoren ist. Folgender Algorithmus nutzt diesen Umstand für einen Angriff auf das KnapsackSystem: Algorithmus 2 1: function L3 -Attack(v, a, S) 2: w ← L3 (v) 3: for all wi ∈ w do 4: if wi is like (y1 , y2 , ..., yn , 0), yi ∈ {0, b}; ∀ 1 ≤ i ≤ n, b ∈ Z\{0} then 5: x ← 1b · wi 6: if a · x = S then 7: return x 8: return NULL Der Algorithmus lässt sich zuerst vom L3 -Algorithmus für das durch v beschriebene Gitter eine reduzierte Basis geben. Für diese Basis prüft er nun für jeden Vektor, ob er von der Form ist, dass auf den vorderen n Stellen entweder eine ganze Zahl b oder 0 steht und die letzte Stelle 0 ist. Ist dies der Fall, dann wird der gefundene Vektor mit 1b multipliziert und dann getestet, ob er auch eine Lösung für den übergebenen Knapsack (a, S) ist. Das muss nicht immer der Fall sein, denn vn+1 könnte in der zugehörigen Linearkombination auch mit einem anderen Faktor als b vorkommen. Haben wir eine Lösung gefunden, dann sind wir an dieser Stelle fertig. Ansonsten wird mit dem nächsten Vektor der reduzierten Basis fortgefahren. Laufzeit: Der L3 -Algorithmus hat eine Laufzeit von O(n4 + log2 m) [MOV96]. Da n ≤ log2 m gilt, ist die Laufzeit in diesem Fall O(log 4 n). Der Rest des Algorithmus hat eine Laufzeit von O(log 3 m), also ergibt sich als Gesamtlaufzeit O(log 4 m). Nun bleibt noch die Frage offen, mit welcher Erfolgswahrscheinlichkeit bei diesem Angriff zu rechnen ist. 7 Definition 4. Die Dichte eines Vektors a ist definiert als d(a) = n max1≤i≤n log2 ai . Für einen Knapsack (a, S) findet der L3 -Angriff mit hoher Wahrscheinlichkeit eine Lösung, wenn d(a) < 0, 9408 [MOV96]. Für einen Knapsack, bei dem a aus aufeinanderfolgenden Zweierpotenzen besteht, also den dichtestmöglichen Knapsack, geht die Dichte für n → ∞ gegen 1. In der Praxis sind Dichten größer als 0, 9408 also zu vernachlässigen. Für das obige Beispiel funktioniert der Angriff übrigens, obwohl die Dichte größer als 0, 9408 ist. Das kann man mit der Maple-Funktion Lattice, die eine L3 -Reduktion durchführt, leicht überprüfen: Beispiel(Fortsetzung). Für das Beispiel aus Kapitel 3 (a0 = (7, 28, 12, 15); S = 50) erzeugt der Maple-Befehl lattice([[1, 0, 0, 0, 7], [0, 1, 0, 0, 28], [0, 0, 1, 0, 12], [0, 0, 0, 1, 15], [0, 0, 0, 0, −50]]); eine reduzierte Basis bestehend aus den Vektoren (0, −1, 1, 1, −1), (1, 1, 0, 1, 0), (−1, 2, 0, 0, −1), (−2, 0, 0, 1, 1) und (0, 1, 2, 0, 2). Der Vektor (1, 1, 0, 1, 0) ist dabei von der von uns gewünschten Form. Die Überprüfung ergibt, dass 1 · 7 + 1 · 28 + 0 · 12 + 1 · 15 = 50 gilt. Also ist der Vektor (1, 1, 0, 1) die Lösung für den Knapsack. 5 Fazit Wir haben das NP-schwere Problem Subset-Sum benutzt, um über eine Trapdoor ein asymmetrisches Kryptosystem aufzubauen. Auch, wenn die verwendete Unterklasse von Problemen nicht erwiesenermaßen NP-schwer ist - es ist kein polynomieller Algorithmus bekannt, der diese Probleme löst. Trotzdem ist unser System nicht sicher, denn wir können das Problem mit nichttrivialer Wahrscheinlichkeit in polynomieller Laufzeit lösen: Unsere Worst-Case-Laufzeit ist nicht polynomiell, aber im Normalfall finden wir schneller eine Lösung. Wir stellen also fest, dass die Worst-Case-Zeitkomplexität der zugrundeliegenden Probleme keine Antwort auf die Frage gibt, ob ein Kryptosystem sicher ist. Entscheidend ist die Frage, ob diese Probleme mit einer gewissen Wahrscheinlichkeit oder sogar im Average Case schnell gelöst werden können. 8 6 Quellen [M OV 96] Menezes, van Oorschot, Vanstone – Handbook of Applied Cryptography, CRC 1996; S. 118ff [CR88] B. Chor, R.L. Rivest - A knapsack-type public-key cryptosystem based on arithmetic in finite fields, IEEE Transactions on Information Theory, Vol. 34, Nr. 5, 1988; S. 901-909. [SH95] C.P. Schnorr, H.H. Hörner - Attacking the Chor-Rivest cryptosystem by improved lattice reduction, Advances in Cryptology - Eurocrypt ’95, SpringerVerlag 1995. [M H78] Ralph C. Merkle, Martin E. Hellman - Hiding Information and Signatures in Trapdoor Knapsacks, IEEE Transactions on Information Theory, Vol. 24, Nr. 5, 1978; S. 525-530 [S82] Adi Shamir - A polynomial time algorithm for breaking the basic MerkleHellman Cryptosystem, Proceedings of the 23rd FOCS Symposium, 1982, S. 145-152 [LO85] J. C. Lagarias, A. M. Odlyzko - Solving Low Density Subset Sum Problems, Journal of the Association for Computing Machinery, Vol. 32, 1985; S. 229-246. [CLRS01] T. H. Cormen, C. E. Leiserson, R. L. Rivest, C. Stein - Introduction to Algorithms, second edition, MIT Press 2001; S. 1013ff 9