Problem Summary

The ciphertext is given as a comma-separated list of ASCII codes obtained by XOR-encrypting an English plaintext with a repeating key of length 3. Each key character is a lowercase English letter, so the key belongs to a set with only \(26^3\) possibilities. The task is to recover the intended plaintext and then compute the sum of its ASCII values.

The decisive facts are that XOR is its own inverse, the key repeats every three positions, and the implementations do not use a separate cryptanalytic shortcut. They test every admissible key and rank the resulting plaintexts with an explicit English-text score.

Mathematical Approach

Let the ciphertext be \(e_0,e_1,\dots,e_{n-1}\). For a candidate key \(k=(k_0,k_1,k_2)\) with each \(k_r\in\{97,98,\dots,122\}\), the decrypted byte at position \(i\) is

$$p_i(k)=e_i \oplus k_{i \bmod 3}.$$

This turns the problem into a finite optimization problem over the key set \(K=\{97,\dots,122\}^3\).

XOR as an involution

The basic invariant is

$$x \oplus y \oplus y = x.$$

If a plaintext byte \(p_i\) was encrypted as \(e_i=p_i \oplus k_{i \bmod 3}\), then applying the same key byte again restores the original symbol:

$$e_i \oplus k_{i \bmod 3}=(p_i \oplus k_{i \bmod 3}) \oplus k_{i \bmod 3}=p_i.$$

No recurrence is needed. Once the key is fixed, every plaintext byte is determined independently by this identity, and the period 3 tells us exactly which key byte is used at each index.

The key space is small enough to exhaust

Because the key has length 3 and each character must be a lowercase ASCII letter,

$$|K|=26^3=17{,}576.$$

That is small enough for direct exhaustive search. For each key, decryption costs one XOR per ciphertext position, so the entire search is completely practical. This is why the solution does not need a more elaborate derivation from residue classes or classical frequency tables.

The method is exact relative to its scoring rule: it does not estimate the best key, it evaluates every candidate key and keeps the best-scoring plaintext.

Scoring English-looking plaintexts

The implementations use a hard validity filter followed by a weighted English-text score. If any decrypted character lies outside the ASCII interval from 9 through 126, the candidate is rejected by assigning a very large negative score. Otherwise the score is

$$S(p)=3N_{\text{space}}(p)+2N_{\text{letter}}(p)+40W_{\text{the}}(p)+25W_{\text{and}}(p)+20W_{\text{of}}(p)+20W_{\text{to}}(p),$$

where \(N_{\text{space}}\) counts spaces, \(N_{\text{letter}}\) counts uppercase and lowercase English letters, and \(W_{\text{word}}\) counts occurrences of the indicated word written with surrounding spaces.

So the selected key is

$$k^\star=\operatorname*{arg\,max}_{k\in K} S(p(k)).$$

This is the real mathematical object used by the code. The search is performed on fully decrypted texts, not by solving each key position separately.

Worked Example: a short XOR round trip

The implementations include a small checkpoint with the plaintext “hello world” and the key “abc”. Writing the key bytes as \(97,98,99\), the first encrypted values are

$$104\oplus97=9,\qquad 101\oplus98=7,\qquad 108\oplus99=15,$$

and continuing with the same period-3 pattern gives the ciphertext prefix \([9,7,15,13,13,67,\dots]\). Decrypting with the same repeating key reverses the process:

$$9\oplus97=104,\qquad 7\oplus98=101,\qquad 15\oplus99=108.$$

The original text is recovered exactly. A wrong key may still produce printable symbols, but it usually scores much worse because it contains fewer letters, fewer spaces, and few or no common English words.

Recovering the required answer

Once the maximizing plaintext \(p(k^\star)\) has been found, the required result is simply

$$A=\sum_{i=0}^{n-1} p_i(k^\star).$$

The search phase identifies the plaintext, and the final summation converts that plaintext into the requested integer.

How the Code Works

Parsing the ciphertext

The C++, Python, and Java implementations read the ciphertext as raw text and scan it character by character. Consecutive decimal digits are collected into one token, converted to an integer, and appended to the ciphertext array. Commas and line breaks act only as separators.

Enumerating candidate keys and decrypting

Next, the implementation loops through all triples of lowercase letters. For each triple, it decrypts the entire ciphertext with the repeating period-3 XOR rule and materializes the candidate plaintext as a string.

Scoring, selecting, and summing

Each candidate plaintext is checked against the ASCII-range filter and then scored with the weighted English-text formula. Whenever a candidate beats the best score seen so far, the implementation stores it as the current winner. After the full \(26^3\) search finishes, it sums the ASCII codes of the winning plaintext. Before the main search, the implementations also verify the XOR round-trip identity on a short known example.

Complexity Analysis

If \(n\) is the number of ciphertext bytes, parsing costs \(O(n)\). The exhaustive search tries \(26^3\) keys, and each key requires \(O(n)\) work to decrypt and score the text, so the dominant running time is

$$O(26^3 n).$$

Because \(26^3=17{,}576\) is a fixed constant, the runtime is linear in the message length with a moderate constant factor. The extra word-count passes in the score are over four fixed short words, so they do not change the asymptotic bound.

The memory usage is \(O(n)\): the implementations store the ciphertext, one current plaintext, and a constant amount of bookkeeping for the best score and the key enumeration.

Footnotes and References

  1. Problem page: https://projecteuler.net/problem=59
  2. XOR cipher: Wikipedia - XOR cipher
  3. ASCII: Wikipedia - ASCII
  4. Frequency analysis and language statistics: Wikipedia - Frequency analysis

Problemzusammenfassung

Der Chiffretext ist als kommagetrennte Liste von ASCII-Zahlen gegeben. Er entstand aus einem englischen Klartext durch XOR-Verschlüsselung mit einem periodischen Schlüssel der Länge 3. Jeder Schlüsselbuchstabe ist ein englischer Kleinbuchstabe, also liegt der Schlüssel in einer Menge mit nur \(26^3\) Möglichkeiten. Gesucht sind der richtige Klartext und danach die Summe seiner ASCII-Werte.

Entscheidend sind drei Punkte: XOR ist seine eigene Umkehrung, der Schlüssel wiederholt sich alle drei Positionen, und die Implementierungen verwenden keinen separaten Krypto-Trick. Stattdessen prüfen sie jeden zulässigen Schlüssel und bewerten den entstehenden Text mit einer expliziten Englisch-Heuristik.

