Quebrar a cifra quebrada

12

Eu projetei um gerador aleatório simples que alterna dois números de maneira caótica, usando um método de multiplicação e módulo. Funciona muito bem para isso.

Se eu fosse usá-lo como um gerador de cifras, seria vulnerável a um ataque de texto simples conhecido, já que um invasor pode fazer engenharia reversa da semente a partir de uma série de números aleatórios de uma maneira computacionalmente eficiente.

Para provar que a cifra está quebrada, localize um par legal de valores iniciais que gerem 7 zeros em uma linha no intervalo [0; 255], usando o mínimo de energia, tempo de CPU etc. etc.

Aqui está o gerador aleatório escrito em JavaScript:

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>")
}

Eu criei uma ferramenta para testar pares de números candidatos, ela pode ser encontrada aqui .

Nos três dias seguintes, nenhum spoiler é permitido , uma resposta deve conter apenas um conjunto de números e, é claro, deve ser um conjunto diferente dos publicados pelos solucionadores anteriores. Posteriormente, você é encorajado a postar código e explicar sua abordagem.

Editar, a quarentena acabou: as
respostas devem conter um conjunto exclusivo de números, explicação e código para documentar o método de solução.

A solução mais elegante vence.

Para constar:
Escrever um programa que pode encontrar uma solução rapidamente é elegante.
Criar um programa que utilize os recursos de uma GPU de maneira eficiente para fazê-lo ainda mais rápido é elegante.
Fazer o trabalho em um pedaço de "material de museu" é elegante.
Encontrar um método de solução viável que possa ser utilizado usando apenas caneta e papel é muito elegante.
Explicar sua solução de maneira instrutiva e facilmente compreensível é elegante.

O uso de computadores múltiplos ou muito caros é deselegante.

aaaaaaaaaaaa
fonte
Tem certeza de que existe uma resposta para isso?
Wile E. Coyote
Sim, existem ~ 256 deles. E também tenho certeza de que é possível encontrar uma resposta em alguns minutos, considerando um PC moderno e a programação correta.
Aaaaaaaaaaaa
Apenas me pergunto, por que as GPUs são elegantes, mas não várias CPUs?
JB
Porque eles são mais difíceis de programar com eficiência do que CPUs. Você precisa se certificar de que está realmente utilizando a GPU, não há pontos em ter a maioria dos shaders ociosos, porque algum outro subsistema é gargalo. E é claro que você ainda precisa implementar um algoritmo eficiente para marcar os grandes pontos.
Aaaaaaaaaaaa
Se eu enviar meu código de trabalho, é quase como se eu enviasse um grande monte de pares de sementes. Fim do jogo já?
JB

Respostas:

6

C ++, 44014022/164607120

Ele está em C ++, usa 1 GB de memória e levou cerca de 45 segundos para encontrar esse primeiro par. Vou atualizar o horário assim que encontrar todos.

Código abaixo. Primeiro, ele encontra todos os pares que geram 4 zeros e depois os reduz por tentativa simples (consulte o checkmétodo). Ele encontra pares que geram 4 zeros, gerando duas matrizes grandes, uma que contém os primeiros 4 bytes de ordem inferior do gerador state1 e a segunda que contém o negativo dos 4 primeiros bytes de ordem inferior do gerador state2. Essas matrizes são então classificadas e pesquisadas por uma correspondência, que corresponde ao gerador geral que gera 4 zeros para iniciar.

As matrizes são grandes demais para serem armazenadas na memória; portanto, ele faz o trabalho em lotes dimensionados para caber na memória.

Parece que a execução completa levará aproximadamente 12 horas.

Edit : Melhorado o código, por isso leva apenas 1 hora para obter todas as sementes possíveis. Agora, ele gera as tabelas em 256 arquivos diferentes, um para cada primeiro byte de saída. Podemos então processar cada arquivo de forma independente, para que não tenhamos de regenerar dados.

Editar : Acontece que você pode gerar as 256 subtabelas individualmente, em vez de todas de uma vez, para que não seja necessário nenhum disco. Tempo de execução até ~ 15 minutos usando 256MB.

#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
fonte
Não achei que um disco rígido fosse rápido o suficiente para ser eficiente para a tarefa. Interessante.
Aaaaaaaaaaaa