Discussione Ufficiale Programmiamo con Inforge | Bruteforce in C

Una Discussione Ufficiale punta a raccogliere tutte le informazioni su un argomento o un fatto di attualità, con costanti aggiornamenti da parte del creatore e dei partecipanti.

Not an engineer

Moderatore
31 Ottobre 2011
2,715
100
1,204
1,091
Ultima modifica:
main.png


Programmiamo con Inforge | Bruteforce

Ad oggi, sul web, si trovano moltissime guide sui vari linguaggi di programmazione e sulle loro molteplici applicazioni. Tuttavia, chi si approccia a queste risorse, non sempre riesce a mettere in pratica ciò che ha appreso. Al fine di limitare queste mancanze, nasce Programmiamo con Inforge.

In questa rubrica potrai scrivere codice per la risoluzione di alcuni problemi legati alla programmazione, mettendo in pratica quanto stai apprendendo dalla teoria oppure mostrando le tue abilità e competenze nel campo dell’informatica.

Nelle precedenti 5 edizioni di questa rubrica vi abbiamo messo alla prova con 5 esercitazioni in ordine di difficoltà:




In questo sesto capitolo vi proponiamo un progetto da sviluppare passo per passo insieme allo staff di Inforge. Il progetto sarà diviso in features: volta per volta vi chiederemo di aggiungere una nuova funzionalità così da guidarvi verso lo sviluppo di un progetto potenzialmente complesso. Avrete tre giorni per implementare ogni feature per tempo. Ogni iterazione si svolgerà nel modo seguente:

  • giorno 1: pubblichiamo l'obbiettivo da sviluppare per la feature corrente che, entro 48h, potrete risolvere in completa autonomia;
  • giorno 3: pubblichiamo un suggerimento per aiutarvi a implementare la feature e, in altre 24h, potete provare a risolvere la feature per tempo.
In qualunque momento durante questi 3 giorni siete liberi di postare la soluzione e di porre domande di qualsiasi tipo, per esempio: "che argomenti mi consigliate di ripassare, utili a risolvere risolvere questa feature?". Non è una challenge, non c'è nessun aspetto competitivo, è programmiamo con Inforge. Ogni utente manterrà il proprio codice e proporrà la propria soluzione, lo staff vi guiderà nello sviluppo. Siete più che invitati a postare soluzioni parziali, spiegare il vostro ragionamento e porre domande. Il progetto è didattico ma, si spera, di vostro interesse. Di volta in volta, alla fine del terzo giorno, pubblicheremo (con eventuali spiegazioni) la soluzione della feature corrente e, contemporaneamente, l'obiettivo della feature successiva, anch'essa da svilupparsi in tre giorni. Il ciclo si ripeterà fino al termine del progetto.

La difficoltà del progetto si basa sul numero di partecipanti delle 5 esercitazioni proposte in precedenza. Chi non le ha risolte è invitato provarci e a pubblicare la propria soluzione nei threads elencati in precedenza; in questo modo valuteremo se vale la pena proseguire verso progetti ed esercitazioni più complesse.

