]> git.za3k.com Git - cryptopals.git/commitdiff
Solve Set 1, Problem 6 (with one hint)
authorZachary Vance <za3k@za3k.com>
Thu, 25 May 2017 07:43:10 +0000 (00:43 -0700)
committerZachary Vance <za3k@za3k.com>
Thu, 25 May 2017 07:43:10 +0000 (00:43 -0700)
17 files changed:
Makefile
english.c
hamming.c [new file with mode: 0644]
hamming.h [new file with mode: 0644]
io.c
io.h
problems/set-1/4.html
problems/set-1/6.html
problems/set-1/7.html
problems/set-1/8.html
set1p4.test.c
set1p6.test.c [new file with mode: 0644]
util.c
util.h
xor.c
xor.h
xor_decrypt.c

index 356a2a364ed5ee665b334eb163d0c008ee9d2af4..e302ddb80da35e6d67bf594dd70f2bab6f0f4b85 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -14,12 +14,15 @@ set1p4.test: set1p4.test.c io.o xor_decrypt.o english.o util.o xor.o
        $(CC) -o $@ $? $(CFLAGS)
 set1p5.test: set1p5.test.c io.o util.o xor.o
        $(CC) -o $@ $? $(CFLAGS)
-test: set1p1.test set1p2.test set1p3.test set1p4.test set1p5.test
+set1p6.test: set1p6.test.c io.o util.o xor.o xor_decrypt.o english.o hamming.o
+       $(CC) -o $@ $? $(CFLAGS)
+test: set1p1.test set1p2.test set1p3.test set1p4.test set1p5.test set1p6.test
        ./set1p1.test >/dev/null || echo "Set 1, Problem 1: Failed"
        ./set1p2.test >/dev/null || echo "Set 1, Problem 2: Failed"
        ./set1p3.test >/dev/null || echo "Set 1, Problem 3: Failed"
        ./set1p4.test >/dev/null || echo "Set 1, Problem 4: Failed"
        ./set1p5.test >/dev/null || echo "Set 1, Problem 5: Failed"
+       ./set1p6.test >/dev/null || echo "Set 1, Problem 6: Failed"
 clean:
        rm -f *.o a.out *.test
 %.o: %.c
index 24273cd5d73988b4ebeb811d56608fe07acd377e..950d8821351d68cf89773c99b04206f4fd712627 100644 (file)
--- a/english.c
+++ b/english.c
@@ -6,7 +6,7 @@ float score_english_character(char c) {
       case 'e': case 't': case 'a': case 'o': case 'i': case 'n': return 3; break;
       case 'S': case 'H': case 'R': case 'D': case 'L': case 'U': return 2; break;
       case 's': case 'h': case 'r': case 'd': case 'l': case 'u': return 2; break;
-      case 0: return 0; break;
+      case '\000': return 0; break;
       default: break;
     }
     if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", c) != 0) return 1;
