Brich die kaputte Chiffre

12

Ich habe einen einfachen Zufallsgenerator entworfen, der mit einer Multiplikations- und einer Modul-Methode zwei Zahlen chaotisch durchläuft. Dafür funktioniert es großartig.

Wenn ich es als Chiffregenerator verwenden würde, wäre es jedoch anfällig für einen bekannten Klartextangriff, da ein Angreifer den Startwert aus einer Reihe von Zufallszahlen auf recheneffiziente Weise zurückentwickeln kann.

Um zu beweisen, dass die Verschlüsselung fehlerhaft ist, suchen Sie ein zulässiges Startwertpaar, das 7 Nullen in einer Reihe im Bereich [0; 255] generiert und dabei so wenig Energie, CPU-Zeit usw. wie möglich verbraucht.

Hier ist der Zufallsgenerator in JavaScript geschrieben:

function seed(state1,state2){
    //Constants
    var mod1=4294967087
    var mul1=65539
    var mod2=4294965887
    var mul2=65537
    function random(limit){
        //Cycle each state variable 1 step
        state1=(state1*mul1)%mod1
        state2=(state2*mul2)%mod2
        //Return a random variable
        return (state1+state2)%limit
    }
    //Return the random function
    return random
}

//Initiate the random generator using 2 integer values,
//they must be in the ranges [1;4294967086] and [1;4294965886]
random=seed(31337,42)
//Write 7 random values in the range [0;255] to screen
for(a=0;a<7;a++){
    document.write(random(256)+"<br>")
}

Ich habe ein Tool zum Testen von Kandidatennummernpaaren erstellt, das hier zu finden ist .

Für die nächsten 3 Tage sind keine Spoiler erlaubt , eine Antwort darf nur eine Reihe von Zahlen enthalten und sollte natürlich eine andere sein als die von früheren Solvern. Danach werden Sie aufgefordert, Ihre Postleitzahl anzugeben und Ihre Vorgehensweise zu erläutern.

Bearbeiten, Quarantäne ist beendet: Die
Antworten sollten sowohl eindeutige Zahlen als auch Erklärungen und Codes enthalten, um die Lösungsmethode zu dokumentieren.

Die eleganteste Lösung gewinnt.

Fürs Protokoll:
Ein Programm zu schreiben, das schnell eine Lösung findet, ist elegant.
Es ist elegant, ein Programm zu entwickeln, das die Funktionen einer GPU effizient nutzt, um es noch schneller zu machen.
Die Arbeit an einem Stück "Museumsgeschirr" ist elegant.
Es ist sehr elegant, eine Lösungsmethode zu finden, die nur mit Stift und Papier verwendet werden kann.
Elegant ist es, Ihre Lösung auf lehrreiche und leicht verständliche Weise zu erklären.

Die Verwendung mehrerer oder sehr teurer Computer ist unelegant.

aaaaaaaaaaa
quelle
Sind Sie sicher, dass es eine Antwort darauf gibt?
Dogbert
Ja, es gibt ungefähr 256 davon. Und ich bin mir auch sicher, dass mit einem modernen PC und der richtigen Programmierung in wenigen Minuten eine Antwort gefunden werden kann.
aaaaaaaaaaa
Ich frage mich nur, warum GPUs elegant sind, aber nicht mehrere CPUs?
JB
Weil sie schwieriger zu programmieren sind als CPUs. Sie müssen sicherstellen, dass Sie tatsächlich die GPU verwenden. Dies ist kein Grund, warum die meisten Shader im Leerlauf sind, da ein anderes Subsystem Engpässe aufweist. Und natürlich müssen Sie immer noch einen effizienten Algorithmus implementieren, um die großen Punkte zu erzielen.
aaaaaaaaaaa
Wenn ich meinen Arbeitscode einreiche, ist es fast so, als hätte ich eine große Menge von Samenpaaren eingereicht. Spielende schon?
JB

Antworten:

6

C ++, 44014022/164607120

Es befindet sich in C ++, verwendet 1 GB Arbeitsspeicher und hat ca. 45 Sekunden gebraucht, um dieses erste Paar zu finden. Ich werde die Zeit aktualisieren, sobald sie alle gefunden haben.

Code unten. Es werden zuerst alle Paare gefunden, die 4 Nullen erzeugen, und diese werden dann durch einen einfachen Versuch heruntergemischt (siehe checkMethode). Es werden Paare gefunden, die 4 Nullen erzeugen, indem zwei große Arrays generiert werden, von denen eines die ersten 4 niederwertigen Bytes des Generators state1 und das zweite das Negative der ersten 4 niederwertigen Bytes des Generators state2 enthält. Diese Arrays werden dann sortiert und nach einer Übereinstimmung durchsucht, die dem gesamten Generator entspricht, der zum Starten 4 Nullen ausgibt.

