Cool
Ich fand die Challenge auf jeden Fall ganz spannend. Du hast keine Standard-Funkion gewählt die trivial zu brechen ist, und trotzdem ist die Crypto-Funktion so einfach dass man da schnell zu Ergebnissen kommt.
Die Haupt-Schwachstelle an deiner Crypto-Funktion ist dass dein Stream-Cipher s_i so krass vorhersehbar ist:
m_i = (e_i - s_i) XOR s_i
s_i = (k_i + i + 1) XOR k_i
* m_i ist die unverschlüsselte Nachricht
* e_i ist die verschlüsselte Nachricht
* k_i ist der Key (beliebig oft wiederholt)
Unabhängig vom Key gibt es nur sehr wenige Bytes die für jedes s_i in Frage kommen, das XOR setzt die meisten Bits auf Null. Mein Code rechnet einfach alle Kandidaten für jedes s_i aus. Damit kennt man alle Kandidaten für m_i. Die s_i- und m_i-Kandidaten habe ich noch durch ein Alphabet vorgefiltert, unter der Annahme dass Passwort und Nachricht aus Text bestehen. Das ist nicht zwingend nötig, macht es aber einfacher die Nachricht zu erkennen.
Selbst das reicht noch nicht ganz aus, um auf das richtige Plaintext-Passwort-Paar zu stoßen. Der Rest war "guessing". Mit ein bisschen mehr Aufwand kann man noch Wissen über die Wiederholung des Passworts miteinbeziehen, das war hier aber gar nicht nötig.
Als Referenz mein Code in Python:
Code:
message_alphabet = map(ord, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;?@[\\]^_`{|}~ ")
key_alphabet = map(ord, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
e = "4a 6f 70 74 3e 89 7b 75 3e 5b cd 69 8c 7a 81 79 7b cc 91 8e 4a 6b a2 a0 ae 75 98 a8 7d 6f 75 a0 6e 65 78 65 b6 6e 6f d0 77 6f 6f 84 c6 72 85 81 73 8f 90 c9"
e = [int(c, 16) for c in e.split()]
sis = [set(((c + i + 1) % 256) ^ c for c in key_alphabet) for i in xrange(256)]
for i in xrange(len(e)):
ms = set(((e[i] - si + 256) % 256) ^ si for si in sis[i])
ms = ms.intersection(set(message_alphabet))
print i, map(chr, sorted(ms))