@@ -19,7 +19,7 @@ float score_english_buf(char* possible_plaintext, int length) {
     for (i=0;i<length;i++) {
       score = score + score_english_character(possible_plaintext[i]);
     }
-    return score;
+    return score / length;
 }
 float score_english_str(char* possible_plaintext) {
     return score_english_buf(possible_plaintext, strlen(possible_plaintext));
diff --git a/hamming.c b/hamming.c
new file mode 100644 (file)
index 0000000..2cca92d
--- /dev/null
+++ b/hamming.c
@@ -0,0 +1,26 @@
+#include <string.h>
+#include <stdio.h>
+
+int bits_set(unsigned char b) {
+  int distance=0;
+  while (b != 0) {
+    if (b%2) { b--; distance++; }
+    b=b/2;
+  }
+  return distance;
+}
+
+int hamming_distance_buf(char* buf1, char* buf2, int length) {
+  int distance = 0;
+  int i;
+  for(i=0;i<length;i++) distance += bits_set(buf1[i] ^ buf2[i]);
+  return distance;
+}
+
+int hamming_distance(char* str1, char* str2) {
+  if (strlen(str1) != strlen(str2)) {
+    printf("hamming_distance: strings should be the same length\n");
+    return -1;
+  }
+  return hamming_distance_buf(str1, str2, strlen(str1));
+}
diff --git a/hamming.h b/hamming.h
new file mode 100644 (file)
index 0000000..5cd3387
--- /dev/null
+++ b/hamming.h
@@ -0,0 +1,2 @@
+int hamming_distance_buf(char* buf1, char* buf2, int length);
+int hamming_distance(char* str1, char* str2);
diff --git a/io.c b/io.c
index dbc0874b8d90ed1782732e665f1cdf991f2162a2..8b59d2dd469b61d2ccf1e86db327784040c31ce1 100644 (file)
--- a/io.c
+++ b/io.c
@@ -131,15 +131,38 @@ int decode_base64(char* src, char* dest, int* destlen) {
 }
 
 char* printable_buffer(char* buffer, int length) {
-  char* output = malloc(length*3+2);
-  encode_hex(buffer, length, output);
-  memcpy(output + length * 2, ": ", 2);
-  memcpy(output + length * 2 + 2, buffer, length);
+  char* hex = malloc(length*2+1);
+  char* output = malloc(length*3+7);
+  encode_hex(buffer, length, hex);
+  sprintf(output, "%s: >|%.*s|<", hex, length, buffer);
+  free(hex);
   return output;
 }
 
 void print_buffer(char* buffer, int length) {
   char* output = printable_buffer(buffer, length);
-  printf("%s\n", output);
+  printf("%*s\n", length, output);
   free(output);
 }
+
+int read_base64_file(char* path, char** ciphertext, int *ciphertext_length) {
+  char* ciphertext_base64=NULL;
+  int ciphertext_base64_length=0;
+  FILE *f = fopen(path, "r");
+  int line_length=0;
+  int line_buf_length;
+  char* line=NULL;
+  while ((line_length = getline(&line, &line_buf_length, f))>0) {
+    if (line[line_length-1] == '\000') line_length--;
+    if (line[line_length-1] == '\n') line_length--;
+    ciphertext_base64 = realloc(ciphertext_base64, ciphertext_base64_length+line_length+1);
+    memcpy(ciphertext_base64+ciphertext_base64_length, line, line_length);
+    ciphertext_base64_length += line_length;
+  }
+  free(line);
+  fclose(f);
+  ciphertext_base64[ciphertext_base64_length]=0;
+  int succ = decode_base64(ciphertext_base64, *ciphertext, ciphertext_length);
+  free(ciphertext_base64);
+  return succ;
+}
diff --git a/io.h b/io.h
index b93090724a0e59756686db173207accbb5449f38..8ec6a117c5484a5860f246e56e9def5219a9d34b 100644 (file)
--- a/io.h
+++ b/io.h
@@ -1,7 +1,10 @@
+#define PRINT_BUFFER(BUF) { printf("Buffer BUF : ");print_buffer(BUF,sizeof(BUF)); }
+
 int decode_hex(char* src, char* dest, int *destlen);
 int decode_base64(char* src, char* dest, int* destlen);
 void encode_base64(const char* src_bytes, int src_size, char* dest);
 void encode_hex(const char* src_bytes, int src_size, char* dest);
 char* printable_buffer(char* buffer, int length);
 void print_buffer(char* buffer, int length);
-#define PRINT_BUFFER(BUF) { printf("Buffer BUF : ");print_buffer(BUF,sizeof(BUF)); }
+
+int read_base64_file(char* path, char** ciphertext, int *ciphertext_length);
index 0676e2dad65d317dc8bad13718396800e90cef26..50f6cd0726c3f9f6cb26cb6263219c3b325b0d7e 100644 (file)
@@ -46,7 +46,7 @@
           <h3>Detect single-character XOR</h3>
           <p>
   One of the 60-character strings in
-  <a href='/static/challenge-data/4.txt'>this file</a>
+  <a href='4.txt'>this file</a>
   has been encrypted by single-character XOR.
 </p>
 <p>
@@ -72,4 +72,4 @@
       var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
     })();*/
   </script>
-</html>
\ No newline at end of file
+</html>
index f5afb6d792460b6bb8397406ed43d7faa141b408..78e71a472a95decc8ecf6e6cb01714bf373005ec 100644 (file)
@@ -63,7 +63,7 @@
   </div>
 </div>
 <p>
-  <a href='/static/challenge-data/6.txt'>There's a file here.</a>
+  <a href='6.txt'>There's a file here.</a>
   It's been base64'd after being encrypted with repeating-key XOR.
 </p>
 <p>
       var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
     })();*/
   </script>
