Mot de passe haché saccadé [fermé]

33

Dans l'esprit du concours Underhanded C , je lance un concours de code Underhanded. L’objectif de ce concours est d’implémenter directement du code, tout en y dissimulant de manière subtile un bogue infâme.

La compétition

Vous êtes une taupe russe secrète qui travaille au service informatique d'une agence d'espionnage américaine. Votre patron américain vient de vous demander de mettre en œuvre un algorithme de hachage de mot de passe à utiliser pour chiffrer les messages secrets.

Votre patron souhaite que vous implémentiez la fonction suivante:

f: String -> byte[16]

qui convertit un mot de passe en une quantité de 16 octets pouvant être utilisée comme clé AES. Votre patron souhaite une fonction sécurisée, ce qui signifie dans ce contexte que différentes chaînes de mots de passe doivent générer des résultats différents avec une probabilité écrasante. Par exemple, renvoyer le hachage md5 de l'entrée serait une simple implémentation de f.

Bien sûr, votre vrai patron de l'agence d'espionnage russe voudrait que vous subvertissiez ce processus. Votre tâche consiste à mettre en place fdes systèmes permettant aux Russes de déchiffrer tous les messages secrets chiffrés à l'aide des clés renvoyées par f.

Pour ce faire, vous devez implémenter fafin de ne renvoyer qu'un petit sous-ensemble des 2 ^ 128 sorties possibles. En particulier, vous fdevez renvoyer au plus 2 ^ 16 résultats différents afin que les Russes puissent effectuer une recherche simple et brutale de la clé correcte pour chaque message chiffré qu'ils souhaitent déchiffrer.

Gardez toutefois à l'esprit que l'espionnage est passible de la peine de mort. Pour ne pas être pris au piège, votre fonction fdoit générer au moins 2 ^ 8 résultats différents, de sorte qu'une inspection superficielle de quelques sorties de fne donnera probablement pas un doublon. Et plus important encore, le code que vous introduisez pour limiter la portée fdoit paraître non intentionnel, et non délibéré. Si jamais vous êtes amené dans une salle d'audience, il doit exister un doute raisonnable quant à savoir si vous avez introduit le bogue délibérément ou par accident.

Juger

Les recrues et moi-même jugerons les entrées (envoyez-moi un courrier électronique si vous souhaitez juger). J'offre une prime de 200 points de réputation pour l'inscription gagnante. Les soumissions doivent être téléchargées avant le 1er mai.

Le jugement tiendra compte des critères suivants:

  • Est -ce que fAdhérez à la spécification, à savoir qu'elle produit de entre 2 ^ 8 et 2 ^ 16 sorties possibles. Ne croyez pas que ce sont des limites strictes, mais nous déduirons des points si vous êtes trop loin des marges.
  • Le bogue est-il plausiblement le résultat d'une erreur involontaire?
  • Les sorties de flook sont-elles aléatoires?
  • Plus votre mise en œuvre est courte f, mieux c'est.
  • Plus votre mise en œuvre sera claire f, mieux ce sera.

Remarques

Vous pouvez utiliser n'importe quel langage pour implémenter votre code. Vous essayez de cacher un bug à la vue de tous, donc le code obfusqué n'est pas suggéré.

Vous voudrez peut-être jeter un coup d'œil à certains des gagnants précédents du concours Underhanded C pour avoir une idée de ce qui fait une bonne soumission.

Les chaînes en entrée seront des caractères ASCII imprimables (32 à 126 inclus). Vous pouvez supposer une longueur maximale raisonnable si vous le souhaitez.

Keith Randall
la source
1
Y at-il des limitations sur la chaîne d'entrée? comme s'il ne s'agissait que d'alphabet?
Ali1S232
@ Gajet: vous devez gérer tous les caractères ascii imprimables (32 à 126 inclus).
Keith Randall
La plage de sortie correspond-elle à des chaînes de 16 octets ou à des chaînes imprimables?
Boothby
@boothby: toutes les valeurs possibles de 16 octets (2 ^ 128 possibilités)
Keith Randall
1
Je vote pour clore cette question hors sujet, car les défis sournois ne sont plus au sujet de ce site. meta.codegolf.stackexchange.com/a/8326/20469
cat.

Réponses:

15

C

2 ^ 16 sorties possibles (ou 2 ^ 8 fois le nombre de caractères utilisés).
Utilise l'implémentation MD5 de Linux, qui est, autant que je sache, bien. Mais cela donne le même hash, par exemple, pour "40" et "42".
EDIT: renommé bcopyen memcpy(paramètres échangés bien sûr).
EDIT: Converti de programme en fonction, pour mieux répondre aux exigences.

#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
la source
est-ce une faille avec MD5?
Ali1S232
@ Gajet, non, j'ai utilisé le MD5 de Linux, ce qui est parfaitement OK (autant que je sache).
Ugoren
pourquoi ils ne génèrent pas plus de sortie possible?
Ali1S232
1
@ Gajet: Pensez à ce qui se passe dans l' bcopyétape ... c'est une belle erreur de direction, car la bcopyfonction BSD réelle fonctionnerait correctement ici.
han
@han, En fait, je vois maintenant que c'est mon bcopybuggy. Je vais le changer en memcpy, et ensuite la même implémentation deviendra valide.
Ugoren
13

C

Ce n'est peut-être pas la participation la plus spectaculaire du concours, mais je pense que ce qui suit est le type de fonction de hachage qui aurait pu être effectué par un codeur trop intelligent, pour son propre bien, avec une vague idée du type d'opérations que vous voyez dans les fonctions de hachage:

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

En fait, la fonction de hachage ne peut pas renvoyer plus de L * 2048 résultats différents, L étant le nombre de longueurs de chaîne d'entrée différentes pouvant se produire. En pratique, j'ai testé le code sur 1,85 million de lignes de saisie uniques à partir de pages de manuel et de documents html sur mon ordinateur portable et je n'ai eu que 85428 hachages uniques différents.

han
la source
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) 
}

Testez, si le résultat ne semble pas similaire pour une entrée similaire:

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'erreur utilise seulement des nombres premiers pour l'encodage. Au lieu de

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

valeurs, nous finissons avec

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

puisqu'il y a 54 nombres premiers en dessous de 256.

Utilisateur inconnu
la source
2
5.22e27 >> 2^16. Il n'y a aucun moyen de recourir à la force brutale avec autant de possibilités.
Keith Randall
vous avez oublié le nom de la langue
ajax333221
@ ajax333221: Scala. Je l'ai ajouté en haut.
utilisateur inconnu
@ KeithRandall: Je ne pouvais "accidentellement" utiliser que des octets positifs, ce qui réduirait les possibilités de math.pow (27, 16), mais il reste environ 8e22.
utilisateur inconnu