Le esercitazioni che hanno avuto maggior successo sono state quelle di livello base, ed è da lì che partiremo. Per come strutturato il progetto, esso è alla portata di tutti: richiede presenza più che conoscenza e, di fatto, potresti essere in grado di partecipare anche se non conosci il C. I più bravi riusciranno a risolvere ogni features nel giro di pochi minuti e i meno bravi (con un po' di buona volontà!) hanno il tempo di studiare gli argomenti necessari a risolverla. Piuttosto che rimanere bloccati, siete invitati a chiedere aiuto e a discuterne: non dovete aspettare i suggerimenti (o le soluzioni) prima di fare domande. Man mano che aggiungeremo features, il progetto diventerà via via più complesso e non vogliamo lasciare indietro nessuno.

Ovviamente siete liberi di partecipare anche a progetto già iniziato o concluso ma, per sfruttare al meglio questa esperienza didattica, vi consigliamo di partecipare (attivamente) nel periodo indicato. Vedrete che ogni feature è ragionevolmente breve da poter essere completata per tempo. Anche da principianti, con l'aiuto allo staff, sarete più che incentivati a fare domande senza fermarvi ad aspettare il suggerimento pre-programmato.

Chi non è pratico di C e chi è maggiormente interessato ad altri linguaggi di programmazione è comunque invitato a partecipare allo sviluppo. Sentitevi liberi di porre domande specifiche per il linguaggio di programmazione che state usando e di postare la vostra soluzione. Ciò nonostante, questo progetto è stato pensato per essere sviluppato in C e il livello di difficoltà potrebbe aumentare (se lo sviluppate in Assembly) o diminuire (se lo sviluppate in Python).

Se vi è possibile, utilizzate una distribuzione GNU/Linux. Il codice che andremo a scrivere sarà per lo più multipiattaforma, ma siamo più propensi a fornirvi supporto se utilizzate lo stesso sistema operativo utilizzato dagli organizzatori del progetto. Se utilizzate altro, dovrete essere sufficientemente autonomi ad includere e compilare eventuali librerie esterne.


Partiamo dalle basi del C

In questa guida puoi trovare i testi per studiare e approfondire il C: I migliori libri per imparare e approfondire il C
In questa discussione puoi trovare le risposte alle domande più frequenti su come scrivere codice in C: Frequently asked questions: da dove si parte?


Progetto: Bruteforce



Prima di iniziare

Il progetto inizierà il 29 Maggio. Cogliamo l'occasione per iniziare a raccogliere feedback di qualunque tipo. In qualunque momento potete darci consigli organizzativi di qualsiasi genere (e.g., "tre giorni sono pochi", "meglio 24h in autonomia e 48h con suggerimento", "il progetto è troppo facile/difficile")

Ringraziamenti


Un ringraziamento particolare a @St3ve per aver contribuito attivamente alla formulazione di questo progetto ;) Speriamo vivamente che sia di vostro gradimento!
 
Ultima modifica:
Soluzione completa del progetto
Finita la feature 4, ci ritroviamo con un programma in grado di:
  • calcolare diverse (nella mia soluzione, oltre 50) funzioni di hash;
  • effettuare un attacco bruteforce in base ad un charset scelto dall'utente; e
  • dato un digest, stampare la funzione (o le funzioni) di hash che potrebbero averlo generato.
Vi ricordo, un attacco bruteforce effettua una ricerca esaustiva: per trovare la password associata ad un dato digest prova tutte le possibili combinazioni di lettere e simboli (di lunghezza sempre crescente) finché non trova la password corrispondente. Un attacco bruteforce non è un attacco al dizionario: non c'è alcun file contenente le keyword da provare, ogni stringa viene generata automaticamente dal programma; il programma non può fallire, prima o poi troverà la password che stiamo cercando. L'unico problema, è che l'attacco è molto lento perché tutte le stringhe lunghe da 1 ad n caratteri generate da un charset di lunghezza k sono
$$\[\sum_{i=1}^k \frac{n (n^k - 1)}{n - 1} \approx n^{k+1}$$\]Se questo valore vi dovesse risultare poco chiaro diciamo che, in prospettiva, se volessimo fare la stessa cosa con un attacco al dizionario, preso un charset composto da minuscole, maiuscole e numeri e fermandoci a calcolare tutte le possibili combinazioni di lunghezza 8, il nostro file peserebbe quasi 200 terabytes. Con il nostro attacco bruteforce non abbiamo bisogno di occupare tanto spazio in memoria: non c'è nessun file contenente le password da testare (le generiamo sul momento) e la memoria ram consumata dal nostro programma non va oltre qualche kilobytes. L'unico problema è che per testare così tante (potenzialmente infinite) password, ci vuole molto tempo. Il codice sorgente da me postato è tendenzialmente portable; fa uso della libreria OpenSSL che è presente in Windows, Linux, MacOS e BSD.

Codice sorgente



Modo d'uso:
Bash:
# trova la password composta da solo lettere minuscole il cui md5 è 9173039d5e505f44dfa151663ce5ee52
$ ./bruteforce md5 9173039d5e505f44dfa151663ce5ee52 abcdefghijklmnopqrstuvwxyz

# trova le funzioni di hash che potrebbero aver generato il digest 28a6d190bd2446633799bf5a2e58d57084c9b538ec498ff69ac0025586b5148a
$ ./whathash 28a6d190bd2446633799bf5a2e58d57084c9b538ec498ff69ac0025586b5148a

Non escludo che ci sia qualche bug o qualche altro difetto. In tal caso, siete libero di segnalarlo e possiamo discutere su come risolvere il problema.