Mathematischer Ansatz

Bezeichne den Chiffretext mit \(e_0,e_1,\dots,e_{n-1}\). Für einen Kandidatenschlüssel \(k=(k_0,k_1,k_2)\) mit \(k_r\in\{97,98,\dots,122\}\) lautet das entschlüsselte Byte an Position \(i\)

$$p_i(k)=e_i \oplus k_{i \bmod 3}.$$

Damit wird das gesamte Problem zu einer endlichen Optimierungsaufgabe über der Schlüsselmengen \(K=\{97,\dots,122\}^3\).

XOR als Involution

Die zentrale Invariante ist

$$x \oplus y \oplus y = x.$$

Wenn ein Klartextbyte \(p_i\) als \(e_i=p_i \oplus k_{i \bmod 3}\) verschlüsselt wurde, dann stellt dieselbe Schlüsselkomponente den Originalwert wieder her:

$$e_i \oplus k_{i \bmod 3}=(p_i \oplus k_{i \bmod 3}) \oplus k_{i \bmod 3}=p_i.$$

Eine Rekurrenz ist hier nicht nötig. Sobald der Schlüssel feststeht, ist jedes Zeichen unabhängig bestimmt, und die Periodenlänge 3 legt exakt fest, welches Schlüsselbyte zu welchem Index gehört.

Der Schlüsselraum ist klein genug für vollständige Enumeration

Da der Schlüssel Länge 3 hat und jeder Buchstabe ein Kleinbuchstabe im ASCII-Bereich ist, gilt

$$|K|=26^3=17{,}576.$$

Diese Zahl ist klein genug für eine direkte vollständige Suche. Für jeden Schlüssel kostet die Entschlüsselung nur ein XOR pro Position, also bleibt die gesamte Suche problemlos beherrschbar. Genau deshalb braucht die Lösung weder eine feinere Aufspaltung nach Restklassen noch klassische Häufigkeitstabellen.

Die Methode ist relativ zu ihrer Bewertungsfunktion exakt: Es wird kein Schlüssel geschätzt, sondern jeder mögliche Schlüssel geprüft und der beste Text ausgewählt.

Bewertung englisch wirkender Klartexte

Die Implementierungen kombinieren einen harten Gültigkeitsfilter mit einer gewichteten Englisch-Bewertung. Falls irgendein entschlüsseltes Zeichen außerhalb des ASCII-Intervalls von 9 bis 126 liegt, wird der Kandidat durch einen sehr großen negativen Wert verworfen. Andernfalls lautet die Punktzahl

$$S(p)=3N_{\text{space}}(p)+2N_{\text{letter}}(p)+40W_{\text{the}}(p)+25W_{\text{and}}(p)+20W_{\text{of}}(p)+20W_{\text{to}}(p),$$

wobei \(N_{\text{space}}\) die Leerzeichen zählt, \(N_{\text{letter}}\) Groß- und Kleinbuchstaben zählt und \(W_{\text{word}}\) die Vorkommen des jeweiligen Wortes mitsamt umgebenden Leerzeichen zählt.

Der ausgewählte Schlüssel ist also

$$k^\star=\operatorname*{arg\,max}_{k\in K} S(p(k)).$$

Genau dieses Optimierungsproblem wird tatsächlich gelöst. Die Implementierungen rekonstruieren die drei Schlüsselpositionen nicht getrennt, sondern bewerten immer den vollständig entschlüsselten Text.

Durchgerechnetes Beispiel: ein kurzer XOR-Rundweg

In den Implementierungen gibt es einen kleinen Test mit dem Klartext „hello world“ und dem Schlüssel „abc“. Schreibt man die Schlüsselbytes als \(97,98,99\), dann entstehen am Anfang die Werte

$$104\oplus97=9,\qquad 101\oplus98=7,\qquad 108\oplus99=15,$$

und beim Weiterlaufen mit derselben Periode 3 erhält man den Chiffretext-Präfix \([9,7,15,13,13,67,\dots]\). Entschlüsselt man mit demselben periodischen Schlüssel, kehrt sich der Prozess um:

$$9\oplus97=104,\qquad 7\oplus98=101,\qquad 15\oplus99=108.$$

Der ursprüngliche Text erscheint exakt wieder. Ein falscher Schlüssel kann zwar druckbare Zeichen erzeugen, verliert aber meistens deutlich bei der Bewertung, weil er weniger Buchstaben, weniger Leerzeichen und kaum häufige englische Wörter enthält.

Wie daraus die geforderte Zahl wird

Sobald der beste Klartext \(p(k^\star)\) gefunden ist, lautet die gesuchte Größe einfach

$$A=\sum_{i=0}^{n-1} p_i(k^\star).$$

Die Suche bestimmt den Klartext, und die abschließende Summe übersetzt ihn in die verlangte ganze Zahl.

Wie der Code arbeitet

Einlesen und Parsen des Chiffretexts

Die C++-, Python- und Java-Implementierungen lesen den Chiffretext als rohen Text und laufen Zeichen für Zeichen darüber. Aufeinanderfolgende Dezimalziffern werden zu einem Token gesammelt, in eine Zahl umgewandelt und an das Chiffretext-Array angehängt. Kommata und Zeilenumbrüche dienen nur als Trenner.

Schlüsselkandidaten durchlaufen und entschlüsseln

Danach durchläuft die Implementierung alle Tripel aus Kleinbuchstaben. Für jedes Tripel wird der gesamte Chiffretext mit der periodischen XOR-Regel der Länge 3 entschlüsselt und als Kandidatenstring aufgebaut.

Bewerten, auswählen, ASCII-Summe bilden

Jeder Kandidat wird zuerst durch den ASCII-Filter geprüft und anschließend mit der gewichteten Englisch-Funktion bewertet. Immer wenn ein Kandidat besser abschneidet als alle zuvor getesteten Schlüssel, wird er als aktueller Sieger gespeichert. Nach Abschluss der vollständigen \(26^3\)-Suche werden die ASCII-Werte dieses Siegertexts aufsummiert. Vor der Hauptsuche prüfen die Implementierungen außerdem an einem kurzen Beispiel die XOR-Rundweg-Eigenschaft.

