commit 30d2d1e5ed349664692276dcc538d85d35e30817
parent 03dd3fff2cc0dc99e3e1483b0da90134fe91d391
Author: Jack Sangdahl <jack@pngu.org>
Date: Mon, 29 Sep 2025 10:19:01 -0600
challenge 3: single-byte xor cipher
Diffstat:
3 files changed, 88 insertions(+), 0 deletions(-)
diff --git a/lib/stats.c b/lib/stats.c
@@ -0,0 +1,44 @@
+#include "stats.h"
+
+int frequency_score(const unsigned char *input, const size_t input_len) {
+ static int freqs[] = {
+ 8167, 1492, 2782, 4253, 12702, 2228, 2015, 6094, 6966,
+ 153, 772, 4025, 2406, 6749, 7507, 1929, 95, 5987,
+ 6327, 9056, 2758, 978, 2360, 150, 1974, 74,
+ };
+ int score = 0;
+ for (size_t i = 0; i < input_len; i++) {
+ if (input[i] == 32)
+ score += 10000;
+ else if (input[i] >= 65 && input[i] <= 90)
+ score += freqs[input[i] - 65];
+ else if (input[i] >= 97 && input[i] <= 122)
+ score += freqs[input[i] - 97];
+ else if (input[i] >= 128)
+ score -= 5000;
+ else
+ score -= 1000;
+ }
+ return score;
+}
+
+double index_coincidence(const char *input) {
+ int n = 0, values[26] = {0}, curr;
+ double sum = 0.0, total = 0.0;
+ for (size_t i = 0; i < strlen(input); i++) {
+ curr = input[i];
+ if (curr >= 65 && curr <= 90) {
+ values[curr - 65]++;
+ n++;
+ } else if (curr >= 97 && curr <= 122) {
+ values[curr - 97]++;
+ n++;
+ }
+ }
+ for (size_t i = 0; i < 26; i++) {
+ curr = values[i];
+ sum += curr * (curr - 1);
+ }
+ total = sum / (n * (n - 1));
+ return total;
+}
diff --git a/lib/stats.h b/lib/stats.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include <string.h>
+
+int frequency_score(const unsigned char *input, const size_t input_len);
+double index_coincidence(const char *input);
diff --git a/src/03_single_byte_xor_cipher.c b/src/03_single_byte_xor_cipher.c
@@ -0,0 +1,38 @@
+// Single-byte XOR cipher
+#include "hex.h"
+#include "stats.h"
+#include "xor.h"
+#include <assert.h>
+#include <stdio.h>
+
+/*
+ * the hex encoded string:
+ * 1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736
+ * ... has been xor'd against single character. find the key, decrypt the
+ * message. you can do this by hand, but don't: wrrite code to do it for you.
+ * how? devise some method for "scoring" a piece of english plaintext. character
+ * frequency is a good metric. evaluate each output and chose the one with the
+ * best score.
+ */
+
+int main(void) {
+ static char *input =
+ "1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736";
+ static char *output = "Cooking MC's like a pound of bacon";
+ char *ascii;
+ unsigned char *xord = NULL;
+ size_t input_len = hex_to_ascii(input, &ascii);
+ unsigned char res =
+ find_single_byte_xor((unsigned char *)ascii, input_len, &frequency_score);
+ if (res > 0) {
+ unsigned char table[] = {res};
+ xor((unsigned char *)ascii, input_len, table, sizeof(char), &xord);
+ printf("Input: %s\n", input);
+ printf("Key: %c\n", res);
+ printf("Output: %s\n", xord);
+ assert(!strcmp((char *)xord, output));
+ }
+ free(ascii);
+ free(xord);
+ return 0;
+}