Conclusione
In totale, la mia soluzione consiste in circa 120 righe contando eventuali spazi. Niente di particolarmente time-demanding programmarla da zero, ma il progetto è stato comunque diviso in step per renderlo comprensibile e fattibile (con l'aiuto dello staff) anche dai principianti. Di fatto, per farmi una vaga idea di come strutturare questa idea del progetto di gruppo, mi ero preparato la soluzione per ogni feature prima dell'inizio del progetto e non mi ha richiesto molto tempo. Idealmente, al termine della quarta feature avremmo voluto continuare ancora un po' il progetto inserendo features richieste dai membri del forum.Tuttavia, il progetto non ha riscosso grande successo quindi, a meno di un'improbabile impennata di interesse, non mi sembra il caso di dilungarlo oltre. Ci abbiamo provato.

A voi lettori non partecipanti, e a voi partecipanti che non avete proseguito oltre la prima feature, vi chiedo: Cosa è andato storto? Cosa avreste voluto vedere di diverso? Vi è sembrato troppo facile o troppo complesso? Troppo lungo o troppo corto? Preferireste utilizzare altri linguaggi? Non vi è piaciuta l'idea delle scadenze a tempo fisso? Preferireste più competizione e meno didattica? Aspettiamo i vostri feedbacks!
 
Ultima modifica da un moderatore:
Come sempre io lo faccio in C#
C#:
using System;
using System.Text;
using System.Security.Cryptography;

namespace Md5
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var md5=MD5.Create())
            {
                foreach(char carattere in args[0])
                {
                    var hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes($"{carattere}"));
                    var hash = BitConverter.ToString(hashBytes).Replace("-", string.Empty);
                    Console.WriteLine($"md5({carattere}) = {hash}");
                }
            }
        }
    }
}
 
Pubblicata la prima feature, inclusa di mini-challenge (da risolvere utilizzando il programma che scriverete).

Ricordate che è un progetto alla portata di tutti. Se rimanete bloccati da qualche parte, potete chiedere aiuto e, in ogni caso, abbiamo prefissato un suggerimento.
 
Bella challange! Pubblico la mia soluzione in C:

Il codice per MD5 l'ho semplicemente incollato da github.

C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

void md5(const uint8_t* initial_msg, size_t initial_len, uint8_t* digest);
void bin2hex(char* result, const unsigned char* old, const size_t oldlen);

void HashEachChar(const char* lpStr)
{
    uint8_t digest[16];
    char digestHex[33];
    int len = strlen(lpStr);
    for (int i = 0; i < len; i++)
    {
        const uint8_t* charPtr = (const uint8_t*)&lpStr[i];
        md5(charPtr, 1, digest);
        bin2hex(digestHex, digest, sizeof(digest));
        printf("md5(%c) = %s\n", lpStr[i], digestHex);
    }
}

void BruteforceChars(const char* match)
{
    uint8_t buff[1];
    uint8_t digest[16];
    char digestHex[33];
    for (int i = 0; i < 256; i++)
    {
        buff[0] = (uint8_t)i;
        md5(buff, 1, digest);
        bin2hex(digestHex, digest, sizeof(digest));
        if (strcmp(digestHex, match) == 0)
        {
            printf("Char with md5(%s) is: %c", match, (char)i);
            break;
        }
    }
}

int main(int argc, char** argv)
{
    // Hash char args
    HashEachChar(argv[1]);
    // Bruteforce ASCII
    BruteforceChars("b15835f133ff2e27c7cb28117bfae8f4");
    return 0;
}

Output:
Codice:
./challange abcdef

md5(a) = 0cc175b9c0f1b6a831c399e269772661
md5(b) = 92eb5ffee6ae2fec3ad71c777531578f
md5(c) = 4a8a08f09d37b73795649038408b5f33
md5(d) = 8277e0910d750195b448797616e091ad
md5(e) = e1671797c52e15f763380b45e841ec32
md5(f) = 8fa14cdd754f91cc6554c9e71929cce7
Char with md5(b15835f133ff2e27c7cb28117bfae8f4) is: "
 