Die Arrays sind zu groß, um im Speicher abgelegt zu werden, sodass die Arbeit stapelweise ausgeführt wird, um nur in den Speicher zu passen.

Es sieht so aus, als würde der gesamte Lauf ca. 12 Stunden dauern.

Bearbeiten : Der Code wurde verbessert, so dass es nur ca. 1 Stunde dauert, bis alle möglichen Samen gefunden sind. Jetzt werden die Tabellen in 256 verschiedene Dateien generiert, eine für jedes erste Byte der Ausgabe. Wir können dann jede Datei einzeln verarbeiten, damit wir keine Daten neu generieren müssen.

Bearbeiten : Es hat sich herausgestellt, dass Sie die 256 Untertabellen einzeln anstatt auf einmal generieren können, sodass keine Festplatte benötigt wird. Laufzeit bis zu ~ 15 Minuten mit 256 MB.

#include <stdio.h>
#include <stdint.h>
#include <algorithm>
using namespace std;

#define M1 65539
#define N1 4294967087
#define M2 65537
#define N2 4294965887
#define MATCHES 7

// M^-1 mod N                                                                                                                                                        
#define M1_INV 3027952124
#define M2_INV 1949206866

int npairs = 0;

void check(uint32_t seed1, uint32_t seed2) {
  uint32_t s1 = seed1;
  uint32_t s2 = seed2;
  for (int i = 0; i < MATCHES; i++) {
    s1 = (uint64_t)s1 * M1 % N1;
    s2 = (uint64_t)s2 * M2 % N2;
    if (((s1 + s2) & 0xff) != 0) return;
  }
  printf("%d %u %u\n", npairs++, seed1, seed2);
}

struct Record {
  uint32_t signature; // 2nd through 5th generated bytes                                                                                                             
  uint32_t seed;      // seed that generated them                                                                                                                    
};
// for sorting Records                                                                                                                                               
bool operator<(const Record &a, const Record &b) {
  return a.signature < b.signature;
}

int main(int argc, char *argv[]) {
  Record *table1 = (Record*)malloc((N1/256+1)*sizeof(*table1));
  Record *table2 = (Record*)malloc((N2/256+1)*sizeof(*table2));

  for (int i = 0; i < 256; i++) {  // iterate over first byte                                                                                                        
    printf("first byte %x\n", i);

    // generate signatures (bytes 2 through 5) for all states of generator 1                                                                                         
    // that generate i as the first byte.                                                                                                                            
    Record *r = table1;
    for (uint64_t k = i; k < N1; k += 256) {
      uint32_t sig = 0;
      uint32_t v = k;
      for (int j = 0; j < 4; j++) {
        v = (uint64_t)v * M1 % N1;
        sig = (sig << 8) + (v & 0xff);
      }
      r->signature = sig;
      r->seed = k;
      r++;
    }
    Record *endtable1 = r;

    // generate signatures (bytes 2 through 5) for all states of generator 2                                                                                         
    // that generate -i as the first byte.                                                                                                                           
    r = table2;
    for (uint64_t k = (-i & 0xff); k < N2; k += 256) {
      uint32_t sig = 0;
      uint32_t v = k;
      for (int j = 0; j < 4; j++) {
        v = (uint64_t)v * M2 % N2;
        sig = (sig << 8) + (-v & 0xff);
      }
      r->signature = sig;
      r->seed = k;
      r++;
    }
    Record *endtable2 = r;

    sort(table1, endtable1);
    sort(table2, endtable2);

    // iterate through looking for matches                                                                                                                           
    const Record *p1 = table1;
    const Record *p2 = table2;
    while (p1 < endtable1  && p2 < endtable2) {
      if (p1->signature < p2->signature) p1++;
      else if (p1->signature > p2->signature) p2++;
      else {
        check((uint64_t)p1->seed * M1_INV % N1, (uint64_t)p2->seed * M2_INV % N2);
        // NOTE: may skip some potential matches, if p1->signature==(p1+1)->signature or p2->signature==(p2+1)->signature                                            
        p1++;
      }
    }
  }
}
Keith Randall
quelle
Ich hätte nicht gedacht, dass eine Festplatte schnell genug ist, um für diese Aufgabe effizient zu sein. Interessant.
aaaaaaaaaaa