25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more...
Home   Projects   Up   Sitemap   Search   Blog   Forum+Chat   About Us   Privacy   Terms of Use   Feedback   FAQ   Images   Services   Ads   Donate   Humor

Decipher.cpp, Encipher.cpp
Create and solve substitution ciphers

A substitution cipher is text in which every letter A-Z has been transposed to some other letter A-Z. The text looks like nonsense, but if you can determine which letter in the ciphered text stands for which letter in the alphabet, you can restore the text to its original state. Substitution cipher puzzles are published in some newspapers and game magazines.

Encipher.cpp creates a substitution cipher from a text file you specify on the command line.

Decipher.cpp assists you with solving a substitution cipher. It provides an interface where you can easily and quickly swap letter translations and see the results immediately in the text. This is a lot easier than doing it on paper, but of course if you use this program you are cheating!

Created with Borland C++ 4.0 for an MSDOS target. Links to the additional source files it needs are in the code below.

New!There is a version of these programs for Microsoft Visual C++.

Screenshot of Decipher.cpp

showing letter frequencies and the text being decoded. Locked letters are green.

Screenshot of Decipher.cpp console.

 

DECIPHER.CPP

/*	DECIPHER.CPP			2-25-96
	Copyright (C)1994-1996 Steven Whitney.
	Initially published by http://25yearsofprogramming.com.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License (GPL)
	Version 3 as published by the Free Software Foundation.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Assists with solving substitution cipher puzzles such as are sometimes published in newspapers.
A substitution cipher is text in which every letter A-Z has been transposed to some other
letter A-Z. The text looks like nonsense, but if you can determine which letter in the
ciphered text stands for which letter in the alphabet, you can restore the text to its
original state.

The program loads the source text file you specify on the command line, then does a quick
preliminary decode in which the most common letters in the text are translated to the most
commonly occurring letters in most English texts. It may or may not be correct, but it's
a start. Then it provides an interface where you can easily and quickly swap letter
translations and see the results immediately in the text. This is a lot easier than doing
it on paper, but of course if you use this program you are cheating!

Created with Borland C++ 4.0 for MSDOS.

-----
to do

Revised letter frequencies, from a tv show: ETAONI....JKXQZ.
TH is the most common letter pair. EA is the most common vowel pair.

*/
#include "c:\bcs\my.h"
#pragma hdrstop

#include "c:\bcs\mylib.cpp"

//////////////////////////////////////////////////////////////////////////////
// GLOBAL VARIABLES
//----------------------------------------------------------------------------
struct translate		// TRANSLATION TABLE
{
	char orig;			// LETTER FROM INPUT TEXT
	char trans;			// LETTER IT'S TRANSLATED TO
	int freq;			// FREQUENCY OF ORIG IN INPUT TEXT
	char lock;			// PREVENTS TRANS FROM BEING CHANGED
} letter[26];

string intext;			// INPUT TEXT
constream scr;			// console display