-</html>
\ No newline at end of file
+</html>
index 39017cfd52d18b9fc5b584d7a99a90bd2207907d..40d69fbb930cf6a9ec04cea2524a8cbfccd60a44 100644 (file)
@@ -46,7 +46,7 @@
           <h3>AES in ECB mode</h3>
           <p>
   The Base64-encoded content
-  <a href='/static/challenge-data/7.txt'>in this file</a>
+  <a href='7.txt'>in this file</a>
   has been encrypted via AES-128 in ECB mode under the key
 </p>
 <pre>"YELLOW SUBMARINE".</pre>
@@ -88,4 +88,4 @@
       var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
     })();*/
   </script>
-</html>
\ No newline at end of file
+</html>
index 62144bfb1e5ea7faa9592e0027d33ae91bcf53b3..d2b4fdb6d8f41445c8c0d7fcb4396dcf456e43a8 100644 (file)
@@ -45,7 +45,7 @@
         <div class='col-md-10'>
           <h3>Detect AES in ECB mode</h3>
           <p>
-  <a href='/static/challenge-data/8.txt'>In this file</a>
+  <a href='8.txt'>In this file</a>
   are a bunch of hex-encoded ciphertexts.
 </p>
 <p>
@@ -76,4 +76,4 @@
       var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
     })();*/
   </script>
-</html>
\ No newline at end of file
+</html>
index d33f572d889493f631866d3d36e916dbaa7eef8a..e8cb72d4da94f5bc33cc6b71fde0aca4c4a54762 100644 (file)
@@ -44,6 +44,8 @@ int main() {
         break;
     }
   }
+  fclose(fd);
+  free(ciphertext_hex);
   printf("Reading from file complete. Read %d lines.\n", candidate_ciphertext_count);
   
   decryption[CIPHERTEXT_LENGTH] = '\0'; // Make it a string.