Ultima modifica:
Per chi non l'ha risolto, suggerimento:
Usando OpenSSL (dovete installare la libreria), l'md5 può essere calcolato in questo modo
C:
// https://stackoverflow.com/a/61333376
void md5(char *result, const char *data, int len) {
  EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
  const EVP_MD *md = EVP_md5();
  unsigned char md_value[EVP_MAX_MD_SIZE];
  unsigned int md_len, i;
  EVP_DigestInit_ex(mdctx, md, NULL);
  EVP_DigestUpdate(mdctx, data, len);
  EVP_DigestFinal_ex(mdctx, md_value, &md_len);
  EVP_MD_CTX_free(mdctx);
  for (i = 0; i < md_len; i++) {
    snprintf(&(result[i * 2]), 16 * 2, "%02x", md_value[i]);
  }
}
e, se usate linux, il codice si compila così gcc -lcrypto -o bruteforce bruteforce.c.

Buone le soluzioni postate da @ddos attack e @JunkCoder. Visto che siamo in tema suggerimenti, vi consiglio di aggiungere anche un pezzo di codice per gestire il caso in cui l'utente non inserisce nessun carattere. È il vostro programma, quindi sta a a voi decidere cosa sia meglio fare. Personalmente, vi consiglio di scegliere uno di questi due comportamenti:
  • stampa un messaggio di errore che spiega all'utente come va utilizzato il programma (e.g., "ti sei dimenticato di inserire il charset");
  • imposta un valore di default ragionevole (e.g., tutti i possibili bytes, solo le lettere e i numeri, solo lettere numeri e simboli ma niente spazi, etc...).

Domani pubblico la mia soluzione e si passa alla seconda feature. Intanto, come vi è sembrata la prima feature? Troppo facile/difficile, tre giorni sono troppi/pochi, c'erano troppe/poche cose da fare, etc... Visto che siamo in pochi possiamo tranquillamente adeguarci alle vostre necessità e al vostro livello di competenza. Se volete cose più difficili, ditelo... altrimenti si può continuare così per un altro po' ;)

Idem per i curiosi che sono rimasti in silenzio. Se volete solo guardare non c'è nessun problema, ma se c'è un motivo particolare per cui non avete postato la vostra soluzione, fatecelo sapere!
 
Soluzione:
Come detto nel suggerimento, io ho scelto di utilizzare OpenSSL.
C:
// gcc -lcrypto -o bruteforce bruteforce.c
#include <openssl/evp.h>
#include <stdio.h>
#include <string.h>

void md5(char *result, const char *data, int len);

int main(int argc, char *argv[]) {
  if (argc <= 1) {
    fprintf(stderr, "Usage: %s charset\n", argv[0]);
    return 1;
  }

  const char *const charset = argv[1];
  const int length = strlen(charset);

  char digest[33];  // 32 caratteri hex di MD5 + '\0'
  for (int i = 0; i < length; i++) {
    md5(digest, &charset[i], 1);
    printf("md5(%c) = %s\n", charset[i], digest);
  }

  return 0;
}

// https://stackoverflow.com/a/61333376
void md5(char *result, const char *data, int len) {
  EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
  const EVP_MD *md = EVP_md5();
  unsigned char md_value[EVP_MAX_MD_SIZE];
  unsigned int md_len, i;
  EVP_DigestInit_ex(mdctx, md, NULL);
  EVP_DigestUpdate(mdctx, data, len);
  EVP_DigestFinal_ex(mdctx, md_value, &md_len);
  EVP_MD_CTX_free(mdctx);
  for (i = 0; i < md_len; i++) {
    snprintf(&(result[i * 2]), 16 * 2, "%02x", md_value[i]);
  }
}

La mini-challange l'avete risolta a modo vostro, ma io mi aspettavo una soluzione più semplice: ./bruteforce abc...ABC...123...\"

Non c'era bisogno di trasformare il bruteforce in un dictionary attack, perché la challange era pensata per essere risolta con quel che abbiamo fatto finora: sapevamo che la password doveva essere di un solo carattere. Per il resto volevo semplicemente farvi notare che alcuni caratteri speciali, tra cui " (virgolette) e * (asterisco) devono essere preceduti da un escape (il simbolo \ di backslash) altrimenti la shell li interpreta a modo suo: " è l'inizio di una stringa e * (glob) viene usato per fare filename expansion (elenca i file nella nostra working directory).

Ho aggiunto l'obettivo per la seconda feature al post iniziale.
 
Per chi non l'ha risolto.
Nella prima feature abbiamo imparato a stampare l'md5 delle password di lunghezza 1. Per generare l'md5 di tutte le password di lunghezza 2 possiamo fare così:

C:
char string[3] = {0};       // 2 + '\0' per semplificare la printf
for (int b = 0; b < length; b++) {  // length è la lunghezza del charset (vedi soluzione precedente)
  string[1] = charset[b];
  for (int a = 0; a < length; a++) {
    string[0] = charset[a];
    md5(digest, string, 2);
    printf("md5(%s) = %s\n", string, digest);
  }
}

Ripeti anche per lunghezza 3 e lunghezza 4, poi combina le soluzioni. Scriverai un codice brutto, ma non ha importanza:
  1. meglio una soluzione pessima che lasciare il problema non risolto;
  2. stiamo facendo sviluppo incrementale e la feature attuale ci serve per affrontare più facilmente la successiva.

Se rimanete bloccati siete liberi di fare domande. Domani posto la soluzione e si passa alla feature 3.
 
Ultima modifica:
Posto anche la soluzione.
Tutto molto semplice, basta ripetere lo stesso codice della feature 1 (o del suggerimento della feature 2) e innestare un nuovo ciclo for di volta in volta. Non è una soluzione elegante, ma non ha importanza. La soluzione pulita arriva con la feature 3, la feature 2 era decisamente alla portata di tutti.

C:
// gcc -lcrypto -o bruteforce bruteforce.c
#include <openssl/evp.h>
#include <stdio.h>
#include <string.h>

void md5(char *result, const char *data, int len);

int main(int argc, char *argv[]) {
  if (argc <= 1) {
    fprintf(stderr, "Usage: %s charset\n", argv[0]);
    return 1;
  }

  const char *const charset = argv[1];
  const int length = strlen(charset);

  char string[5] = {0};
  char digest[32];

  // 1 carattere
  for (int a = 0; a < length; a++) {
    string[0] = charset[a];
    md5(digest, string, 1);
    printf("md5(%s) = %s\n", string, digest);
  }

  // 2 caratteri
  for (int b = 0; b < length; b++) {
    string[1] = charset[b];
    for (int a = 0; a < length; a++) {
      string[0] = charset[a];
      md5(digest, string, 2);
      printf("md5(%s) = %s\n", string, digest);
    }
  }

  // 3 caratteri
  for (int c = 0; c < length; c++) {
    string[2] = charset[c];
    for (int b = 0; b < length; b++) {
      string[1] = charset[b];
      for (int a = 0; a < length; a++) {
        string[0] = charset[a];
        md5(digest, string, 3);
        printf("md5(%s) = %s\n", string, digest);
      }
    }
  }

  // 4 caratteri
  for (int d = 0; d < length; d++) {
    string[3] = charset[d];
    for (int c = 0; c < length; c++) {
      string[2] = charset[c];
      for (int b = 0; b < length; b++) {
        string[1] = charset[b];
        for (int a = 0; a < length; a++) {
          string[0] = charset[a];
          md5(digest, string, 4);
          printf("md5(%s) = %s\n", string, digest);
        }
      }
    }
  }

  return 0;
}

// https://stackoverflow.com/questions/7627723/how-to-create-a-md5-hash-of-a-string-in-c
void md5(char *result, const char *data, int len) {
  EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
  const EVP_MD *md = EVP_md5();
  unsigned char md_value[EVP_MAX_MD_SIZE];
  unsigned int md_len, i;
  EVP_DigestInit_ex(mdctx, md, NULL);
  EVP_DigestUpdate(mdctx, data, len);
  EVP_DigestFinal_ex(mdctx, md_value, &md_len);
  EVP_MD_CTX_free(mdctx);
  for (i = 0; i < md_len; i++) {
    snprintf(&(result[i * 2]), 16 * 2, "%02x", md_value[i]);
  }
}

Aspetto a postare la soluzione della mini-challange. È molto facile da risolvere, quindi potete provarci anche semplicemente compilando ed eseguendo la soluzione che ho appena postato: dovete aggiungere il codice per controllare se l'hash è uguale a quello a cui siete interessati (lo rifaremo, in modo migliore, in una feature successiva), ma la versione più semplice è una modifica banale che i due partecipanti avevano già implementato nella feature 1.

Aggiungo l'obiettivo per la feature 3 nell'open post. Altri tre giorni e poi si passa alla successiva.
 