Komplexitätsanalyse

Ist \(n\) die Anzahl der Chiffretext-Bytes, dann kostet das Parsen \(O(n)\). Die vollständige Suche testet \(26^3\) Schlüssel, und jeder Schlüssel benötigt \(O(n)\) Arbeit für Entschlüsselung und Bewertung. Die dominante Laufzeit ist also

$$O(26^3 n).$$

Da \(26^3=17{,}576\) eine feste Konstante ist, bleibt die Laufzeit linear in der Nachrichtenlänge, nur mit einem moderaten konstanten Faktor. Die zusätzlichen Wortzählungen betreffen vier feste kurze Wörter und ändern deshalb die asymptotische Schranke nicht.

Der Speicherverbrauch ist \(O(n)\): gespeichert werden der Chiffretext, jeweils ein aktueller Klartext und eine konstante Menge an Verwaltungsdaten für den bisher besten Wert und die Schlüsselschleifen.

Fußnoten und Quellen

  1. Problemseite: https://projecteuler.net/problem=59
  2. XOR-Chiffre: Wikipedia - XOR-Verknüpfung
  3. ASCII: Wikipedia - ASCII
  4. Häufigkeitsanalyse: Wikipedia - Häufigkeitsanalyse

Problem Özeti

Şifreli metin, İngilizce bir düz metnin uzunluğu 3 olan tekrarlı bir anahtarla XOR uygulanması sonucu oluşmuş ASCII kodlarının virgülle ayrılmış listesidir. Anahtarın her karakteri küçük İngilizce harftir; dolayısıyla anahtar uzayı yalnızca \(26^3\) olasılık içerir. Amaç doğru düz metni bulmak ve sonra onun ASCII değerleri toplamını hesaplamaktır.

Belirleyici üç nokta şunlardır: XOR kendi tersidir, anahtar her üç konumda bir tekrar eder ve uygulamalar ayrı bir kriptanaliz kestirimi kullanmaz. Bunun yerine tüm geçerli anahtarları deneyip ortaya çıkan metinleri açık bir İngilizce-benzerlik puanıyla sıralarlar.

Matematiksel Yaklaşım

Şifreli metni \(e_0,e_1,\dots,e_{n-1}\) ile gösterelim. Her \(k_r\in\{97,98,\dots,122\}\) olmak üzere aday anahtar \(k=(k_0,k_1,k_2)\) için \(i\). konumdaki çözülmüş bayt

$$p_i(k)=e_i \oplus k_{i \bmod 3}$$

olur. Böylece problem, \(K=\{97,\dots,122\}^3\) kümesi üzerinde sonlu bir optimizasyon problemine dönüşür.

XOR işleminin terslenebilir yapısı

Temel değişmez

$$x \oplus y \oplus y = x$$

eşitliğidir. Eğer bir düz metin baytı \(p_i\), \(e_i=p_i \oplus k_{i \bmod 3}\) biçiminde şifrelenmişse aynı anahtar baytını yeniden uygulamak orijinal karakteri geri getirir:

$$e_i \oplus k_{i \bmod 3}=(p_i \oplus k_{i \bmod 3}) \oplus k_{i \bmod 3}=p_i.$$

Burada bir yineleme bağıntısına gerek yoktur. Anahtar sabitlendiği anda her konum bağımsız olarak belirlenir ve periyot 3 kuralı hangi indekste hangi anahtar baytının kullanılacağını tamamen açık hale getirir.

Anahtar uzayı doğrudan taranabilecek kadar küçüktür

Anahtar uzunluğu 3 ve her karakter küçük ASCII harfi olduğu için

$$|K|=26^3=17{,}576.$$

Bu büyüklük tam tarama için yeterince küçüktür. Her anahtar için gereken işlem, şifreli metindeki her konumda tek bir XOR yapmaktır. Bu nedenle çözümün artık sınıflarına ayrılmış daha karmaşık bir frekans analizi kurmasına gerek kalmaz.

Yöntem, kullandığı puanlama ölçütüne göre eksiksizdir: en iyi anahtar tahmin edilmez, bütün adaylar değerlendirilir ve en yüksek puanlı düz metin seçilir.

İngilizceye benzeyen metni puanlamak

Uygulamalar önce sert bir geçerlilik filtresi, sonra ağırlıklı bir İngilizce metin puanı kullanır. Eğer çözülmüş karakterlerden herhangi biri 9 ile 126 arasındaki ASCII aralığının dışına düşerse aday çok büyük negatif bir puanla elenir. Aksi durumda puan

$$S(p)=3N_{\text{space}}(p)+2N_{\text{letter}}(p)+40W_{\text{the}}(p)+25W_{\text{and}}(p)+20W_{\text{of}}(p)+20W_{\text{to}}(p)$$

şeklindedir. Burada \(N_{\text{space}}\) boşluk sayısını, \(N_{\text{letter}}\) büyük ve küçük İngilizce harflerin sayısını, \(W_{\text{word}}\) ise ilgili sözcüğün iki yanında boşluk olacak biçimde kaç kez geçtiğini sayar.

Dolayısıyla seçilen anahtar

$$k^\star=\operatorname*{arg\,max}_{k\in K} S(p(k))$$

olur. Kodun çözdüğü gerçek matematiksel nesne budur. Yani anahtarın üç konumu ayrı ayrı çözülmez; her zaman tüm metin çözülüp puanlanır.

Çalışılmış Örnek: kısa bir XOR gidiş-dönüşü

Uygulamalardaki küçük doğrulama örneği “hello world” düz metni ve “abc” anahtarıdır. Anahtar baytlarını \(97,98,99\) olarak yazarsak ilk şifreli değerler

$$104\oplus97=9,\qquad 101\oplus98=7,\qquad 108\oplus99=15$$

olur; aynı periyot 3 düzeni sürdürülünce şifreli önek \([9,7,15,13,13,67,\dots]\) elde edilir. Aynı tekrarlı anahtarla tekrar XOR yapılınca süreç tersine döner:

$$9\oplus97=104,\qquad 7\oplus98=101,\qquad 15\oplus99=108.$$

Böylece orijinal metin eksiksiz geri gelir. Yanlış bir anahtar bazen yazdırılabilir karakterler üretebilir; fakat genellikle daha az harf, daha az boşluk ve daha az yaygın İngilizce sözcük içerdiği için puanı belirgin biçimde düşer.

