Hashing de senha secreta [fechado]

33

No espírito do Concurso C de Underhanded , estou iniciando um Concurso de Código de Underhanded. O objetivo deste concurso é implementar diretamente algum código, ocultando sutilmente um bug nefasto dentro dele.

O concurso

Você é uma toupeira secreta da Rússia que trabalha no departamento de TI de uma agência de espionagem americana. Seu chefe americano acabou de solicitar que você implemente um algoritmo de hash de senha para criptografar mensagens secretas.

Seu chefe deseja que você implemente a seguinte função:

f: String -> byte[16]

que converte uma senha em uma quantidade de 16 bytes adequada para uso como chave AES. Seu chefe deseja uma função segura, o que, nesse contexto, significa que diferentes cadeias de senha devem gerar resultados diferentes com uma probabilidade esmagadora. Por exemplo, retornar o hash md5 da entrada seria uma implementação simples de f.

Obviamente, seu verdadeiro chefe na agência de espionagem russa gostaria que você subvertesse esse processo. Sua tarefa é implementar de fforma que os russos possam descriptografar todas as mensagens secretas criptografadas usando as chaves retornadas por f.

Para fazer isso, você deve implementar fpara que ele retorne apenas um pequeno subconjunto das 2 ^ 128 saídas possíveis. Em particular, você fdeve retornar no máximo 2 ^ 16 resultados diferentes, para que os russos possam fazer uma pesquisa fácil de força bruta pela chave correta para cada mensagem criptografada que desejam descriptografar.

Lembre-se, no entanto, que a espionagem acarreta a pena de morte. Para não ser pego, sua função fdeve gerar pelo menos 2 ^ 8 resultados diferentes, de modo que fé improvável que uma inspeção superficial de algumas saídas revele uma duplicata. E o mais importante, o código que você introduz para limitar o intervalo fdeve parecer não intencional, não deliberado. Se você for levado a um tribunal, deve haver alguma dúvida razoável sobre se você introduziu o bug deliberadamente ou por acidente.

A julgar

Eu e mais duas pessoas recrutadas julgaremos as inscrições (envie-me um e-mail se desejar julgar). Estou oferecendo uma recompensa de 200 reputações pela entrada vencedora. As inscrições devem ser enviadas até 1º de maio.

O julgamento levará em consideração os seguintes critérios:

  • Será que faderem à especificação, ou seja, faz gerar entre 2 ^ 8 e 2 ^ 16 possíveis saídas. Não pense que esses são limites rígidos, mas deduziremos pontos se você estiver muito fora do alcance.
  • O erro é plausivelmente o resultado de um erro não intencional?
  • As saídas fparecem aleatórias?
  • Quanto menor a sua implementação f, melhor.
  • Quanto mais clara sua implementação f, melhor.

Notas

Você pode usar qualquer idioma para implementar seu código. Você está tentando ocultar um bug à vista, portanto, código ofuscado não é sugerido.

Você pode dar uma olhada em alguns dos vencedores anteriores do concurso Underhanded C para ter uma ideia do que é uma boa finalização.

As seqüências de entrada serão ascii imprimíveis (32 a 126, inclusive). Você pode assumir um comprimento máximo razoável, se quiser.

Keith Randall
fonte
1
existe alguma limitação na string de entrada? como se fosse apenas um alfabeto?
Ali1S232
@ Gajet: você deve manipular todos os caracteres ascii imprimíveis (32 a 126, inclusive).
21412 Keith Randall
O intervalo de saída é todo de 16 bytes ou apenas para impressão?
Boothby
@boothby: todos os valores possíveis de 16 bytes (2 ^ 128 possibilidades) #
Keith Randall #
1
Estou votando para encerrar esta questão como fora de tópico, porque os desafios secretos não estão mais no tópico neste site. meta.codegolf.stackexchange.com/a/8326/20469
cat

Respostas:

15

C

2 ^ 16 saídas possíveis (ou 2 ^ 8 vezes o número de caracteres usados).
Usa a implementação MD5 do Linux, que é AFAIK, tudo bem. Mas isso fornece o mesmo hash, por exemplo, para "40" e "42".
EDIT: renomeado bcopypara memcpy(parâmetros trocados, é claro).
EDIT: Convertido de programa em função, para melhor atender aos requisitos.

#include <string.h>
#include <openssl/md5.h>

void f(const unsigned char *input, unsigned char output[16]) {

    /* Put the input in a 32-byte buffer, padded with zeros. */
    unsigned char workbuf[32] = {0};
    strncpy(workbuf, input, sizeof(workbuf));

    unsigned char res[MD5_DIGEST_LENGTH];
    MD5(workbuf, sizeof(workbuf), res);

    /* NOTE: MD5 has known weaknesses, so using it isn't 100% secure.
     * To compensate, prefix the input buffer with its own MD5, and hash again. */
    memcpy(workbuf+1, workbuf, sizeof(workbuf)-1);
    workbuf[0] = res[0];
    MD5(workbuf, sizeof(workbuf), res);

    /* Copy the result to the output buffer */
    memcpy(output, res, 16);
}