@@ -51,6 +53,7 @@ int main() {
   best_score = -1000;
   for(i=0;i<candidate_ciphertext_count;i++) {
     find_best_xor_candidate_buf(1, ciphertext_buffers[i], CIPHERTEXT_LENGTH, &xor_byte, decryption, &score);
+    free(ciphertext_buffers[i]);
     if (score>best_score) {
       best_score = score;
       memcpy(best_decryption, decryption, CIPHERTEXT_LENGTH+1);
diff --git a/set1p6.test.c b/set1p6.test.c
new file mode 100644 (file)
index 0000000..dc78da3
--- /dev/null
@@ -0,0 +1,182 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "english.h"
+#include "hamming.h"
+#include "io.h"
+#include "util.h"
+#include "xor.h"
+#include "xor_decrypt.h"
+#define MIN_KEYSIZE 2
+#define MAX_KEYSIZE 40
+  
+float score_repeating_xor_keysize(char* ciphertext, int ciphertext_size, int keysize) {
+  //  The KEYSIZE with the smallest normalized edit distance is probably
+  //  the key. You could proceed perhaps with the smallest 2-3 KEYSIZE
+  //  values. Or take 4 KEYSIZE blocks instead of 2 and average the
+  //  distances.
+  if (ciphertext_size < keysize*4) {
+    printf("in score_repeating_xor_keysize: attempted key size too large (%d vs %d)\n", keysize, ciphertext_size);
+    return -1;
+  }
+  float d01 = hamming_distance_buf(ciphertext+0*keysize, ciphertext+1*keysize, keysize) / keysize;
+  float d12 = hamming_distance_buf(ciphertext+1*keysize, ciphertext+2*keysize, keysize) / keysize;
+  float d23 = hamming_distance_buf(ciphertext+2*keysize, ciphertext+3*keysize, keysize) / keysize;
+  float d02 = hamming_distance_buf(ciphertext+0*keysize, ciphertext+2*keysize, keysize) / keysize;
+  float d13 = hamming_distance_buf(ciphertext+1*keysize, ciphertext+3*keysize, keysize) / keysize;
+  float d03 = hamming_distance_buf(ciphertext+0*keysize, ciphertext+3*keysize, keysize) / keysize;
+  return (d01+d12+d23+d02+d13+d03)/3;
+}
+
+void find_repeating_xor_keysize(char* ciphertext, int ciphertext_length,
+                                int min_keysize, int max_keysize,
+                                int *keysize, float *keysize_score) {
+  int best_keysize;
+  int keysize_guess;
+  float best_keysize_score=1000;
+  float keysize_guess_score;
+  for(keysize_guess=MIN_KEYSIZE; keysize_guess<=MAX_KEYSIZE; keysize_guess++) {
+    keysize_guess_score = score_repeating_xor_keysize(ciphertext, ciphertext_length, keysize_guess); 
+    if (keysize_guess_score < best_keysize_score) {
+      best_keysize=keysize_guess;
+      best_keysize_score=keysize_guess_score;
+    }
+  }
+  *keysize=best_keysize;
+  if (keysize_score != NULL) *keysize_score=best_keysize_score;
+}
+
+int main() {
+  // Test hamming distance code
+  int hd = hamming_distance("this is a test", "wokka wokka!!!");
+  if (hd==37) {
+    printf("Hamming distance: correct\n");
+  } else {
+    printf("Hamming distance: incorrect\n");
+    printf("        Expected: 37\n");
+    printf("             Got: %d\n", hd);
+  }
+
+  // Read the file
+  char *ciphertext = malloc(20000);
+  int ciphertext_length;
+  if(read_base64_file("problems/set-1/6.txt", &ciphertext, &ciphertext_length)) {
+    printf("Base64 decode: complete. Size: %d\n", ciphertext_length);
+  } else {
+    printf("Base64 decode: failed\n");
+    exit(1);
+  }
+
+  // Guess the keysize using hamming distances of keysize-sized chunks
+  int keysize;
+  float keysize_score;
+  find_repeating_xor_keysize(ciphertext, ciphertext_length,
+                             MIN_KEYSIZE, MAX_KEYSIZE,
+                             &keysize, &keysize_score);
+  if (keysize == 29) {
+    printf("Keysize guess: correct\n");
+  } else {
+    printf("Guessed keysize: %d (%0.2f)\n", keysize, keysize_score);
+    printf("Keysize guess: incorrect\n");
+    exit(1);
+  }
+
+  // Check transpose code
+  char **transposed_simple = NULL;
+  int transpose_length_simple = 0;
+  transpose("ABCDEFG", 7, &transposed_simple, 3, &transpose_length_simple);
+  if (transpose_length_simple == 3) {
+    printf("Simple transpose: correct length\n");
+  } else {
+    printf("Simple transpose: incorrect length\n");
+    exit(1);
+  }
+  if (transposed_simple == NULL) { printf("no return\n"); exit(1); }
+  if (memcmp(transposed_simple[0], "ADG", 3)!=0) { printf("Transpose wrong %3s vs %3s.", "ADF", transposed_simple[0]); exit(1); }
+  if (memcmp(transposed_simple[1], "BE\000", 3)!=0) { printf("Transpose wrong %3s vs %4s.", "BE\\0", transposed_simple[1]); exit(1); }
+  if (memcmp(transposed_simple[2], "CF\000", 3)!=0) { printf("Transpose wrong %3s vs %4s.", "CF\\0", transposed_simple[2]); exit(1); }
+  printf("Simple transpose: correct\n");
+  free(transposed_simple[0]);
+  free(transposed_simple[1]);
+  free(transposed_simple[2]);
+  free(transposed_simple);
+
+  //Do transpose with sanity check
+  char **transposed = NULL;
+  int transpose_length = 0;
+  char *transpose_check = malloc(ciphertext_length);
+  transpose(ciphertext, ciphertext_length, &transposed, keysize, &transpose_length);
+  detranspose(transposed, keysize, transpose_check, ciphertext_length);
+  if (memcmp(ciphertext, transpose_check, ciphertext_length)==0) {
+    printf("Transpose: success\n");
+  } else {
+    printf("Transpose: failed\n");
+    exit(1);
+  }
+  free(transpose_check);
+
+  // Find each key bit
+  char *key = malloc(keysize+1);
+  int i;
+  key[keysize]=0;
+  char **best_decryptions = calloc(sizeof(char*), keysize);
+  float best_score=0;
+  printf("transpose length: %d\n", transpose_length);
+  for (i=0; i<keysize; i++) {
+    best_decryptions[i] = malloc(transpose_length+1);
+    best_decryptions[i][transpose_length]=0;
+    find_best_xor_candidate_buf(1,
+                                transposed[i], transpose_length,
+                                key + i, best_decryptions[i], &best_score);
+    /*
+    printf("                         %d-th bytes begin : ", i);
+    print_buffer(transposed[i], 20);
+    printf("Key byte %d, choosing %hhx (with score %0.2f)\n", i, key[i], best_score);
+    printf("(Apparent) Decryption of %d-th bytes begins: ", i);
+    print_buffer(best_decryptions[i], 20);
+    */
+
+    /*
+    char *sanity_check = malloc(transpose_length+1);
+    sanity_check[transpose_length]=0;
+    repeating_xor(transposed[i], key+i, 1, sanity_check, transpose_length);
+    float sanity_score = score_english_buf(sanity_check, transpose_length);
+    printf("Verifying key byte %d, using %hhx (with score %0.2f)\n", i, key[i], sanity_score);
+    printf("(Sanity C) Decryption of %d-th bytes begins: ", i);
+    print_buffer(sanity_check, 20);
+    free(sanity_check);
+    */
+
+    free(transposed[i]);
+  }
+  free(transposed);
+  printf("Guessed key: %s\n", key);
+
+  // Print the results (Method 1)
+  char *plaintext1 = malloc(ciphertext_length+1);
+  detranspose(best_decryptions, keysize, plaintext1, ciphertext_length);
+  plaintext1[ciphertext_length]=0;
+  for (i=0; i<keysize; i++) {
+    free(best_decryptions[i]);
+  }
+  free(best_decryptions);
+  float score1 = score_english_buf(plaintext1, ciphertext_length);
+  printf("Guessed decryption [method 1] (%0.2f): %20s\n", score1, plaintext1);
+  print_buffer(plaintext1, 100);
+
+  // Print the results (Method 2)
+  char *plaintext2 = malloc(ciphertext_length+1);
+  repeating_xor(ciphertext, key, keysize, plaintext2, ciphertext_length);
+  plaintext2[ciphertext_length]=0;
+  free(ciphertext);
+  free(key);
+  float score = score_english_buf(plaintext2, ciphertext_length);
+  printf("Guessed decryption [method 2] (%0.2f): %20s\n", score, plaintext2);
+  //print_buffer(plaintext2, 20);
+  if (memcmp(plaintext1, plaintext2, ciphertext_length+1)!=0) {
+    printf("Two methods give different results.\n");
+    exit(1);
+  }
+  free(plaintext1);
+  free(plaintext2);
+}
diff --git a/util.c b/util.c
index d3f32072fe7608bbf517d90142fa7f978dd0603f..0d83d64ae57cb0a45cb8c2cc58dec1a1928ea11c 100644 (file)
--- a/util.c
+++ b/util.c
@@ -1,5 +1,37 @@
+#include <stdlib.h>
+
 void fill(char* buffer1, int buf1_length, char* buffer2, int buf2_length) {
     int i;
     for (i=0;i<buf2_length;i++)
       buffer2[i] = buffer1[i%buf1_length];
 }
+
+void transpose(char* buffer, int buffer_length,
+               char ***transposed, int streams, int *stream_length_out) {
+  int i,j, si, stream_length;
+  stream_length=(buffer_length/streams)+1;
+  if (stream_length_out != NULL) *stream_length_out = stream_length;
+  if (*transposed == NULL) {
+    *transposed=(char **) calloc(sizeof(char*), streams);
+    for (i=0;i<streams;i++) {
+      (*transposed)[i] = (char *) malloc(stream_length);
+    }
+  }
+  for (i=0; i<stream_length; i++) {
+    for (j=0;j<streams; j++) {
+      si = i*streams + j;
+      (*transposed)[j][i] = (si<buffer_length) ? buffer[si] : 0;
+    }
+  }
+}
+void detranspose(char **transposed, int streams,
+                 char* buffer, int buffer_length) {
+  int i,j, si;
+  int stream_length=(buffer_length/streams)+1;
+  for (i=0; i<stream_length; i++) {
+    for (j=0;j<streams; j++) {
+      si = i*streams + j;
+      if (si < buffer_length) buffer[si] = transposed[j][i];
+    }
+  }
+}
diff --git a/util.h b/util.h
index 67aa1c8bf19ee9968eaffa948d2bdacba393e270..ba11fe0c6ce4c9316ae34dcc27b759bbe15592c6 100644 (file)
--- a/util.h
+++ b/util.h
@@ -1 +1,5 @@
 void fill(char* buffer1, int buf1_length, char* buffer2, int buf2_length);
+void transpose(char* buffer, int buffer_length,
+               char ***transposed, int streams, int *stream_length_out);
+void detranspose(char **transpose, int streams,
+                 char* buffer, int buffer_length);
diff --git a/xor.c b/xor.c
index 3a16263a52a4b24f4cfd2c9f02d619049c7087c3..3f522395831190a69789de283b597a24714db57b 100644 (file)
--- a/xor.c
+++ b/xor.c
@@ -1,15 +1,15 @@
 #include <stdlib.h>
 #include "util.h"
 
-void xor(char* buf1, char* buf2, char* buf3, int length) {
+void xor(char* in1, char* in2, char* out, int length) {
   int i;
   for(i=0; i<length; i++)
-    buf3[i] = buf1[i] ^ buf2[i];
+    out[i] = in1[i] ^ in2[i];
 }
 
-void repeating_xor(char* buf1, char* key, int keylength, char* buf3, int length) {
-  char *buf2 = malloc(length);
-  fill(key, keylength, buf2, length);
-  xor(buf1, buf2, buf3, length);
-  free(buf2);
+void repeating_xor(char* long_in, char* key, int keylength, char* out, int length) {
+  char *in2 = malloc(length);
+  fill(key, keylength, in2, length);
+  xor(long_in, in2, out, length);
+  free(in2);
 }
diff --git a/xor.h b/xor.h
index 0f037897e19fa708cd2f6f69cc4cab29602cb7f4..e57638a5a2a7d1dbf016e59104c71df940c2fe4b 100644 (file)
--- a/xor.h
+++ b/xor.h
@@ -1,2 +1,2 @@
-void xor(char* buf1, char* buf2, char* buf3, int length);
-void repeating_xor(char* buf1, char* key, int keylength, char* buf3, int length);
+void xor(char* in1, char* in2, char* out, int length);
+void repeating_xor(char* long_in, char* key, int keylength, char* out, int length);
index fdfb45c2248bc91f8ab30a4e5f98c7c8255b1839..ccfd4c2f18c7b40e06a7043b931bb7607ce98e74 100644 (file)
@@ -4,29 +4,36 @@
 #include "util.h"
 #include "xor.h"
 
-void select_best_xor_candidate_buf(char** candidates, int num_candidates, int candidate_length, char* ciphertext_buffer, int ciphertext_length, char* best_candidate, char* best_decryption, float* best_score) {
+void select_best_xor_candidate_buf(char** candidates, int num_candidates, int candidate_length, 
+                                   char* ciphertext_buffer, int ciphertext_length, 
+                                   char* best_candidate, char* best_decryption, float* best_score) {
   int candidate;
   unsigned char xor_byte;
   float score;
   unsigned char best_xor_candidate;
   char *xor_buffer = malloc(ciphertext_length);
   char *plaintext_buffer = malloc(ciphertext_length);
+  float top_score=-100;
   int ci;
 
-  *best_score = -100;
   for (ci=0; ci<num_candidates; ci++) {
     fill(candidates[ci], candidate_length, xor_buffer, ciphertext_length);
     xor(xor_buffer, ciphertext_buffer, plaintext_buffer, ciphertext_length);
     score = score_english_buf(plaintext_buffer, ciphertext_length);
-    if (score > *best_score) {
-      *best_score = score;
-      strncpy(best_decryption, plaintext_buffer, ciphertext_length);
-      strncpy(best_candidate, candidates[ci], candidate_length);
+    if (score > top_score) {
+      top_score = score;
+      if (best_candidate != NULL) memcpy(best_candidate, candidates[ci], candidate_length);
+      if (best_decryption != NULL) memcpy(best_decryption, plaintext_buffer, ciphertext_length);
     }
   }
+  if (best_score != NULL) *best_score = top_score;
+  free(plaintext_buffer);
+  free(xor_buffer);
 }
 
-void find_best_xor_candidate_buf(int length, char* ciphertext, int ciphertext_length, char* best_candidate, char* best_decryption, float* best_score) {
+void find_best_xor_candidate_buf(int length,
+                                 char* ciphertext, int ciphertext_length,
+                                 char* best_candidate, char* best_decryption, float* best_score) {
   int num_candidates = 1;
   unsigned int i, j, ci;
   for (i=0; i<length; i++) num_candidates = num_candidates*256;
@@ -41,4 +48,6 @@ void find_best_xor_candidate_buf(int length, char* ciphertext, int ciphertext_le
   select_best_xor_candidate_buf(candidate_pointers, num_candidates, length,
                                 ciphertext, ciphertext_length,
                                 best_candidate, best_decryption, best_score);
+  free(candidates);
+  free(candidate_pointers);
 }