İstenen sonucun elde edilmesi

En yüksek puanlı düz metin \(p(k^\star)\) bulunduğunda sorunun istediği değer yalnızca

$$A=\sum_{i=0}^{n-1} p_i(k^\star)$$

toplamıdır. Arama aşaması metni belirler, son toplam ise bu metni istenen tam sayıya çevirir.

Kod Nasıl Çalışır

Şifreli metni ayrıştırma

C++, Python ve Java uygulamaları girdi metnini ham karakter dizisi olarak okur ve karakter karakter tarar. Art arda gelen ondalık rakamlar tek bir token içinde toplanır, sayıya çevrilir ve şifreli metin dizisine eklenir. Virgüller ile satır sonları yalnızca ayırıcıdır.

Aday anahtarları deneme ve çözme

Sonraki aşamada küçük harflerden oluşan tüm üçlüler dolaşılır. Her üçlü için tüm şifreli metin, periyodu 3 olan tekrarlı XOR kuralıyla çözülür ve aday düz metin bir karakter dizisi olarak oluşturulur.

Puanlama, seçim ve ASCII toplamı

Her aday düz metin önce ASCII aralık filtresinden geçirilir, sonra ağırlıklı İngilizce puanıyla değerlendirilir. Bir aday o ana kadar görülen en iyi skoru geçtiğinde yeni en iyi metin olarak saklanır. Tüm \(26^3\) arama bittiğinde kazanan metnin ASCII kodları toplanır. Ana arama başlamadan önce uygulamalar kısa bir örnek üzerinde XOR'un gidiş-dönüş özelliğini de kontrol eder.

Karmaşıklık Analizi

\(n\) şifreli metin bayt sayısı olsun. Ayrıştırma \(O(n)\) zaman alır. Kapsamlı arama \(26^3\) anahtar dener ve her anahtar için çözme ile puanlama \(O(n)\) iş gerektirir. Baskın çalışma süresi bu yüzden

$$O(26^3 n)$$

olur. \(26^3=17{,}576\) sabit bir sayı olduğundan çalışma süresi mesaj uzunluğunda doğrusaldır; yalnızca sabit katsayısı orta büyüklüktedir. Puanlama sırasında yapılan ek kelime sayımları dört sabit kısa ifade üzerinde çalıştığı için asimptotik sınırı değiştirmez.

Bellek kullanımı \(O(n)\)'dir. Bunun nedeni şifreli metnin, o anda üretilen bir düz metnin ve en iyi skora ilişkin sabit boyutlu birkaç değişkenin tutulmasıdır.

Dipnotlar ve Kaynaklar

  1. Problem sayfası: https://projecteuler.net/problem=59
  2. XOR şifrelemesi: Wikipedia - XOR cipher
  3. ASCII: Wikipedia - ASCII
  4. Frekans analizi: Wikipedia - Frekans analizi

Resumen del Problema

El texto cifrado aparece como una lista de códigos ASCII separados por comas. Proviene de un texto en inglés cifrado con XOR usando una clave repetida de longitud 3. Cada carácter de la clave es una letra minúscula inglesa, así que el espacio de claves contiene solo \(26^3\) posibilidades. La tarea consiste en recuperar el texto correcto y después sumar sus valores ASCII.

Los hechos decisivos son tres: XOR es su propio inverso, la clave se repite cada tres posiciones y las implementaciones no usan un atajo criptanalítico independiente. En cambio, prueban todas las claves admisibles y ordenan los textos obtenidos mediante una puntuación explícita de apariencia inglesa.

Enfoque Matemático

Sea el texto cifrado \(e_0,e_1,\dots,e_{n-1}\). Para una clave candidata \(k=(k_0,k_1,k_2)\) con \(k_r\in\{97,98,\dots,122\}\), el byte descifrado en la posición \(i\) es

$$p_i(k)=e_i \oplus k_{i \bmod 3}.$$

Esto convierte todo el problema en una optimización finita sobre el conjunto \(K=\{97,\dots,122\}^3\).

XOR como involución

La identidad fundamental es

$$x \oplus y \oplus y = x.$$

Si un byte del texto plano \(p_i\) fue cifrado como \(e_i=p_i \oplus k_{i \bmod 3}\), entonces aplicar de nuevo el mismo byte de clave restaura el símbolo original:

$$e_i \oplus k_{i \bmod 3}=(p_i \oplus k_{i \bmod 3}) \oplus k_{i \bmod 3}=p_i.$$

Aquí no hace falta ninguna recurrencia. Una vez fijada la clave, cada posición queda determinada de manera independiente, y el período 3 indica exactamente qué componente de la clave actúa sobre cada índice.

El espacio de claves es lo bastante pequeño para agotarlo

Como la clave tiene longitud 3 y cada carácter debe ser una letra minúscula ASCII, se cumple

$$|K|=26^3=17{,}576.$$

Esa cantidad es lo bastante pequeña para una búsqueda exhaustiva directa. Para cada clave, descifrar cuesta un XOR por posición del texto cifrado, así que el recorrido completo es perfectamente viable. Por eso la solución no necesita una derivación más complicada por clases de residuos ni tablas clásicas de frecuencias.

El método es exacto con respecto a su criterio de puntuación: no estima la mejor clave, sino que evalúa todas y conserva el texto con la puntuación máxima.

Puntuación de textos con aspecto inglés

Las implementaciones usan primero un filtro duro de validez y luego una puntuación ponderada de texto inglés. Si algún carácter descifrado cae fuera del intervalo ASCII de 9 a 126, el candidato se descarta con una puntuación muy negativa. En caso contrario, la puntuación es

$$S(p)=3N_{\text{space}}(p)+2N_{\text{letter}}(p)+40W_{\text{the}}(p)+25W_{\text{and}}(p)+20W_{\text{of}}(p)+20W_{\text{to}}(p).$$

Aquí \(N_{\text{space}}\) cuenta espacios, \(N_{\text{letter}}\) cuenta letras inglesas mayúsculas y minúsculas, y \(W_{\text{word}}\) cuenta las apariciones de la palabra indicada escritas con espacios a ambos lados.

Por lo tanto, la clave elegida es

