Hashing password sottovalutato [chiuso]


33

Nello spirito del Underhanded C Contest , sto avviando un Underhanded Code Contest. L'obiettivo di questo concorso è implementare in modo diretto un po 'di codice, nascondendo sottilmente un insetto nefasto al suo interno.

Il contesto

Sei una talpa russa segreta che lavora nel dipartimento IT di un'agenzia di spionaggio americana. Il tuo capo americano ti ha appena chiesto di implementare un algoritmo di hashing della password da utilizzare per crittografare i messaggi segreti.

Il tuo capo vuole che tu attui la seguente funzione:

f: String -> byte[16]

che converte una password in una quantità di 16 byte adatta all'uso come chiave AES. Il tuo capo vuole una funzione sicura, che in questo contesto significa che stringhe di password diverse dovrebbero generare risultati diversi con una probabilità schiacciante. Ad esempio, restituire l' hash md5 dell'input sarebbe una semplice implementazione di f.

Naturalmente, il tuo vero capo nell'agenzia di spionaggio russa vorrebbe che tu sovvertissi questo processo. Il tuo compito è implementare in modo ftale che i russi possano decifrare tutti i messaggi segreti crittografati usando le chiavi restituite da f.

Per fare ciò, è necessario implementare in fmodo che restituisca solo un piccolo sottoinsieme delle 2 ^ 128 possibili uscite. In particolare, fdevi restituire al massimo 2 ^ 16 risultati diversi in modo che i russi possano fare una semplice ricerca della forza bruta per la chiave corretta per ogni messaggio crittografato che desiderano decrittografare.

Tieni presente, tuttavia, che lo spionaggio comporta la pena di morte. Per non essere scoperto, la tua funzione fdeve generare almeno 2 ^ 8 risultati diversi, quindi fè improbabile che un'ispezione superficiale di alcune uscite di rivelare un duplicato. E, soprattutto, il codice che introduci per limitare l'intervallo fdeve apparire involontario, non intenzionale. Se vieni mai trascinato in un'aula di tribunale, ci deve essere qualche ragionevole dubbio sul fatto che tu abbia introdotto il bug deliberatamente o per caso.

A giudicare

Io e altri due che assumerò giudicherò le voci (inviami un'e-mail se vuoi giudicare). Offro un premio di 200 reputazione per l'iscrizione vincente. Le candidature devono essere caricate entro il 1 maggio.

Il giudizio terrà conto dei seguenti criteri:

  • fa f aderire alla specifica, cioè non genera tra 2 ^ 8 e 2 ^ 16 possibili uscite. Non pensare che si tratti di limiti rigidi, ma dedurremo dei punti se sei troppo fuori portata.
  • Il bug è plausibilmente il risultato di un errore involontario?
  • Le uscite di fsembrano casuali?
  • Più breve è la tua implementazione f, meglio è.
  • Più chiara è la tua implementazione f, meglio è.

Gli appunti

È possibile utilizzare qualsiasi lingua per implementare il codice. Stai cercando di nascondere un bug in bella vista, quindi il codice offuscato non è consigliato.

Potresti dare un'occhiata ad alcuni dei precedenti vincitori del concorso Underhanded C per avere un'idea di ciò che rende una buona presentazione.

Le stringhe di input saranno ascii stampabili (da 32 a 126, incluso). Se lo desideri, puoi assumere una lunghezza massima ragionevole.


1
ci sono delle limitazioni nella stringa di input? come se fosse composto solo da alfabeto?
Ali1S232

@Gajet: è necessario gestire tutti i caratteri ASCII stampabili (da 32 a 126 inclusi).
Keith Randall

L'intervallo di output è costituito da tutte le stringhe a 16 byte o solo da quelle stampabili?
stand

@boothby: tutti i possibili valori di 16 byte (2 ^ 128 possibilità)
Keith Randall

1
Sto votando per chiudere questa domanda come fuori tema perché le sfide subdole non sono più in argomento su questo sito. meta.codegolf.stackexchange.com/a/8326/20469
cat

Risposte:


15

C

2 ^ 16 possibili uscite (o 2 ^ 8 volte il numero di caratteri utilizzati).
Utilizza l'implementazione MD5 di Linux, che è AFAIK, bene. Ma questo dà lo stesso hash, ad esempio, per "40" e "42".
EDIT: rinominato bcopyin memcpy(parametri scambiati ovviamente).
EDIT: convertito da programma in funzione, per soddisfare meglio i requisiti.

#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;
}

è un difetto con MD5?
Ali1S232

@Gajet, No, ho usato MD5 di Linux, che è perfettamente OK (AFAIK).
ugoren,

loro perché non genera più output possibile?
Ali1S232

1
@Gajet: considera cosa succede nel bcopypassaggio ... è un bel po 'di errore di direzione, dato che la bcopyfunzione BSD effettiva funzionerebbe correttamente qui.
han

@han, in realtà, ora vedo che il mio bcopyè difettoso. Lo cambierò in memcpy, e quindi la stessa implementazione diventerà valida.
ugoren,

13

C

Questa potrebbe non essere la voce più appariscente del concorso, ma penso che il seguente sia il tipo di funzione hash che potrebbe essere stata eseguita da qualsiasi programmatore troppo intelligente per il proprio bene, con una vaga idea del tipo di operazioni che vedi nelle funzioni 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");
     }
}

In effetti la funzione hash non può restituire più di L * 2048 risultati diversi, dove L è il numero di diverse lunghezze di stringa di input che possono verificarsi. In pratica, ho testato il codice su 1,85 milioni di righe di input univoche da pagine di manuale e documenti html sul mio laptop e ho ottenuto solo 85428 hash univoci diversi.


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

Test, se il risultato non è simile per input simili:

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

L'errore sta usando solo numeri primi per la codifica. Invece di

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

valori, finiamo con

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

poiché ci sono 54 numeri primi inferiori a 256.


2
5.22e27 >> 2^16. Non c'è modo di forzare la forza che molte possibilità.
Keith Randall,

hai dimenticato il nome della lingua
ajax333221

@ ajax333221: Scala. L'ho aggiunto all'inizio.
utente sconosciuto

@KeithRandall: potrei 'accidentalmente' usare solo byte positivi, il che ridurrebbe le possibilità di math.pow (27, 16), ma è ancora circa 8e22.
utente sconosciuto
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.