Initial commit.

This commit is contained in:
2024-08-22 23:46:20 +02:00
commit 48a73d846d
8 changed files with 124896 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
build/

12
Makefile Normal file
View File

@@ -0,0 +1,12 @@
CC := gcc
CFLAGS := -lm -g -O3 -std=c2x -MMD
#CFLAGS += -fsanitize=address -lasan
all: build/solve
-include build/solve.d
build/solve: crack-vigenere.c | str.h
@mkdir -p $(@D)
$(CC) $(CFLAGS) $^ -o $@

245
crack-vigenere.c Normal file
View File

@@ -0,0 +1,245 @@
#include <assert.h>
#include <ctype.h>
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "str.h"
#define MIN(a, b) ((a) > (b) ? (b) : (a))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
static const char charset[26] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
static double charfreq_english[sizeof charset] = {
['A' - 'A'] = 0.082,
['B' - 'A'] = 0.015,
['C' - 'A'] = 0.028,
['D' - 'A'] = 0.043,
['E' - 'A'] = 0.127,
['F' - 'A'] = 0.022,
['G' - 'A'] = 0.020,
['H' - 'A'] = 0.061,
['I' - 'A'] = 0.070,
['J' - 'A'] = 0.0015,
['K' - 'A'] = 0.0077,
['L' - 'A'] = 0.040,
['M' - 'A'] = 0.024,
['N' - 'A'] = 0.067,
['O' - 'A'] = 0.075,
['P' - 'A'] = 0.019,
['Q' - 'A'] = 0.0095,
['R' - 'A'] = 0.060,
['S' - 'A'] = 0.063,
['T' - 'A'] = 0.091,
['U' - 'A'] = 0.028,
['V' - 'A'] = 0.0098,
['W' - 'A'] = 0.024,
['X' - 'A'] = 0.0015,
['Y' - 'A'] = 0.020,
['Z' - 'A'] = 0.00074,
};
int do_nothing(int ch)
{
return ch;
}
static int charset_contains(int ch)
{
return ch >= 'A' && ch <= 'Z';
}
static size_t charset_index(char ch)
{
if (isalpha(ch)) {
return toupper(ch) - 'A';
}
fprintf(stderr, "%s: invalid char %d\n", __func__, ch);
abort();
}
/* calculate incidence of coincidence of `text`
*
* map will transform the characters before calculating the ioc. For example,
* ioc(data, ..., tolower) will transform samples with tolower before checking
* if they are equal
* */
double ioc(struct str text, int stride, int offset, int (*map)(int))
{
assert(offset < stride);
if (stride > text.len) {
return NAN;
}
if (text.len < 1) {
return NAN;
}
int samples = 2048;
int matches = 0;
if (map == NULL) {
map == do_nothing;
}
for (int i = 0; i < samples; i++) {
size_t rand_a = (rand() % (text.len / stride)) * stride + offset;
size_t rand_b;
do {
rand_b = (rand() % (text.len / stride)) * stride + offset;
} while (rand_a == rand_b);
char a = map(text.data[rand_a]);
char b = map(text.data[rand_b]);
if (a == b) {
matches++;
}
}
return (double)matches / (double)(samples);
}
static void frequency_count(double output[static sizeof charset], const struct str text, size_t offset, size_t stride)
{
for (size_t i = 0; i < sizeof charset; i++) {
output[i] = 0;
}
assert(offset < stride);
for (size_t i = offset; i < text.len; i += stride) {
if (!charset_contains(text.data[i])) {
continue;
}
output[charset_index(text.data[i])] += 1.0;
}
}
static double frequency_correlation(const double a[static sizeof charset], const double b[static sizeof charset], size_t shift)
{
double sum = 0;
for (size_t i = 0; i < sizeof charset; i++) {
sum += a[i] * b[(i + shift) % sizeof charset];
}
return sum;
}
static void frequency_print(const double freq[static sizeof charset])
{
for (int i = 0; i < sizeof charset; i++) {
fprintf(stderr, "[%c] = %.0lf, ", charset[i], freq[i]);
}
}
static void vigenere_encode(struct str text, char* output, const char* key, size_t key_len, const char* charset, size_t charset_len)
{
for (size_t i = 0; i < text.len; i++) {
const char ch = text.data[i];
if (charset_contains(ch)) {
output[i] = charset[(charset_index(ch) + key[i % key_len]) % charset_len];
}
}
}
static void vigenere_decode(struct str text, char* output, const char* key, size_t key_len, const char* charset, size_t charset_len)
{
for (size_t i = 0; i < text.len; i++) {
const char ch = text.data[i];
if (charset_contains(ch)) {
output[i] = charset[(charset_index(ch) - key[i % key_len] + charset_len) % charset_len];
}
}
}
int main(int argc, char** argv)
{
srand(time(NULL));
FILE* f = argc < 2
? stdin
: fopen(argv[1], "r");
if (f == NULL) {
fprintf(stderr, "couldn't open file %s%m", argv[1]);
exit(EXIT_FAILURE);
}
struct str text = read_all_filter(f, charset_contains, toupper);
if (fclose(f) != 0) {
perror("fclose");
/* not fatal, continue */
}
if (text.data == NULL) {
exit(EXIT_FAILURE);
}
/* Find key length (stride)
* ========================*/
/* values better than threshold immidiately break the loop */
int key_len = 1;
{
constexpr double threshold = 1.6;
double best_score = -1.0;
for (int stride = 1; stride < text.len / 2; stride++) {
double result = 0.0;
for (int j = 0; j < stride; j++) {
result += ioc(text, stride, j, toupper);
}
result /= stride;
result *= 26.0; /* normalization */
if (result > best_score) {
best_score = result;
key_len = stride;
if (result > threshold) {
break;
}
}
}
fprintf(stderr, "best stride: %i (IOC %.2lf)\n", key_len, best_score);
}
/* Crack caesar ciphers column wise
* ================================ */
char key[key_len] = {}; /* VLAs are bad but whatever */
{
double frequencies[sizeof charset] = { 0 };
for (size_t col = 0; col < key_len; col++) {
frequency_count(frequencies, text, col, key_len);
double best = 0;
for (size_t i = 0; i < sizeof charset; i++) {
double n = frequency_correlation(frequencies, charfreq_english, i);
if (n > best) {
key[col] = sizeof charset - i;
best = n;
}
}
}
}
/* print key */
printf("key: ");
for (size_t i = 0; i < key_len; i++) {
printf("%c", charset[key[i]]);
}
printf("\n");
vigenere_decode(text, text.data, key, key_len, charset, sizeof charset);
str_println(text, stdout);
str_free(&text);
return EXIT_SUCCESS;
}