$$k^\star=\operatorname*{arg\,max}_{k\in K} S(p(k)).$$

Este es el objeto matemático real que utiliza el código. No se resuelven por separado las tres posiciones de la clave; siempre se evalúa el texto ya descifrado en su totalidad.

Ejemplo Desarrollado: una ida y vuelta corta con XOR

Las implementaciones incluyen una comprobación pequeña con el texto plano “hello world” y la clave “abc”. Si escribimos los bytes de la clave como \(97,98,99\), los primeros valores cifrados son

$$104\oplus97=9,\qquad 101\oplus98=7,\qquad 108\oplus99=15,$$

y al continuar con el mismo patrón de período 3 se obtiene el prefijo cifrado \([9,7,15,13,13,67,\dots]\). Al descifrar con la misma clave repetida, el proceso se invierte:

$$9\oplus97=104,\qquad 7\oplus98=101,\qquad 15\oplus99=108.$$

El texto original se recupera exactamente. Una clave incorrecta puede producir símbolos imprimibles, pero normalmente obtiene una puntuación mucho menor porque contiene menos letras, menos espacios y pocas o ninguna palabra inglesa frecuente.

Cómo se obtiene el valor pedido

Una vez hallado el texto ganador \(p(k^\star)\), la respuesta pedida por el problema es simplemente

$$A=\sum_{i=0}^{n-1} p_i(k^\star).$$

La búsqueda identifica el texto correcto y la suma final lo transforma en el entero solicitado.

Cómo Funciona el Código

Análisis del texto cifrado

Las implementaciones en C++, Python y Java leen el contenido como texto bruto y lo recorren carácter por carácter. Los dígitos decimales consecutivos se agrupan en un mismo token, se convierten en entero y se añaden al arreglo del texto cifrado. Las comas y los saltos de línea actúan solo como separadores.

Enumeración de claves y descifrado

Después, la implementación recorre todos los ternas posibles de letras minúsculas. Para cada terna descifra el mensaje completo con la regla XOR periódica de longitud 3 y construye el texto plano candidato como una cadena.

Puntuación, selección y suma ASCII

Cada texto candidato pasa primero por el filtro del rango ASCII y luego por la puntuación ponderada de inglés. Cuando un candidato supera la mejor puntuación observada hasta ese momento, pasa a ser el mejor texto actual. Tras completar la búsqueda \(26^3\), se suman los códigos ASCII del texto ganador. Antes de la búsqueda principal, las implementaciones también verifican la identidad de ida y vuelta de XOR con un ejemplo breve conocido.

Análisis de Complejidad

Si \(n\) es el número de bytes del texto cifrado, el análisis inicial cuesta \(O(n)\). La búsqueda exhaustiva prueba \(26^3\) claves, y cada clave requiere \(O(n)\) trabajo para descifrar y puntuar. Por tanto, el coste dominante es

$$O(26^3 n).$$

Como \(26^3=17{,}576\) es una constante fija, el tiempo es lineal en la longitud del mensaje con un factor constante moderado. Las búsquedas adicionales de palabras solo recorren cuatro patrones cortos fijos, de modo que no alteran la cota asintótica.

El uso de memoria es \(O(n)\), porque la implementación mantiene el texto cifrado, un texto plano actual y una cantidad constante de información auxiliar para la mejor puntuación y la enumeración de claves.

Notas y Referencias

  1. Página del problema: https://projecteuler.net/problem=59
  2. Cifrado XOR: Wikipedia - XOR
  3. ASCII: Wikipedia - ASCII
  4. Análisis de frecuencia: Wikipedia - Análisis de frecuencias

问题概述

题目给出的是一串用逗号分隔的 ASCII 整数,它们来自一段英文原文在长度为 3 的循环密钥下经过 XOR 加密后的结果。密钥的每个字符都限制为英文小写字母,因此全部可能的密钥只有 \(26^3\) 个。目标是恢复正确的英文文本,然后计算该文本全部 ASCII 值的总和。

真正决定解法的事实有三个:XOR 运算是自反可逆的,密钥每隔 3 个位置重复一次,而且这些实现并没有使用单独的密码分析捷径。它们采取的是完全枚举所有合法密钥,再用一个明确写出的英文文本评分函数对解密结果进行比较。

数学方法

设密文为 \(e_0,e_1,\dots,e_{n-1}\)。对于候选密钥 \(k=(k_0,k_1,k_2)\),其中每个 \(k_r\in\{97,98,\dots,122\}\),位置 \(i\) 上的解密字节为

$$p_i(k)=e_i \oplus k_{i \bmod 3}.$$

这样一来,整个问题就被改写成密钥集合 \(K=\{97,\dots,122\}^3\) 上的一个有限优化问题。

XOR 的自逆性质

核心不变量是

$$x \oplus y \oplus y = x.$$

如果某个明文字节 \(p_i\) 在加密时变成了 \(e_i=p_i \oplus k_{i \bmod 3}\),那么再次对它异或同一个密钥字节,就会回到原来的字符:

$$e_i \oplus k_{i \bmod 3}=(p_i \oplus k_{i \bmod 3}) \oplus k_{i \bmod 3}=p_i.$$

这里不需要任何递推关系。一旦密钥固定下来,每个位置的明文字节都由这个恒等式单独决定,而周期 3 则准确告诉我们每个位置应当使用哪一个密钥字节。

密钥空间足够小,可以完全枚举

因为密钥长度是 3,且每个字符必须是小写 ASCII 字母,所以

$$|K|=26^3=17{,}576.$$

这个数量足够小,可以直接进行穷举。对每一个候选密钥,解密只需要对每个密文字节做一次 XOR,因此整体搜索在计算上完全可行。这也是为什么解法不必额外构造按模 3 分类的频率分析推导。

从评分准则的角度看,这个方法是精确的:实现并不是“猜测”哪个密钥最好,而是把所有候选都试一遍,再选择评分最高的那一个。

把“像英文”转化为显式评分

实现先做一个硬性的有效性过滤,再计算一个带权英文评分。如果某个解密字符落在 ASCII 9 到 126 之外,就给这个候选一个非常大的负分,直接将其淘汰。否则评分定义为

$$S(p)=3N_{\text{space}}(p)+2N_{\text{letter}}(p)+40W_{\text{the}}(p)+25W_{\text{and}}(p)+20W_{\text{of}}(p)+20W_{\text{to}}(p).$$

