Praktikumsaufgaben und Lunhbox voidb@x group let unhbox voidb
Transcrição
Praktikumsaufgaben und Lunhbox voidb@x group let unhbox voidb
Informationssicherheit – Lösung der Praktikumsaufgabe 3 – Thema: Kryptografische Hashes 1. • Die Anzahl der möglichen Hashes beträgt 28 = 256, ist somit viel zu klein. • Es ist trivial, eine Datei so zu manipulieren, dass sie einen bestimmten Hash erzeugt. 2. Da wir die Implementierung von MD5 nicht kennengelernt haben und die OpenSSLBibliothek, die eine entsprechende Implementierung enthält, ebenfalls noch nicht behandelt wurde, ist es günstig, das Kommandozeilenprogramm md5sum zu nutzen, das den MD5-Hash der als Parameter übergebenen Datei ermittelt. Das Programm muss dann im einzelnen die folgenden Dinge tun: • 2 Klartext-Dateien generieren, beide dürfen sich nur in einem Bit unterscheiden • 2 Hashes bilden ({md5sum <dateiname>) Ergebnis wieder in Datei schreiben • Hashes aus Datei einlesen • Hash 1 XOR Hash 2 = Bits, die sich geändert haben • Zählen der gesetzten Bits im Ergebnis der XOR-Operation • Anzahl geänderter Bits akkumulieren und schließlich durch Anzahl Iterationen dividieren Um eine statistisch relevante Aussage zu treffen, sollte der Ablauf viele Male wiederholt werden. Problematisch ist die Tatsache, dass unter C maximal 64bittige Datentypen existieren (long long), der Hash aber eine Länge von 128 Bit aufweist. Es bietet sich an, jeweils 2 long long- Variablen für die Hashes und das XOR-Ergebnis zu nutzen. Listing 1 zeigt eine mögliche Lösung. Listing 1: Ein Programm zur statistischen Prüfung von MD5 #include #include #include #include <stdio.h> <stdlib.h> <unistd.h> <math.h> #define N 50000 #define DATFILE "hashes.dat" #define FILE1 "file1.in" #define FILE2 "file2.in" c Robert Baumgartl, 2009-16 –1– 18. Mai 2016 Informationssicherheit #define CMD1 "md5sum < "FILE1" | cut -f1 -d ’ ’> "DATFILE #define CMD2 "md5sum < "FILE2" | cut -f1 -d ’ ’>> "DATFILE int main(void) { FILE *f1, *f2, *f3; unsigned long long llhi1, lllo1, llhi2, lllo2, xor1, xor2; int bits_changed, sum_bits_changed, iterations; char buf[17]; double chbits_per_iteration = 0.0; /* statistics init */ sum_bits_changed = 0; /* generate test files which differ in exactly one bit */ if ((f1 = fopen(FILE1, "w")) == NULL) { perror("fopen, f1"); exit(EXIT_FAILURE); } if ((f2 = fopen(FILE2, "w")) == NULL) { perror("fopen, f2"); exit(EXIT_FAILURE); } fputc(’b’, f1); fputc(’c’, f2); fclose(f1); fclose(f2); /* Master Loop */ for (iterations=0; iterations<N; iterations++) { /* call md5sum (write result into DATFILE) */ system(CMD1); system(CMD2); /* analyze DATFILE */ if ((f3 = fopen(DATFILE, "r")) == NULL) { perror("fopen, datfile"); exit(EXIT_FAILURE); } /* Hash of file1 */ buf[16] = 0; fread(buf, 16, 1, f3); llhi1 = strtoull(buf, NULL, 16); fread(buf, 16, 1, f3); lllo1 = strtoull(buf, NULL, 16); printf("Hash of file1: %016llx:%016llx\n", llhi1, lllo1); getc(f3); /* read \n from DATFILE */ c Robert Baumgartl, 2009-16 –2– 18. Mai 2016 Informationssicherheit /* Hash of file2 */ fread(buf, 16, 1, f3); llhi2 = strtoull(buf, NULL, 16); fread(buf, 16, 1, f3); lllo2 = strtoull(buf, NULL, 16); printf("Hash of file2: %016Lx:%016Lx\n", llhi2, lllo2); fclose(f3); xor1 = llhi1 ˆ llhi2; xor2 = lllo1 ˆ lllo2; /* count set bits in xor1 and xor2 */ bits_changed = 0; while (xor1 != 0) { if (xor1 & 0x01) { bits_changed++; } xor1 >>= 1; } while (xor2 != 0) { if (xor2 & 0x01) { bits_changed++; } xor2 >>= 1; } printf("Bits changed: %d\n", bits_changed); /* update statistics */ sum_bits_changed += bits_changed; /* generate two new files which differ in exactly one bit by appending the same char to both files */ if ((f1 = fopen(FILE1, "a")) == NULL) { perror("fopen, f1"); exit(EXIT_FAILURE); } if ((f2 = fopen(FILE2, "a")) == NULL) { perror("fopen, f2"); exit(EXIT_FAILURE); } fputc(’a’, f1); fputc(’a’, f2); fclose(f1); fclose(f2); } /* Master Loop */ /* Results */ chbits_per_iteration = (double) sum_bits_changed / (double) N; printf("On average, %.2f bits have changed for %i test cases.", chbits_per_iteration, N); printf(" That are %.2f%%.\n", chbits_per_iteration/1.28); /* clean up */ unlink(DATFILE); c Robert Baumgartl, 2009-16 –3– 18. Mai 2016 Informationssicherheit unlink(FILE1); unlink(FILE2); return(EXIT_SUCCESS); } Ein Testlauf mit 10.000 Iterationen ergibt: [...] Hash of file1: 7973ca41d7c467b8:0b392a931fe416ea Hash of file2: 90326469ed4d00fc:ed1ac9f34a733181 Bits changed: 61 Hash of file1: 2bb394f3f4533007:0b1ea152b1da3437 Hash of file2: 449f7741f16f2b2d:c6004cb5862d8765 Bits changed: 72 On average, 64.02 bits have changed for 10000 test cases. That are 50.01%. 3.∗ a) Das in Listing 2 detaillierte Programm misst die Zeit, die die Kommandozeilenprogramme für die Verarbeitung einer 4 GiB großen Datei benötigen und errechnet daraus den Durchsatz. Listing 2: Performancevergleich verschiedener Hashverfahren /* hashperf.c if test file size > 2 GiB is req’d, compile with gcc -D_FILE_OFFSET_BITS=64 -o hashperf hashperf.c -lm */ #include #include #include #include #include <stdio.h> <stdlib.h> <unistd.h> <string.h> <math.h> #define N 1000 /* use a single test file of size 4 GiB */ #define TESTFILE "test.dat" unsigned long long testfilesize = 0x01LL<<32; /* compare five different hash programs */ #define NUMHASHES 5 char *hashes[NUMHASHES] = {"sha1sum", "sha224sum", "sha512sum", "md5sum", "cksum"}; int hashlength[NUMHASHES] = {160, 224, 512, 128, 32}; #define LOGFILE "hashperf.txt" int main(void) { FILE *fout, *fin; unsigned long long filepos; int c, ret; c Robert Baumgartl, 2009-16 –4– 18. Mai 2016 Informationssicherheit char buf[128]; double dur; /* generate file, if it doesn’t exist already */ if ((fout=fopen(TESTFILE, "r")) == NULL) { if ((fout = fopen(TESTFILE, "w")) == NULL) { printf("Could not fopen() %s\n", TESTFILE); perror("fopen"); exit(EXIT_FAILURE); } printf("Size of test file: %llu\n", testfilesize); for (filepos=0; filepos<testfilesize; filepos++) { ret = putc(random(), fout); if (ret == EOF) { perror("putc"); } } fclose(fout); } unlink(LOGFILE); /* measure timing of generating the hash over TESTfile for all hash programs */ for (c=0; c<NUMHASHES; c++) { sprintf(buf, "/usr/bin/time -f%%e -o %s -a %s %s >/dev/null", LOGFILE, hashes[c], TESTFILE); printf("executing %s\n", buf, LOGFILE); system(buf); } /* analyze timing */ if ((fin=fopen(LOGFILE, "r")) == NULL) { perror("fopen() LOGFILE.\n"); exit(EXIT_FAILURE); } printf("Throughput of\n"); for (c=0; c<NUMHASHES; c++) { ret = fscanf(fin, "%lf", &dur); if (ret != 1) { printf("fscanf failed in %ith entry of %s\n", c, LOGFILE); exit(EXIT_FAILURE); } printf("dur = %lf\n", dur); fgets(buf, 2, fin); /* read away ’ \n’ */ printf("%s: %0.2lf MiB/s.\n", hashes[c], (double) testfilesize/dur/1024.0/1024.0); } fclose(fin); /* do not delete the TESTFILE on purpose, because it is hard to generate */ return(EXIT_SUCCESS); } c Robert Baumgartl, 2009-16 –5– 18. Mai 2016 Informationssicherheit Auf einem mehrere Jahre alten Notebook mit Intel Core 2 Duo und einer Taktfrequenz von 2.53 GHz wurden damit die folgenden Ergebnisse ermittelt: MiB · s−1 sha1sum 43.05 sha224sum 45.36 sha512sum 17.49 md5sum 39.54 cksum 47.86 Tabelle 1: Durchsätze für 5 verschiedene Hashverfahren b) Das Programm in Listing 3 ermittelt die Dauer der vollständigen Enumeration des Schlüsselraums für die betreffenden Hashverfahrens unter der Annahme, dass in einer Sekunde eine Milliarde Hashes generiert werden in Sekunden sowie in einem geeigneten größeren Zeitmaßstab. Das Akronym ‘BBP’ steht für die Lebensdauer des Universums (Big Bang Periods ≈ 13.7 Mrd. Jahre; ca. 4.32 · 1017 s). Listing 3: Dauer der Enumeration des Schlüsselraums von Hashverfahren #include <stdio.h> #include <stdlib.h> #include <math.h> /* One billion hashes per second */ #define HASHES_PER_SEC 1.0E9 /* We compare NUMHASHES different hash programs */ #define NUMHASHES 5 char *hashes[NUMHASHES] = {"sha1sum", "sha224sum", "sha512sum", "md5sum", "cksum"}; int hashlength[NUMHASHES] = {160, 224, 512, 128, 32}; struct uentry { char *name; double secsperunit; }; /* We have NUMUNITS units of time for illustration */ #define NUMUNITS 5 struct uentry units[NUMUNITS] = { { "BBP", 13.7*1.0E9*365.0*86400.0 }, /* Big Bang Periods (13.7 Billion Years) */ { "Ma", 1.0E6*86400.0*365.0 }, /* MegaYears (1 Million Years) */ { "a", 86400.0*365.0 }, /* 1 Year */ { "m", 86400.0*30.0 }, /* 1 Month */ { "d", 86400.0 } /* 1 Day */ }; int main(void) { int c, d; double seconds, result; for (c=0; c<NUMHASHES; c++) { printf("%s: ", hashes[c]); c Robert Baumgartl, 2009-16 –6– 18. Mai 2016 Informationssicherheit seconds = pow(2.0, (double) hashlength[c]) / HASHES_PER_SEC; printf("%0.2lE s ", seconds); for (d=0; d<5; d++){ result = seconds / units[d].secsperunit; if (result > 1.0){ printf("= %0.2lE %s\n", result, units[d].name); break; } } if (result < 1.0) { /* no dividing factor has been found */ printf("\n"); } } return(EXIT_SUCCESS); } Eine Ausgabe des Programms (gleiche Plattform wie bei a)): robge@ipaetz2:˜/txt/job/htw/is/src/prak03/hashperf$ ./bruteforce sha1sum: 1.46E+39 s = 3.38E+21 BBP sha224sum: 2.70E+58 s = 6.24E+40 BBP sha512sum: 1.34E+145 s = 3.10E+127 BBP md5sum: 3.40E+29 s = 7.88E+11 BBP cksum: 4.29E+00 s 4.∗ Die angegebene Lösung wurde durch Herrn Marco Ziebell entwickelt, dem an dieser Stelle recht herzlich gedankt sei. Listing 4: Implementierung von MD2 #include #include #include #include <stdlib.h> <stdio.h> <string.h> <inttypes.h> uint8_t* md2(const uint8_t const *message, unsigned int messagelength) ; static unsigned char S[256] = { 41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6, 19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188, 76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24, 138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251, 245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63, 148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50, 39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165, 181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210, 150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157, 112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27, 96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15, 85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197, 234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65, c Robert Baumgartl, 2009-16 –7– 18. Mai 2016 Informationssicherheit 129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123, 8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233, 203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228, 166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237, 31, 26, 219, 153, 141, 51, 159, 17, 131, 20 }; uint8_t* md2(const uint8_t const *message, unsigned int messagelength) { unsigned int paddinglength = 0; /* length of message + padding (to be computed later) */ unsigned int N = 0; /* buffer for (msg + padding + checksum) */ uint8_t *M = NULL; /* pointer to checksum within M */ uint8_t *C = NULL; /* buffer for hash computation */ static uint8_t X[48] = {0}; /* temporary variables */ size_t i, j, k; unsigned int c = 0, L = 0, t = 0; /* * Step 1 of the MD2 Algorithm * P A D D I N G */ /* padding is always between 1 and 16 bytes */ paddinglength = 16 - (messagelength % 16u); /* compute buffer length */ N = messagelength + paddinglength; /* alloc space for (message + padding + checksum) */ M = (uint8_t*) calloc(1, N + 16); if(M == NULL){ fputs("calloc failed\n",stderr); exit(EXIT_FAILURE); } C = (M + N); /* write buffer content */ memcpy(M, message, messagelength); memset(M+messagelength , (int) paddinglength, paddinglength); /* * Step 2 of 5 of the MD2 Algorithm * C H E C K S U M */ memset(C, 0, 16); L = 0; for(i = 0; i <= ((N / 16) - 1); ++i) { for(j = 0; j < 16; ++j) { c = M[i * 16 + j]; C[j] ˆ= S[c ˆ L]; L = C[j]; } } c Robert Baumgartl, 2009-16 –8– 18. Mai 2016 Informationssicherheit /* * Step 3 of 5 of the MD2 Algorithm * I N I T I A L I Z E */ memset(X, 0, 48); /* * Step 4 of 5 of the MD2 Algorithm * P R O C E S S M E S S A G E */ for(i = 0u; i <= ( ( (N + 16) / 16) - 1); ++i) { for( j = 0; j < 16; ++j){ X[16+j] = M[i * 16 + j]; X[32+j] = (X[16+j] ˆ X[j]); } t = 0; for(j = 0; j < 18; ++j) { for(k = 0; k < 48; ++k) { t = X[k] ˆ= S[t]; } t = (t+j) & 0xff; } } if(M != NULL){ free(M); } return X; } int main(void) { int i = 0 ; int j = 0; /* some reference results */ char *buf[] = { "" , "abc", "abcdefghijklmnopqrstuvwxyz" }; char *sol[] = { "8350e5a3e24c153df2275c9f80692773", "da853b0d3f88d99b30283a69e6ded6bb", "4e8ddff3650292ab5a4108c3aa47940b" }; uint8_t *md = NULL; for(j = 0; j < 3; ++j){ printf("Eingabe: %s\n", buf[j]); md = md2((const uint8_t *) buf[j], (unsigned int) strlen(buf[j])); printf("LSG: %s\n", sol[j]); c Robert Baumgartl, 2009-16 –9– 18. Mai 2016 Informationssicherheit fputs("OUT: ", stdout); for(i = 0; i < 16; ++i) { printf("%02x",md[i]); } printf("\n-------------------------------------\n\n"); } return EXIT_SUCCESS; } 5. Das Programm ermittelt den SHA1-Hash der als Kommandozeilenparameter übergebenen Datei und gibt diesen als Hexadezimalzahl nach stdout aus. Die Implementierung erfolgt mittels der OpenSSL-Bibliothek, die wir auch in der Belegaufgabe einsetzen werden. Um das Hashverfahren auszutauschen, muss man nur 4 Elemente verändern. Das Headerfile muss ausgetauscht werden, die Länge des Hashes ist eine andere und das Verfahren selbst muss im Aufruf von EVP DigestInit() ersetzt werden. Die notwendigen Änderungen enthält md5.diff. Er wird folgendermaßen angewandt, vorausgesetzt, die Quelldatei hash.c befindet sich im gleichen Verzeichnis: robge@ipaetz2:˜/tmp$ patch c Robert Baumgartl, 2009-16 < md5.diff – 10 – 18. Mai 2016