170
str.h Normal file
View File

@@ -0,0 +1,170 @@
#pragma once
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
struct str {
size_t len;
size_t cap;
char* data;
};
static struct str str_slice(struct str str, size_t begin, size_t end)
{
return (struct str) {
.len = end - begin,
.data = str.data + begin
};
}
static void str_free(struct str* str)
{
free(str->data);
str->len = 0;
str->cap = 0;
}
static int str_right_pad(struct str* str, char ch, size_t n)
{
struct str restore = *str;
int retval = 0;
size_t new_len = str->len + n;
if (new_len > str->cap) {
str->cap *= 2;
void* tmp = realloc(str, str->cap);
if (!tmp) {
retval = -errno;
goto fail;
}
str->data = tmp;
}
for (size_t i = str->len; i < new_len; i++) {
str->data[i] = ch;
}
str->len = new_len;
return retval;
fail:
*str = restore;
return retval;
}
static void str_println(struct str str, FILE* f)
{
for (size_t i = 0; i < str.len; i++) {
fputc(str.data[i], f);
}
fputc('\n', f);
}
static int str_append(struct str* str, int ch)
{
struct str restore = *str;
if (str->len + 1 > str->cap) {
str->cap *= 2;
void* tmp = realloc(str->data, str->cap);
if (!tmp) {
*str = restore;
return -1;
}
str->data = tmp;
}
str->data[str->len++] = ch;
return 0;
}
/* read contents from file `f` and return as str */
static struct str read_all(FILE* f)
{
struct str str = {
.cap = 4096,
.len = 0,
.data = NULL,
};
str.data = malloc(str.cap);
if (str.data == NULL) {
perror("malloc");
return (struct str) { 0 };
}
while (1) {
str.len += fread(&str.data[str.len], 1, str.cap - str.len, f);
if (str.len != str.cap) {
if (feof(f)) {
break;
} else if (ferror(f)) {
perror("fread");
goto fail;
} else {
/* programming error */
fprintf(stderr, "whoopsie!\n");
abort();
}
}
str.cap *= 2;
void* tmp = realloc(str.data, str.cap);
if (!tmp) {
perror("realloc");
goto fail;
}
str.data = tmp;
}
str.len -= 1;
/* shrink to what's needed */
void* tmp = realloc(str.data, str.len);
if (tmp) {
/* it's ok if shrinking fails */
str.data = tmp;
str.cap = str.len;
}
return str;
fail:
free(str.data);
return (struct str) { 0 };
}
/* read contents from file `f`, but first apply `map` to characters and then
* filter them with `filter` */
static struct str read_all_filter(FILE* f, int (*filter)(int ch), int (*map)(int ch))
{
struct str str = {
.data = NULL,
.cap = 4096,
.len = 0,
};
str.data = malloc(str.cap);
if (str.data == NULL) {
perror("malloc");
goto malloc_failed;
}
int c;
while ((c = fgetc(f)) != EOF) {
c = map(c);
if (!filter(c)) {
continue;
}
int ok = str_append(&str, c);
if (ok == -1) {
goto fail;
}
}
return str;
fail:
free(str.data);
malloc_failed:
return (struct str) { 0 };
}

10
txt/lego-city.txt Normal file
View File

@@ -0,0 +1,10 @@
H QLY VWG WLOSIY TBPC KSH YMGPF EB CPJV GTEM
OHRCW ALP CSOQLP KLPTNCLHVC
KLC!
MFWHR KSH OIWTQKDKPU HRO ZTB HF EKL VPDQQS
GCHWECP HDS CTILPTYS, HCNPU ALP DHNSKNKLV LYR IOBP WOI CPGYIV
EKL RPH SISIRHUGJ NCHZVNWPSY QFKA CPJV GTEM

1
txt/picoctf.txt Normal file
View File

@@ -0,0 +1 @@
rgnoDVD{O0NU_WQ3_G1G3O3T3_A1AH3S_2951c89f}

124456
txt/shakespeare-encrypted.txt Normal file

File diff suppressed because it is too large Load Diff

1
txt/vigenered.txt Normal file
View File

@@ -0,0 +1 @@
QPWKALVRXCQZIKGRBPFAEOMFLJMSDZVDHXCXJYEBIMTRQWNMEAIZRVKCVKVLXNEICFZPZCZZHKMLVZVZIZRRQWDKECHOSNYXXLSPMYKVQXJTDCIOMEEXDQVSRXLRLKZH