其中 \(N_{\text{space}}\) 表示空格数量,\(N_{\text{letter}}\) 表示英文大小写字母数量,\(W_{\text{word}}\) 表示相应英文单词在两侧带空格时出现的次数。

因此最终选出的密钥是

$$k^\star=\operatorname*{arg\,max}_{k\in K} S(p(k)).$$

这就是代码真正实现的数学对象。它并不是把三个密钥位置分别求出来,而是始终对完整解密后的文本整体打分。

具体例子:一次短的 XOR 往返

实现里带有一个很小的校验例子,明文是 “hello world”,密钥是 “abc”。把密钥字节写成 \(97,98,99\) 后,前几个加密值为

$$104\oplus97=9,\qquad 101\oplus98=7,\qquad 108\oplus99=15,$$

继续按同样的 3 周期模式进行,就得到密文前缀 \([9,7,15,13,13,67,\dots]\)。再用同一个循环密钥解密,过程会完全反向:

$$9\oplus97=104,\qquad 7\oplus98=101,\qquad 15\oplus99=108.$$

于是原始文本被精确恢复。错误的密钥有时也会生成看似可打印的字符,但通常得分会低很多,因为其中的字母更少、空格更少,而且几乎没有常见英文单词。

如何得到题目要求的数值

当最高分文本 \(p(k^\star)\) 被确定之后,题目要求的答案就是

$$A=\sum_{i=0}^{n-1} p_i(k^\star).$$

搜索阶段负责找出正确文本,最后这一步只是把该文本转换成题目要求的整数。

代码如何工作

解析密文

C++、Python 和 Java 实现都会把输入当作原始文本逐字符扫描。连续的十进制数字会被收集成一个完整 token,转成整数后加入密文数组;逗号和换行只起分隔作用。

枚举密钥并执行解密

随后,程序遍历所有由小写字母组成的三元组。对每一个三元组,都用长度为 3 的循环 XOR 规则解密整段密文,并把结果构造成候选明文字符串。

评分、保留最优结果并求 ASCII 和

每个候选明文先经过 ASCII 范围过滤,再按上述英文评分函数计算分数。只要当前候选超过此前见过的最佳分数,程序就把它保存为新的最优文本。完整的 \(26^3\) 搜索结束后,再对这个最优文本的 ASCII 码求和。主搜索开始前,三个实现还都会用一个短样例验证 XOR 的往返可逆性。

复杂度分析

设密文字节数为 \(n\)。解析输入需要 \(O(n)\) 时间。穷举阶段会测试 \(26^3\) 个密钥,而每个密钥都要花 \(O(n)\) 时间完成解密与评分,因此主导时间复杂度是

$$O(26^3 n).$$

由于 \(26^3=17{,}576\) 是固定常数,所以总运行时间对消息长度而言是线性的,只是常数因子中等。评分里对四个固定短词的额外查找仍然是线性的,不会改变渐近数量级。

空间复杂度为 \(O(n)\)。程序需要保存密文、当前构造的一个候选明文,以及少量与最佳分数和密钥枚举相关的常数级辅助信息。

注释与参考资料

  1. 题目页面: https://projecteuler.net/problem=59
  2. XOR 密码: Wikipedia - 异或
  3. ASCII: Wikipedia - ASCII
  4. 频率分析: Wikipedia - 频率分析

Краткое содержание задачи

Шифртекст задан как список ASCII-кодов, разделенных запятыми. Он получен из английского текста с помощью XOR-шифрования повторяющимся ключом длины 3. Каждый символ ключа является строчной английской буквой, поэтому всего существует лишь \(26^3\) допустимых ключей. Нужно восстановить правильный открытый текст, а затем вычислить сумму его ASCII-значений.

Решающими являются три факта: XOR обратим сам к себе, ключ повторяется через каждые три позиции, а реализации не используют отдельный криптоаналитический трюк. Вместо этого они перебирают все допустимые ключи и сравнивают полученные расшифровки по явной функции оценки английского текста.

Математический подход

Обозначим шифртекст через \(e_0,e_1,\dots,e_{n-1}\). Для ключа-кандидата \(k=(k_0,k_1,k_2)\), где каждый \(k_r\in\{97,98,\dots,122\}\), расшифрованный байт в позиции \(i\) равен

$$p_i(k)=e_i \oplus k_{i \bmod 3}.$$

Тем самым вся задача превращается в конечную задачу оптимизации на множестве ключей \(K=\{97,\dots,122\}^3\).

XOR как самoобратимая операция

Главная инвариантная формула имеет вид

$$x \oplus y \oplus y = x.$$

Если байт открытого текста \(p_i\) был зашифрован как \(e_i=p_i \oplus k_{i \bmod 3}\), то повторное применение того же ключевого байта возвращает исходный символ:

$$e_i \oplus k_{i \bmod 3}=(p_i \oplus k_{i \bmod 3}) \oplus k_{i \bmod 3}=p_i.$$

Никакой рекуррентной формулы здесь не требуется. Как только ключ фиксирован, каждый символ определяется независимо, а период 3 точно указывает, какой байт ключа используется в каждом индексе.

Пространство ключей достаточно мало для полного перебора

Так как длина ключа равна 3, а каждый его символ должен быть строчной ASCII-буквой, имеем

$$|K|=26^3=17{,}576.$$

Это число достаточно мало для прямого полного перебора. Для каждого ключа расшифровка требует лишь одного XOR на каждую позицию шифртекста, поэтому весь поиск остается практически мгновенным. Именно поэтому решению не нужна более сложная схема с отдельным анализом классов по модулю 3.

Метод точен относительно своей функции оценки: реализации не угадывают лучший ключ, а проверяют все возможные и сохраняют расшифровку с максимальным баллом.

Явная функция оценки английского текста

Реализации сначала применяют жесткий фильтр допустимости, а затем взвешенную оценку английского текста. Если какой-либо расшифрованный символ выходит за пределы ASCII-диапазона от 9 до 126, кандидат получает очень большое отрицательное значение и фактически отбрасывается. Иначе оценка равна

$$S(p)=3N_{\text{space}}(p)+2N_{\text{letter}}(p)+40W_{\text{the}}(p)+25W_{\text{and}}(p)+20W_{\text{of}}(p)+20W_{\text{to}}(p).$$