/* Some operating systems don't have memcpy(), so include a simple implementation */
void *
memcpy(void *_dest, const void *_src, size_t n)
{
    const unsigned char *src = _src;
    unsigned char *dest = _dest;
    while (n--) *dest++ = *src++;
    return _dest;
}
Ugoren
fonte
isso é uma falha no MD5?
Ali1S232
@ Gajet, Não, usei o MD5 do Linux, o que é perfeitamente aceitável (AFAIK).
Ugoren
eles porque não gera mais saída possível?
Ali1S232
1
@ Gajet: Considere o que acontece na bcopyetapa ... é uma boa direção errada, uma vez que a bcopyfunção BSD real funcionaria corretamente aqui.
ha12
@han, na verdade, agora vejo que o meu bcopyé buggy. Vou mudar para memcpye, em seguida, a mesma implementação se tornará válida.
ugoren
13

C

Essa pode não ser a entrada mais chamativa do concurso, mas acho que o seguinte é o tipo de função hash que poderia ter sido feita por qualquer codificador inteligente demais para o seu próprio bem, com uma vaga idéia do tipo de operações que você vê nas funções hash:

#include <stdio.h>
#include <string.h>
#include <stdint.h>

void hash(const char* s, uint8_t* r, size_t n)
{
     uint32_t h = 123456789UL;
     for (size_t i = 0; i < n; i++) {
          for (const char* p = s; *p; p++) {
               h = h * 33 + *p;
          }
          *r++ = (h >> 3) & 0xff;
          h = h ^ 987654321UL;
     }
}

int main()
{
     size_t n = 1024;
     char s[n];
     size_t m = 16;
     uint8_t b[m];
     while (fgets(s, n, stdin)) {
          hash(s, b, m);
          for (size_t i = 0; i < m; ++i)
               printf("%02x", b[i]);
          printf("\n");
     }
}

De fato, a função hash pode retornar não mais que L * 2048 resultados diferentes, onde L é o número de diferentes comprimentos de string de entrada que podem ocorrer. Na prática, testei o código em 1,85 milhão de linhas de entrada exclusivas de páginas de manual e documentos html no meu laptop, e obtive apenas 85428 hashes únicos diferentes.

han
fonte
0

Scala:

// smaller values for more easy tests:
val len = 16
// make a 16 bytes fingerprint
def to16Bytes (l: BigInt, pos: Int=len) : List[Byte] = 
  if (pos == 1) List (l.toByte) else (l % 256L).toByte :: to16Bytes (l / 256L, pos-1)
/** if number isn't prime, take next */
def nextProbPrime (l: BigInt) : BigInt = 
  if (l.isProbablePrime (9)) l else nextProbPrime (l + 1)
/** Take every input, shift and add, but take primes */
def codify (s: String): BigInt = 
  (BigInt (17) /: s) ((a, b) => nextProbPrime (a * BigInt (257) + b))
/** very, very short Strings - less than 14 bytes - have to be filled, to obscure them a bit: */
def f (s: String) : Array [Byte] = {
  val filled = (if (s.size < 14) s + "secret" + s else s)
  to16Bytes (codify (filled + filled.reverse)).toArray.map (l => nextProbPrime (l).toByte) 
}

Teste, se o resultado não for semelhante para entradas semelhantes:

val samples = List ("a", "aa", "b", "", "longer example", "This is a foolish, fishy test") 

samples.map (f) 

 List[Array[Byte]] = List(
Array (-41, -113, -79, 127, 29, 127, 31, 67, -19, 83, -73, -31, -101, -113, 97, -113), 
Array (-19, 7, -43, 89, -97, -113, 47, -53, -113, -127, -31, -113, -67, -23, 127, 127), 
Array (-41, -113, -79, 127, 29, 127, 31, 67, -19, 83, -73, -31, -101, -113, 97, -113), 
Array (37, -19, -7, 67, -83, 89, 59, -11, -23, -47, 97, 83, 19, 2, 2, 2), 
Array (79, 101, -47, -103, 47, -13, 29, -37, -83, -3, -37, 59, 127, 97, -43, -43), 
Array (37, 53, -43, -73, -67, 5, 11, -89, -37, -103, 107, 97, 37, -71, 59, 67))

O erro está usando apenas primos para a codificação. Ao invés de

scala> math.pow (256, 16)
res5: Double = 3.4028236692093846E38

valores, terminamos com

scala> math.pow (54, 16)
res6: Double = 5.227573613485917E27

pois há 54 números primos abaixo de 256.

Usuário desconhecido
fonte
2
5.22e27 >> 2^16. Não há como brutar a força com tantas possibilidades.
Keith Randall
você esqueceu o nome do idioma
ajax333221
@ ajax333221: Scala. Eu adicionei ao topo.
usuário desconhecido
@KeithRandall: Eu poderia 'acidentalmente' usar apenas bytes positivos, o que reduziria as possibilidades para math.pow (27, 16), mas isso ainda é sobre 8e22.
usuário desconhecido