Substitution Cipher Cryptanalysis
Here is another re-release. Several years ago, I made a tool to crack substitution ciphers. I have cleaned up the code and made some improvements.
The program uses setlocale() to modify the effect of e.g. the isalpha() and toupper() standard C functions. This makes it possible to support several languages that use more than just A to Z.
Here's what it looks like in action:
(Press F1 or F5 for help.)
Compile this code, and remember to link with the curses library:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <curses.h>
#include <limits.h>
#include <locale.h>
#define TEXT_MAX 65536
#define SAVE_EXTENSION "scca"
#define PAGE_OFFSET_SKIP 10
static int allowed_char[UCHAR_MAX];
static unsigned char cipher[UCHAR_MAX];
static unsigned char text[TEXT_MAX] = {'\0'};
static int allowed_char_len;
static int cipher_pos = 0;
static int text_offset = 0;
static void cipher_init(void)
{
unsigned char c;
setlocale(LC_ALL, "");
allowed_char_len = 0;
for (c = 0; c < UCHAR_MAX; c++) {
if (isupper(c)) {
allowed_char[c] = allowed_char_len;
allowed_char_len++;
} else {
allowed_char[c] = -1;
}
cipher[c] = ' ';
}
}
static void cipher_erase(void)
{
unsigned char c;
for (c = 0; c < UCHAR_MAX; c++) {
cipher[c] = ' ';
}
}
static unsigned char cipher_applied(unsigned char plain)
{
unsigned char c;
if (isupper(plain)) {
c = allowed_char[plain];
if (cipher[c] == ' ') {
return plain;
} else {
return cipher[c];
}
} else {
return plain;
}
}
static int text_read(char *filename)
{
int c, n;
FILE *fh;
fh = fopen(filename, "r");
if (! fh) {
fprintf(stderr, "fopen() failed on file: %s\n", filename);
return 1;
}
setlocale(LC_ALL, "");
n = 0;
while ((c = fgetc(fh)) != EOF) {
if (n > TEXT_MAX)
break;
if (c == '\r')
continue; /* CR causes issues, just strip it. */
text[n] = toupper(c);
n++;
}
fclose(fh);
return 0;
}
static void text_save(char *old_filename)
{
int i;
FILE *fh;
static char new_filename[PATH_MAX];
snprintf(new_filename, PATH_MAX, "%s.%s", old_filename, SAVE_EXTENSION);
erase();
fh = fopen(new_filename, "w");
if (fh == NULL) {
mvprintw(0, 0, "Could not open file for writing: %s", new_filename);
} else {
for (i = 0; i < TEXT_MAX; i++) {
if (text[i] == '\0')
break;
fputc(cipher_applied(text[i]), fh);
}
mvprintw(0, 0, "Deciphered text saved to: %s", new_filename);
}
fclose(fh);
mvprintw(1, 0, "Press any key to contiue...");
refresh();
flushinp();
getch(); /* Wait for keypress. */
flushinp();
}
static void display_help(void)
{
erase();
mvprintw(0, 0, "Left: Move cipher cursor left.");
mvprintw(1, 0, "Right: Move ciiper cursor right.");
mvprintw(2, 0, "Up: Scroll one line up.");
mvprintw(3, 0, "Down: Scroll one line down.");
mvprintw(4, 0, "Page Up: Scroll %d lines up.", PAGE_OFFSET_SKIP);
mvprintw(5, 0, "Page Down: Scroll %d lines down.", PAGE_OFFSET_SKIP);
mvprintw(6, 0, "Space: Erase cipher character.");
mvprintw(7, 0, "[A-Z]: Insert cipher character.");
mvprintw(8, 0, "F1 / F5: Display this help.");
mvprintw(9, 0, "F2 / F6: Display character frequency.");
mvprintw(10, 0, "F3 / F7: Reset cipher. (Erase all.)");
mvprintw(11, 0, "F4 / F8: Save deciphered text to file.");
mvprintw(12, 0, "F10: Quit");
mvprintw(14, 0, "Press any key to contiue...");
refresh();
flushinp();
getch(); /* Wait for keypress. */
flushinp();
}
static void display_frequency(void)
{
int count[UCHAR_MAX];
int i, y, x, maxy, maxx;
unsigned char c;
for (c = 0; c < UCHAR_MAX; c++) {
count[c] = 0;
}
for (i = 0; i < TEXT_MAX; i++) {
if (text[i] == '\0')
break;
count[text[i]]++;
}
erase();
getmaxyx(stdscr, maxy, maxx);
y = x = 0;
for (c = 0; c < UCHAR_MAX; c++) {
if (! isupper(c))
continue;
mvprintw(y, x, "%c: %d", c, count[c]);
x += 10;
if (x > (maxx - 10)) {
x = 0;
y++;
}
}
mvprintw(y + 1, 0, "Press any key to contiue...");
refresh();
flushinp();
getch(); /* Wait for keypress. */
flushinp();
}
static void screen_init(void)
{
initscr();
atexit((void *)endwin);
noecho();
keypad(stdscr, TRUE);
}
static void screen_update(void)
{
unsigned char c;
int i, y, x, maxy, maxx, skip_newline;
getmaxyx(stdscr, maxy, maxx);
erase();
/* Alphabet. */
x = 0;
for (c = 0; c < UCHAR_MAX; c++) {
if (allowed_char[c] != -1) {
mvaddch(0, x, c);
x++;
if (x > maxx)
break;
}
}
/* Cipher */
x = 0;
for (c = 0; c < UCHAR_MAX; c++) {
mvaddch(1, x, cipher[c]);
x++;
if (x > maxx)
break;
}
/* Upper Separation Line */
mvhline(2, 0, ACS_HLINE, maxx);
/* Text */
skip_newline = text_offset;
move(3, 0);
for (i = 0; i < TEXT_MAX; i++) {
if (text[i] == '\0')
break;
if (skip_newline > 0) {
if (text[i] == '\n') {
skip_newline--;
}
continue;
}
c = cipher_applied(text[i]);
if (c != text[i]) {
attron(A_REVERSE);
}
addch(c);
if (c != text[i]) {
attroff(A_REVERSE);
}
getyx(stdscr, y, x);
if (y >= (maxy - 1)) {
break;
}
}
/* Lower Separation Line */
mvhline(maxy - 1, 0, ACS_HLINE, maxx);
move(1, cipher_pos);
refresh();
}
static void screen_resize(void)
{
endwin(); /* To get new window limits. */
screen_update();
flushinp();
keypad(stdscr, TRUE);
cipher_pos = 0;
}
int main(int argc, char *argv[])
{
int c;
if (argc != 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return 0;
}
cipher_init();
if (text_read(argv[1]) != 0) {
return 1;
}
screen_init();
while (1) {
screen_update();
c = getch();
switch (c) {
case KEY_RESIZE:
screen_resize();
case KEY_LEFT:
cipher_pos--;
if (cipher_pos < 0)
cipher_pos = 0;
break;
case KEY_RIGHT:
cipher_pos++;
if (cipher_pos > allowed_char_len)
cipher_pos = allowed_char_len;
break;
case KEY_UP:
text_offset--;
if (text_offset < 0)
text_offset = 0;
break;
case KEY_DOWN:
text_offset++;
/* NOTE: Nothing preventing infinite scrolling... */
break;
case KEY_PPAGE:
text_offset -= PAGE_OFFSET_SKIP;
if (text_offset < 0)
text_offset = 0;
break;
case KEY_NPAGE:
text_offset += PAGE_OFFSET_SKIP;
/* NOTE: Nothing preventing infinite scrolling... */
break;
case KEY_F(1):
case KEY_F(5):
display_help();
break;
case KEY_F(2):
case KEY_F(6):
display_frequency();
break;
case KEY_F(3):
case KEY_F(7):
cipher_erase();
break;
case KEY_F(4):
case KEY_F(8):
text_save(argv[1]);
break;
case KEY_F(10):
exit(0);
case ' ':
cipher[cipher_pos] = ' ';
break;
default:
if (isalpha(c)) {
cipher[cipher_pos] = toupper(c);
}
break;
}
}
return 0;
}