Suggerimento
Ogni ciclo for conta da 0 fino a length (escluso), dove length è la lunghezza del charset. Al posto di mantenere una variabile per ogni contatore possiamo utilizzare un array lungo N, dove N è la più lunga stringa che andremo a generare. Se length fosse 10 questo codice preso dalla soluzione precedente
C:
for (int d = 0; d < length; d++) {
  string[3] = charset[d];
  for (int c = 0; c < length; c++) {
    string[2] = charset[c];
    for (int b = 0; b < length; b++) {
      string[1] = charset[b];
      for (int a = 0; a < length; a++) {
        string[0] = charset[a];
        md5(digest, string, 4); // il 4 conta il numero di cicli innestati, è da parametrizzare
        printf("md5(%s) = %s\n", string, digest);
      }
    }
  }
}
non fa altro che che contare da 0000 a 9999 e
  1. utilizzare la cifra delle unità (i.e., prima posizione) per indicizzare charset da assegnare a string[0];
  2. utilizzare la cifra nelle decine (i.e., seconda posizione) per indicizzare charset da assegnare a string[1];
  3. utilizzare la cifra nelle decine (i.e., terza posizione) per indicizzare charset da assegnare a string[2];
  4. utilizzare la cifra nelle decine (i.e., quarta posizione) per indicizzare charset da assegnare a string[3].
Se length è un numero generico, stiamo contando da 000...000 (N zeri) al più grande numero di N cifre espresso in base length. Una volta che parametrizziamo anche il numero di cifre (4 nel codice postato), abbiamo quasi finito. L'ultima cosa che ci manca è aggiornare il contenuto della stringa: dobbiamo generalizzare lo string[_] = ...; presente nella soluzione precedente. Il modo più banale per farlo è quello di rigenerare l'intera stringa di volta in volta... ma, più efficientemente (se siamo capaci), possiamo ricalcolare solo la parte modificata.

Non è semplicissimo. È il primo e probabilmente sarà l'ultimo punto un po' difficile di questo progetto didattico. Il suggerimento aiuta, ma non lo rende banale come le features precedenti. Ciò nonostante, la soluzione sarà molto corta e sullo stesso stile delle soluzioni precedenti: le scadenze sono brevi (ogni 3 giorni) perché il codice che dovete scrivere non vi chiede più di 15-30 minuti di tempo, 1h se dovete pure studiare gli argomenti necessari a risolverlo.

Se qualcuno ci sta provando ed è rimasto bloccato, possiamo discuterne. Altre 24h e posto la soluzione e si passa alla feature 4.
 
Ultima modifica:
Soluzione
Il mio N si chiama MSGLEN e l'ho fissato a 1024, che mi sembra un numero ragionevolente grande. Al posto della string, ho utilizzato una combinazione di code con message: code è l'array che funge da contatore modulo lunghezza del charset e message è la sequenza di bytes che verrà hashata.

Per contare modulo lunghezza del charset ho scritto la funzione ricorsiva next, che non fa altro che una somma con riporto: quando il valore nella casella attuale raggiunge n (i.e., la lunghezza del charset), azzeralo e incrementa ricorsivamente la casella successiva. Ogni casella dell'array, vi ricordo, rappresenta una cifra valore di ogni cifra del numero modulo lunghezza del charset. Il numero è espresso da sinistra (meno significativa) a destra (più significativa), al contrario di come facciamo con carta e penna (e.g., al posto di scrivere mille come 1000, scrivo 0001 dove 1 è in code[3]).
C:
int next(int *code, int n, int length) {
  if (++(*code) == n) {
    *code = 0;
    return next(code + 1, n, length - 1);
  }
  return length;
}
Il parametro length indica la lunghezza dell'array code e il valore restituito da next è il complemento della quantità di celle modificate. Per esempio, con n=10 e length=4 e code={9981} (aka., milleottocentonovantanove), una chiamata a next genera code={0091} (aka., millenovecento) e restituisce 1 perché ho modificato 3 cifre, length=4 e 4-3=1. Quando la funzione restituisce zero, ho generato il più grande numero composto da length cifre in base n.

Per aggiornare la stringa da hashare, ho utilizzato la funzione refresh che dovrebbe essere abbastanza autoesplicativa: uso l'array code per indicizzare l'array charset, poi assegno il carattere/simbolo alla corrispondente componente di message.
C:
void refresh(char *message, const int *code, const char *charset, int length) {
  for (int i = 0; i < length; i++) message[i] = charset[code[i]];
}