Здесь \(N_{\text{space}}\) считает пробелы, \(N_{\text{letter}}\) считает заглавные и строчные английские буквы, а \(W_{\text{word}}\) считает вхождения указанного слова, записанного с пробелами по краям.

Следовательно, выбранный ключ определяется как

$$k^\star=\operatorname*{arg\,max}_{k\in K} S(p(k)).$$

Именно этот объект фактически реализован в коде. Три позиции ключа не восстанавливаются по отдельности; всегда оценивается полностью расшифрованный текст.

Разобранный пример: короткий XOR-цикл туда и обратно

В реализациях есть небольшой контрольный пример с открытым текстом “hello world” и ключом “abc”. Если записать байты ключа как \(97,98,99\), то первые зашифрованные значения равны

$$104\oplus97=9,\qquad 101\oplus98=7,\qquad 108\oplus99=15,$$

а дальнейшее повторение того же периода 3 дает префикс шифртекста \([9,7,15,13,13,67,\dots]\). При расшифровке тем же повторяющимся ключом процесс обращается:

$$9\oplus97=104,\qquad 7\oplus98=101,\qquad 15\oplus99=108.$$

Исходный текст восстанавливается точно. Неверный ключ иногда тоже порождает печатаемые символы, но почти всегда получает намного меньший балл, потому что содержит меньше букв, меньше пробелов и почти не содержит распространенных английских слов.

Получение требуемого ответа

Когда найден текст \(p(k^\star)\) с максимальной оценкой, искомая величина задачи равна просто

$$A=\sum_{i=0}^{n-1} p_i(k^\star).$$

Поиск определяет правильную расшифровку, а заключительная сумма переводит ее в требуемое целое число.

Как работает код

Разбор шифртекста

Реализации на C++, Python и Java читают вход как сырой текст и проходят по нему посимвольно. Последовательные десятичные цифры объединяются в один токен, преобразуются в целое число и добавляются в массив шифртекста. Запятые и переводы строк служат только разделителями.

Перебор ключей и расшифровка

Затем программа перебирает все тройки строчных букв. Для каждой тройки она расшифровывает весь шифртекст по периодическому XOR-правилу длины 3 и строит строку-кандидат открытого текста.

Оценка, выбор лучшего текста и сумма ASCII

Каждый кандидат сначала проверяется фильтром ASCII-диапазона, а потом оценивается взвешенной английской функцией. Если текущий кандидат превосходит лучший результат, найденный ранее, он сохраняется как новый лидер. После завершения полного перебора \(26^3\) ключей реализации суммируют ASCII-коды лучшего текста. Перед основным поиском они также проверяют обратимость XOR на коротком известном примере.

Анализ сложности

Пусть \(n\) обозначает число байтов в шифртексте. Разбор входа занимает \(O(n)\). Полный перебор рассматривает \(26^3\) ключей, и для каждого требуется \(O(n)\) работы на расшифровку и оценку, так что доминирующая сложность равна

$$O(26^3 n).$$

Поскольку \(26^3=17{,}576\) является фиксированной константой, время работы линейно по длине сообщения с умеренным постоянным множителем. Дополнительные проходы для подсчета четырех фиксированных коротких слов не меняют асимптотической оценки.

Память равна \(O(n)\): хранятся шифртекст, один текущий вариант открытого текста и постоянный объем служебной информации для лучшего балла и перебора ключей.

Примечания и ссылки

  1. Страница задачи: https://projecteuler.net/problem=59
  2. XOR-шифр: Wikipedia - XOR cipher
  3. ASCII: Wikipedia - ASCII
  4. Частотный анализ: Wikipedia - Частотный анализ

ملخص المشكلة

النص المشفر معطى على هيئة قائمة من أعداد ASCII مفصولة بفواصل. وقد نتج من نص إنجليزي عادي بعد تطبيق XOR بمفتاح دوري طوله 3. كل حرف في المفتاح هو حرف إنجليزي صغير، ولذلك فإن عدد المفاتيح الممكنة لا يتجاوز \(26^3\). المطلوب هو استعادة النص الصحيح ثم حساب مجموع قيم ASCII الخاصة به.

هناك ثلاث حقائق تحكم الحل بالكامل: عملية XOR هي معكوس نفسها، والمفتاح يتكرر كل ثلاث خانات، والتنفيذات لا تستخدم اختصارًا تحليليًا منفصلًا. بدلًا من ذلك فهي تجرب كل مفتاح مسموح وتفاضل بين النصوص الناتجة عبر دالة صريحة تقيس مدى شبهها بالنص الإنجليزي.

المنهج الرياضي

لنرمز إلى النص المشفر بـ \(e_0,e_1,\dots,e_{n-1}\). وإذا أخذنا مفتاحًا مرشحًا \(k=(k_0,k_1,k_2)\) بحيث يكون كل \(k_r\in\{97,98,\dots,122\}\)، فإن البايت المفكوك في الموضع \(i\) يساوي

$$p_i(k)=e_i \oplus k_{i \bmod 3}.$$

وبذلك تتحول المسألة كلها إلى مسألة تحسين منتهية على مجموعة المفاتيح \(K=\{97,\dots,122\}^3\).

XOR كعملية ذاتية العكس

الثابت الأساسي هو

$$x \oplus y \oplus y = x.$$

فإذا كان بايت من النص العادي \(p_i\) قد شُفِّر وفق العلاقة \(e_i=p_i \oplus k_{i \bmod 3}\)، فإن تطبيق بايت المفتاح نفسه مرة أخرى يعيد الرمز الأصلي:

$$e_i \oplus k_{i \bmod 3}=(p_i \oplus k_{i \bmod 3}) \oplus k_{i \bmod 3}=p_i.$$

لا توجد هنا علاقة عودية نحتاج إلى حلها. ما إن يثبت المفتاح حتى تصبح كل خانة محددة بصورة مستقلة، ويخبرنا الدور 3 مباشرة بأي بايت من المفتاح نستخدمه عند كل موضع.

حيز المفاتيح صغير بما يكفي للاستنفاد الكامل

بما أن طول المفتاح هو 3 وكل حرف فيه يجب أن يكون حرف ASCII صغيرًا، فإن

$$|K|=26^3=17{,}576.$$