//----------------------------------------------------------------------------
// DETERMINE LETTER FREQUENCIES
void freqtally()
{
for(int i = 0 ; i < 26 ; i++)					// FOR EACH LETTER,
	for(int j = 0 ; j < intext.length() ; j++)	// SEARCH FOR IT,
		if(intext[j] == letter[i].orig)			// AND, WHEN FOUND,
			letter[i].freq++;					// INCREMENT COUNT
}
//----------------------------------------------------------------------------
// INITIALIZES TRANSLATION TABLE
void initable()
{
string source("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
for(int i = 0 ; i < 26 ; i++)
{
	letter[i].orig = source[i];
	letter[i].trans = source[i];
	letter[i].freq = 0;
	letter[i].lock = FALSE;
}
}
//----------------------------------------------------------------------------
// GETS INPUT TEXT FROM DISK FILE
// RETURNS TRUE IF OK, aborts program ON ERROR
int readtext(const char *filename)
{
ifstream infile(filename);
if(!infile)
	aborts("Error reading file, or not found.");
intext.read_file(infile);
intext.to_upper();
return(TRUE);
}
//----------------------------------------------------------------------------
// DISPLAY TEXT, AS TRANSLATED, and optionally also write to disk file.
// This looks kind of messy, but putting it into 2 different functions
// didn't look much better.  It would probably be ok to just use one
// function, with the constream manipulators, and let them be ignored
// when writing to a disk file.  Could also break some of the sections here
// into separate functions (showtranslations(), showfreq(), showastranslated())
void showtrans(int tofile)  // FLAG WHETHER TO also WRITE TO DISK FILE
{
ofstream outfile;
if(tofile)
	outfile.open("deciphed.txt");

for(int i = 0 ; i < 26 ; i++)				// SHOW CURRENT CODE SCHEME
{
	scr << setclr(letter[i].lock ? GREEN : LIGHTGRAY);   // DIFFERENT COLOR IF LOCKED
	scr	<< setw(3) << setiosflags(ios::left) << letter[i].trans;
	if(tofile)
		outfile	<< setw(3) << setiosflags(ios::left) << letter[i].trans;
}
scr << endl;
if(tofile)
	outfile << endl;
scr << setclr(RED);
for(i = 0 ; i < 26 ; i++)					// SHOW LETTER FREQUENCIES
{
	scr	<< setw(3) << setiosflags(ios::left) << letter[i].freq;
	if(tofile)
		outfile	<< setw(3) << setiosflags(ios::left) << letter[i].freq;
}
scr << endl << endl;
if(tofile)
	outfile << endl << endl;
for(i = 0 ; i < intext.length() ; i++)		// FOR EACH CHAR IN INTEXT,
{
	if(kbhit())								// ANY KEY CUTS LISTING SHORT
	{
		getch();
		break;
	}
	BOOL found = FALSE;
	for(int j = 0 ; j < 26 ; j++)			// LOOK IT UP IN TABLE
		if(intext[i] == letter[j].orig)		// IF FOUND, IT'S A LETTER
		{                                  	// SHOW ITS TRANSLATION
			scr << setclr(letter[j].lock ? GREEN : LIGHTGRAY) << letter[j].trans;
			if(tofile)
				outfile << letter[j].trans;
			found = TRUE;
			break;							// BREAK LOOP TO DO NEXT CHAR
		}
	if(!found)								// OUTPUT NON-LETTER AS-IS
	{
		if(intext[i] == '\t')	// for some reason, constream seems not
			scr << "    ";		// to expand tabs, so expand to spaces.
		else
			scr << setclr(LIGHTGRAY) << intext[i];
		if(tofile)
			outfile << intext[i];
	}
}
scr << endl;
if(tofile)
	outfile << endl;
}                      // end showtrans()
//----------------------------------------------------------------------------
// SWAP TRANSLATIONS FOR 2 LETTERS
// BEFORE: ZBRUET   AFTER: ZBRUET  <- LETTER[].ORIG, NEVER CHANGES
//         ZBRUET          ETRUZB  <- LETTER[].TRANS
// RETURNS TRUE IF OK, FALSE IF SWAP DISALLOWED
int swaplets(char a, char b)	// LETTERS WHOSE TRANSLATIONS ARE TO BE SWAPPED
{
a = toupper(a);
b = toupper(b);
				// adex and bdex are INDEXES OF A AND B IN LETTER[].TRANS
for(int adex = 0 ; (adex < 26) && (letter[adex].trans != a) ; adex++); // LOCATE A
for(int bdex = 0 ; (bdex < 26) && (letter[bdex].trans != b) ; bdex++); // LOCATE B

if((letter[adex].lock) || (letter[bdex].lock))	// TEST FOR LOCKED
	return(FALSE);

char ch = letter[adex].trans;					// SWAP THEIR TRANSLATIONS
letter[adex].trans = letter[bdex].trans;
letter[bdex].trans = ch;
return(TRUE);
}
//----------------------------------------------------------------------------
// INITIALLY ASSIGN TRANSLATIONS FOR MOST FREQUENT LETTERS
void etaoin()
{
// ASSIGN MOST FREQUENT LETTER TO 'E', TEMPORARILY LOCK IN PLACE,
// 2ND MOST FREQUENT = 'T', ETC.
swaplets(letter[0].orig,'E'); letter[0].lock = TRUE;
swaplets(letter[1].orig,'T'); letter[1].lock = TRUE;
swaplets(letter[2].orig,'A'); letter[2].lock = TRUE;
swaplets(letter[3].orig,'O'); letter[3].lock = TRUE;
swaplets(letter[4].orig,'I'); letter[4].lock = TRUE;
swaplets(letter[5].orig,'N'); letter[5].lock = TRUE;
swaplets(letter[6].orig,'S'); letter[6].lock = TRUE;
swaplets(letter[7].orig,'H'); letter[7].lock = TRUE;
swaplets(letter[8].orig,'R'); letter[8].lock = TRUE;
swaplets(letter[9].orig,'D'); letter[9].lock = TRUE;
swaplets(letter[10].orig,'L'); letter[10].lock = TRUE;
swaplets(letter[11].orig,'U'); letter[11].lock = TRUE;
					// SHUFFLE THESE SELDOM-USED TO END OF ARRAY, IF POSSIBLE
swaplets(letter[25].orig,'Q'); letter[25].lock = TRUE;
swaplets(letter[24].orig,'J'); letter[24].lock = TRUE;
swaplets(letter[23].orig,'X'); letter[23].lock = TRUE;
swaplets(letter[22].orig,'Z');

for(int i = 0 ; i < 26 ; i++)			// RE-UNLOCK THEM ALL
	letter[i].lock = FALSE;
}
//----------------------------------------------------------------------------
// SORT ARRAY IN ORDER OF DECREASING FREQUENCY
int sortlets(struct translate *l, struct translate *r)
{
	return(r->freq - l->freq);		// a reverse-sort (backwards)
}
//----------------------------------------------------------------------------
//	MAIN
int main(int argc, char **argv)
{
scr.window(1,1,80,25);
scr.clrscr();
scr << "Usage: DECIPHER filename.ext.  Output, if any, goes to DECIPHED.TXT" << endl;
if(argc != 2)
	return(1);

scr.clrscr();
initable();							// PUT LETTERS IN THE TRANS. TABLE
readtext(argv[1]);					// GET CIPHERED TEXT FROM FILE
freqtally();						// TALLY LETTER FREQUENCIES
									// sort by decreasing frequency
qsort(letter,26,sizeof(letter[0]),(int (*)(const void *,const void *)) sortlets);

// if you make this initial translation optional, it won't be cheating. use yesno().
etaoin();							// ASSIGN ETAOIN TO MOST FREQ.

int i, j;
string buf;
char ch;
while(1)							// START PROCESSING
{
	showtrans(FALSE);				// DISPLAY TEXT
	scr << setclr(LIGHTGRAY) << "<S>wap, <L>ock, <U>nlock, <D>one, <Q>uit: ";
	ch = tolower(getche());
	scr << endl;
	switch(ch)
	{
		case 's':							// SWAP LETTERS
			do
			{
				scr << "Swap: ";
				getline(cin,buf,'\n');
			}
			while(buf.length() < 2);		// C/R = ABORT
			if(!swaplets(buf[0],buf[1]))
				putchar(7);					// A LETTER IS LOCKED - BEEP
			scr << endl;
			break;
		case 'u':                           // unlock letters
		case 'l':							// LOCK LETTERS
			scr << (ch == 'l' ? "Lock letter(s): " : "Unlock letter(s): ");
			getline(cin,buf,'\n');			// could use my cgets() from net.cpp
			if(!buf.length())				// C/R = ABORT
				break;
			buf.to_upper();
			for(j = 0 ; j < buf.length() ; j++)
				for(i = 0 ; i < 26 ; i++)
					if(letter[i].trans == buf[j])
					{
						letter[i].lock = (ch == 'l' ? TRUE : FALSE);
						break;
					}
			scr << endl;
			break;
		case 'd':							// DONE - WRITE TO DISK & EXIT
			showtrans(TRUE);
		case 'q':              				// quit - no write to disk
			scr << "Press ESC to quit. ";
			if(getch() == 27)
				return(0);
			scr << endl;
			break;
		default:
			break;
	}					// END SWITCH
}						// END WHILE(1)
}						// END MAIN

 

ENCIPHER.CPP

/*	ENCIPHER.CPP		2-25-96
	Copyright (C)1994-1996 Steven Whitney.
	Initially published by http://25yearsofprogramming.com.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License (GPL)
	Version 3 as published by the Free Software Foundation.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


CREATES SUBSTITUTION CIPHERS BY RANDOMLY REARRANGING THE ALPHABET.
It inputs the file you specify on the command line, enciphers it, and writes it to CIPHERED.TXT.

Probably my only remaining example of qsort().  Don't change to sorted container.

*/
#include "c:\bcs\my.h"
#pragma hdrstop

#include "c:\bcs\mylib.cpp"

//////////////////////////////////////////////////////////////////////////////
struct translate		// TRANSLATION TABLE
{
	char orig;			// LETTER FROM INPUT TEXT
	char trans;			// LETTER IT'S TRANSLATED TO
	int sequence;		// USED FOR SHUFFLING
} letter[26];

//----------------------------------------------------------------------------
// SORT ROUTINE FOR SHUFFLING
int sortlets(struct translate *l,struct translate *r)
{
	return(l->sequence - r->sequence);
}
//----------------------------------------------------------------------------
// PUT translation LETTERS INTO TRANSLATION TABLE,  ASSIGN RANDOM NUMBER TO
// EACH LETTER of the alphabet, AND SORT THE ARRAY BY THE RANDOM NUMBERS,
// which causes them to be randomly shuffled.
// Then add the original letters, in proper order.  This leaves the array
// elements in order of source letters ABC..., with each one tied to
// a random and unique translation letter.
void initable()
{
string source("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
for(int i = 0 ; i < 26 ; i++)				// FILL TRANS WITH translation LETTERS
{
	letter[i].trans = source[i];
	letter[i].sequence = random(1000);
}
											// MIX THEM UP
qsort(letter,26,sizeof(letter[0]),(int (*)(const void *,const void *)) sortlets);

for(i = 0 ; i < 26 ; i++)		// NOW FILL in ORIG IN PROPER ORDER
	letter[i].orig = source[i];
}
//----------------------------------------------------------------------------
// MAIN
int main(int argc, char **argv)
{
cout << "Usage: ENCIPHER filename.ext  (Output file will be CIPHERED.TXT)";

if(argc != 2)
	return(1);

randomize();
initable();				// FILL translation table, SHUFFLE ALPHABET

string buf;
ifstream infile(argv[1]);
if(!infile)
	aborts("Input file not found.");
ofstream outfile("ciphered.txt");
while(getline(infile,buf,'\n'))
{
	for(int j = 0 ; j < buf.length() ; j++)	// for each letter in file line,
	{
		char ch = toupper(buf[j]);       	// to uppercase for testing
		for(int i = 0 ; i < 26 ; i++)		// LOOK EACH LETTER UP,
			if(ch == letter[i].orig)
			{
				ch = letter[i].trans;		// AND TRANSLATE IT
				break;
			}
		buf[j] = ch;     		// move back, possibly translated
	}
	outfile << buf << endl;
}
if(outfile.fail())
	cout << "File write error." << endl;
else
	cout << "\nCiphered text is in file CIPHERED.TXT." << endl;
return(0);
}										// END MAIN

 

 

Valid HTML 4.01 Transitional Valid CSS
View content labeling at ICRA.
Copyright ©2008 Steven Whitney. Last modified 07/03/2008.