Questo procedimento viene ripetuto finché next non restituisce zero. Inoltre, il valore restituito da next mi aiuta a capire quanti caratteri devo refreshare; ovvero, il parametro length da passare a refresh: MSGLEN - next(...) + 1. Se mi salvo il massimo tra questi valori, ho anche la lunghezza effettiva (il valore da passare a md5) di message.
C:
int invrem, length = 0;
while (invrem = next(code, symbols, MSGLEN)) {
  if (MSGLEN - invrem + 1 > length) length = MSGLEN - invrem + 1;
  refresh(message, code, charset, MSGLEN - invrem + 1);
  md5(digest, message, length);
  printf("md5(%s) = %s\n", message, digest);
}

Riguardo alle cifre (array code), dobbiamo avere l'accortezza di partire da -1 perché 000..000 è una sequenza valida.
C:
int code[MSGLEN];
memset(code, -1, sizeof(int) * MSGLEN);

E questo è tutto. Il codice completo lo posterò al termine della quarta e ultima feature, ma con quel che vi ho scritto finora potete quasi limitarvi a fare ctrl+c ctrl+v nel posto giusto. Come al solito, se c'è qualcosa di poco chiaro siete invitati a fare domande.

La quarta e ultima feature è un po' più corposa, ma non è niente di difficile. La aggiungo in questo momento all'open post.
Come al solito, l'appuntamento è tra due giorni per il suggerimento e tra tre giorni per la soluzione.
 
Suggerimento
Ci sono molte cose diverse da fare, ma non c'è molto da dire: se siete arrivati alla feature 3 riuscirete a fare anche la 4. Quando aggiungete una (o più) nuova funzione di hash, è meglio se mantenete uno scheletro simile. Io avevo scritto
C:
void md5(char *result, const char *data, int len);
quindi il mio scheletro sarà qualcosa del tipo
C:
void nome_funzione(char *result, const char *data, int len);
Nei linguaggi di programmazione orientati agli oggetti diremmo che vogliamo un interfaccia comune per tutte le funzioni di hash. Il modo più semplice per evitare l'if-else è utilizzare un enum e uno switch-case, ma a dire il vero ci sono tante cose che possiamo fare. Ancora di più se abbiamo usato un linguaggio diverso dal C, dove magari abbiamo first-class functions e altre astrazioni che ci semplificano la vita.

Sappiamo che il digest inserito dall'utente sarà qualcosa di questo tipo
55639ca7dc70276e94f70dbc5e89adde2f215d30606d632e72b022eae51e15d0. Ogni coppia di valori rappresenta un byte espresso in esadecimale. Noi vogliamo convertire quella stringa in un array di byte grande la metà. I valori, invece di andare da 0 a F (esadecimale), andranno da 0 a 255.

Per quanto riguarda l'utility di supporto (inserisci un digest e io ti dico il nome della funzione) vi basta controllare la lunghezza della stringa: se la stringa inserita dall'utente è lunga N caratteri esadecimali, la funzione di hash dovrà fare un mapping verso stringhe di bytes lunghe N/2. Vogliamo spiegare all'utente cosa deve inserire nel vostro programma principale, quindi inutile stampare MD5 (in maiuscolo) se il vostro programma principale vuole che il nome della funzione sia md5 (in minuscolo); inutile stampare md4 se il vostro programma non lo supporta.

Domani posto la soluzione della feature 4, che sarà anche la soluzione completa di tutto il progetto.
 
Ultima modifica da un moderatore:
Per trovare la password associata a b15835f133ff2e27c7cb28117bfae8f4 io faccio:
C#:
using System;
using System.Text;
using System.IO;
using System.Security.Cryptography;

namespace Md5
{
    class Program
    {
        static string GeneraMd5(string parola)
        {
            var md5 = MD5.Create();
            var hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(parola));
            var hash = BitConverter.ToString(hashBytes).Replace("-", string.Empty);
            return hash.ToString();
        }
        static void Main(string[] args)
        {
            string hash = "b15835f133ff2e27c7cb28117bfae8f4", line=""; //hash da trovare
            using (var md5 = MD5.Create())
            {
                using (var stream=new StreamReader("rockyou.txt"))
                {
                    while((line=stream.ReadLine()) != null)
                    {
                        string hashtemporaneo = GeneraMd5(line);
                        if (hashtemporaneo==hash)
                        {
                            Console.WriteLine($"Ho trovato l'hash è: {hashtemporaneo}");
                            Environment.Exit(0);
                        }
                    }
                }
            }
        }
    }
}
Messaggio unito automaticamente:

La password è "
 
  • Mi piace
Reazioni: Max Fridman