وهذا عدد صغير بما يكفي لإجراء بحث شامل مباشر. فك التشفير لكل مفتاح لا يتطلب إلا عملية XOR واحدة لكل موضع من النص المشفر، ولذلك يبقى البحث كله عمليًا جدًا. ولهذا لا تحتاج الحلول إلى بناء أكثر تعقيدًا يعتمد على تحليل كل فئة باقية على حدة.

الطريقة دقيقة بالنسبة إلى معيارها في التقييم: فهي لا تخمن أفضل مفتاح، بل تمر على جميع المفاتيح الممكنة ثم تختار النص الأعلى درجة.

تحويل شبه النص الإنجليزي إلى دالة تقييم

تستخدم التنفيذات أولًا مرشح صلاحية صارمًا، ثم درجة موزونة للنص الإنجليزي. فإذا ظهر أي محرف مفكوك خارج مجال ASCII من 9 إلى 126، يُمنح المرشح قيمة سالبة كبيرة جدًا فيُستبعد عمليًا. وإذا كان النص صالحًا، فإن الدرجة تُعطى بالعلاقة

$$S(p)=3N_{\text{space}}(p)+2N_{\text{letter}}(p)+40W_{\text{the}}(p)+25W_{\text{and}}(p)+20W_{\text{of}}(p)+20W_{\text{to}}(p).$$

حيث يعد \(N_{\text{space}}\) عدد الفراغات، ويعد \(N_{\text{letter}}\) عدد الحروف الإنجليزية الصغيرة والكبيرة، ويعد \(W_{\text{word}}\) عدد مرات ظهور الكلمة المحددة عندما تكون محاطة بفراغات.

ومن ثم يكون المفتاح المختار هو

$$k^\star=\operatorname*{arg\,max}_{k\in K} S(p(k)).$$

وهذا هو الكائن الرياضي الحقيقي الذي تنفذه الشيفرة. فهي لا تستخرج أحرف المفتاح الثلاثة كلًا على حدة، بل تقيم النص المفكوك كاملًا في كل مرة.

مثال محلول: دورة XOR قصيرة ذهابًا وإيابًا

تحتوي التنفيذات على اختبار صغير يستخدم النص “hello world” والمفتاح “abc”. وإذا كتبنا بايتات المفتاح على صورة \(97,98,99\)، فإن أول القيم المشفرة تكون

$$104\oplus97=9,\qquad 101\oplus98=7,\qquad 108\oplus99=15,$$

ومع متابعة النمط الدوري نفسه بطول 3 نحصل على بادئة النص المشفر \([9,7,15,13,13,67,\dots]\). وعند فك التشفير بالمفتاح الدوري نفسه تنعكس العملية تمامًا:

$$9\oplus97=104,\qquad 7\oplus98=101,\qquad 15\oplus99=108.$$

وهكذا نستعيد النص الأصلي بدقة. أما المفتاح الخاطئ فقد ينتج أحيانًا محارف قابلة للطباعة، لكنه غالبًا ما يحصل على درجة أقل بكثير لأنه يحتوي على حروف أقل وفراغات أقل وعدد أقل من الكلمات الإنجليزية الشائعة.

استخراج القيمة المطلوبة في النهاية

بعد تحديد النص الأفضل \(p(k^\star)\)، تصبح الإجابة المطلوبة في المسألة هي فقط

$$A=\sum_{i=0}^{n-1} p_i(k^\star).$$

مرحلة البحث تحدد النص الصحيح، ثم تحول الخطوة الأخيرة هذا النص إلى العدد الصحيح الذي يطلبه السؤال.

كيف تعمل الشيفرة

تحليل النص المشفر

تقرأ تنفيذات C++ وPython وJava المدخل على أنه نص خام ثم تمر عليه محرفًا محرفًا. تُجمع الأرقام العشرية المتتالية في رمز واحد، ثم تُحوَّل إلى عدد صحيح وتُضاف إلى مصفوفة النص المشفر. أما الفواصل ونهايات الأسطر فليست إلا فواصل بين القيم.

تعداد المفاتيح وفك التشفير

بعد ذلك تمر الشيفرة على جميع الثلاثيات الممكنة من الحروف الصغيرة. ولكل ثلاثية تفك الرسالة كلها باستخدام قاعدة XOR الدورية ذات الطول 3، ثم تبني النص المفكوك المرشح على هيئة سلسلة محارف.

التقييم والاختيار وجمع ASCII

يمر كل نص مرشح أولًا عبر مرشح مجال ASCII ثم يحصل على درجته الموزونة الخاصة بالنص الإنجليزي. وكلما تجاوز مرشح ما أفضل درجة شوهدت حتى تلك اللحظة، يُحفظ على أنه النص الأفضل الحالي. وبعد اكتمال البحث على جميع المفاتيح \(26^3\)، تُجمع قيم ASCII الخاصة بالنص الفائز. وقبل بدء البحث الرئيسي، تتحقق التنفيذات أيضًا من خاصية الذهاب والإياب لعملية XOR على مثال قصير معروف.

تحليل التعقيد

إذا كان \(n\) هو عدد بايتات النص المشفر، فإن تحليل الإدخال يحتاج إلى \(O(n)\). والبحث الشامل يجرب \(26^3\) مفتاحًا، وكل مفتاح يحتاج إلى \(O(n)\) عملًا لفك التشفير والتقييم، ولذلك يكون الزمن الغالب هو

$$O(26^3 n).$$

وبما أن \(26^3=17{,}576\) ثابتة، فإن زمن التنفيذ خطي في طول الرسالة مع ثابت معتدل. كما أن عمليات عد الكلمات الإضافية تتعلق بأربع كلمات قصيرة ثابتة، ولذلك لا تغير الرتبة التقاربية.

استهلاك الذاكرة هو \(O(n)\)، لأن التنفيذ يحتفظ بالنص المشفر، وبنص مرشح واحد في كل مرة، وبقدر ثابت من البيانات المساعدة الخاصة بأفضل درجة وتعداد المفاتيح.

حواشٍ ومراجع

  1. صفحة المسألة: https://projecteuler.net/problem=59
  2. تشفير XOR: Wikipedia - XOR cipher
  3. ASCII: Wikipedia - ASCII
  4. تحليل التردد: Wikipedia - تحليل التكرار