Last active
August 26, 2025 20:57
-
-
Save amcgregor/3c3816afd470f245a469547b98f5f110 to your computer and use it in GitHub Desktop.
My rotating-ring Cheshire cipher, including sample plaintext and ciphertext. #encryption #ciphertext #rotcipher
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# encoding: utf-8 | |
from optparse import OptionParser | |
import sys | |
class Cheshire(object): | |
"""Cheshire, a class for simple Caesar-cipher-like encryption. | |
Cheshire, depending on use, is a very complex form of Caesar-cipher. | |
It has superb security if used with a properly random passphrase and | |
completely random alphabet. The encrypted result usualy has a | |
uniform character distribution. | |
Cheshire was chosen as the name due to the polymorphing ability of | |
the cipher. For example, with an ordered alphabet and a passphrase | |
length of one, Cheshire is a simple Caesar cipher. Using a | |
passphrase the same length as the message to encode, Cheshire is an | |
unbreakable one-time-pad. Cheshire supports passphrase repitition | |
and autokeying, too. | |
The entire Cheshire cipher is implementable as one fixed, and two | |
free-spinning alphabetic wheels. | |
B{Note:} All characters in the passphrase and message body B{must} | |
be present in the alphabet. | |
>>> alphabet = "QWERTYUIOPASDFGH JKLZXCVBNM,." | |
>>> cat = Cheshire("MY KEYPHRASE", alphabet) | |
>>> cat.encode("HELLO WORLD.") | |
'KMRWUSXCQPWM' | |
>>> cat.decode(" Y F.MATFDXQPGQDU,AMN") | |
'FAREWELL CREUL WORLD.' | |
Using Cheshire as an overkill ROT-13: | |
>>> alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | |
>>> cat = Cheshire("N", alphabet) | |
>>> cat.encode("HELLO WORLD.") | |
'URYYB JBEYQ.' | |
>>> cat.decode("SNERJRYY PERHY JBEYQ.") | |
'FAREWELL CREUL WORLD.' | |
""" | |
def __init__(self, keys, alphabet, autokey = False): | |
"""Initialize the Cheshire cipher. | |
All characters in the keyphrase B{must} be present in the | |
alphabet. | |
@param keys: The keyphrase. This can be a string, or a list or | |
tuple of individual characters. | |
@param alphabet: The alphabet. | |
@type alphabet: string | |
@param autokey: Enable or disable autokeying. | |
@type autokey: bool | |
""" | |
self.keys = keys | |
self.alphabet = alphabet | |
self.autokey = autokey | |
def rotate(self, offset): | |
"""Rotate the alphabet by a given offset. | |
@param offset: The offset by which to rotate the alphabet. | |
@type offset: int | |
@return: Returns the untrimmed, rotated alphabet. | |
@rtype: string | |
""" | |
return self.alphabet[offset:] + self.alphabet[:offset] | |
def encode(self, text): | |
"""Encode a block of plaintext using the passphrase and alphabet. | |
@param text: The plaintext block to encode. | |
@type text: string | |
@return: Returns the cyphertext representation of the plaintext. | |
@rtype: string | |
""" | |
cyphertext = list() | |
for i in xrange(len(text)): | |
if not self.autokey or i < len(self.keys): | |
alphabet = self.rotate(self.alphabet.index(self.keys[i%len(self.keys)])) | |
else: | |
alphabet = self.rotate(self.alphabet.index(text[i-len(self.keys)])) | |
if self.alphabet.find(text[i]) < 0: | |
cyphertext.append(text[i]) | |
continue | |
cyphertext.append(self.alphabet[alphabet.index(text[i])]) | |
return "".join(cyphertext) | |
def decode(self, text): | |
"""Decode a block of ciphertext using the passphrase and alphabet. | |
@param text: The ciphertext block to decode. | |
@type text: string | |
@return: Returns the plaintext representation of the ciphertext. | |
@rtype: string | |
""" | |
cleartext = list() | |
for i in xrange(len(text)): | |
if not self.autokey or i < len(self.keys): | |
alphabet = self.rotate(self.alphabet.index(self.keys[i%len(self.keys)])) | |
else: | |
alphabet = self.rotate(self.alphabet.index(cleartext[i-len(self.keys)])) | |
if self.alphabet.find(text[i]) < 0: | |
cleartext.append(text[i]) | |
continue | |
cleartext.append(alphabet[self.alphabet.index(text[i])]) | |
return "".join(cleartext) | |
if __name__ == "__main__": | |
base_alphabet = '''T&#LAt=~M$:c- hD]ZB|S+rfk7_}1J[,\0HW | |
obj(3Y"m?C'2e{vFaK5GE/^z<Xs.Rixwdp>8PlV4!u*n;9Og)6UQyqN@I%''' | |
# Step 1: Decode command-line arguments. | |
parser = OptionParser(usage="%prog [-Adev] [--help] [-a alphabet] [--] <keyphrase> [filename]", version="%prog 1.0") | |
parser.add_option("-a", "--alphabet", dest="alphabet", default=base_alphabet, | |
help="use a custom ALPHABET", metavar="ALPHABET") | |
parser.add_option("-A", "--auto-key", action="store_true", dest="autokey", default=False, | |
help="use plaintext to continue the keyphrase") | |
parser.add_option("-e", "--encode", action="store_true", dest="encode", default=True, | |
help="encode the given input or file (default)") | |
parser.add_option("-d", "--decode", action="store_false", dest="encode", | |
help="decode the given input or file") | |
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, | |
help="display process analysis") | |
parser.add_option("-i", "--ignore-errors", action="store_true", dest="ignore", default=False, | |
help="ignore invalid characters in keyphrase and text") | |
(options, args) = parser.parse_args() | |
if len(args) < 1: | |
parser.error("You must supply a keyphrase.") | |
if len(args) > 2: | |
parser.error("Supply at most one input file.") | |
keyphrase = args[0] | |
keys = list() | |
for i in xrange(len(options.alphabet)): | |
if base_alphabet.count(options.alphabet[i]) != 1: | |
parser.error("More than one occurance of the character '%s' in alphabet." % options.alphabet[i]) | |
for i in xrange(len(keyphrase)): | |
if base_alphabet.find(keyphrase[i]) < 0: | |
if not options.ignore: | |
parser.error("Invalid character '%s' in keyphrase." % keyphrase[i]) | |
else: keys.append(keyphrase[i]) | |
if len(keys) == 0: | |
parser.error("No valid characters in keyphrase.") | |
# Load the plaintext/ciphertext from the selected file, or stdin. | |
if len(args) == 2: file = open(args[1]) | |
else: file = sys.stdin | |
text = file.read() | |
cheshire = Cheshire(keys, options.alphabet, options.autokey) | |
if options.encode: | |
result = cheshire.encode(text) | |
print result | |
else: | |
result = cheshire.decode(text) | |
print result | |
if options.verbose: | |
print >> sys.stderr, "Character frequency analysis:" | |
characters = dict() | |
for char in options.alphabet: | |
characters[char] = [0.0, 0.0] | |
for char in text: | |
if char in characters: characters[char][0] += 1 | |
for char in result: | |
if char in characters: characters[char][1] += 1 | |
source_len = len(text) | |
result_len = len(result) | |
items = [(v, k) for k, v in characters.items()] | |
items.sort() | |
items.reverse() | |
items = [(k, v) for v, k in items] | |
for char, counts in items: | |
print >> sys.stderr, "Character: %s\tSource: %d (%0.2f)\tDestination: %d (%0.2f)" % ( char, counts[0], counts[0]/source_len*100, counts[1], counts[1]/source_len*100 ) | |
sys.exit() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6^sj^WxcZ}lI,.TD/LW_mz!{+;,&g%FfOp@[++a.8H35 PbhTTT8+g<N1@xkWicW|(x.w[)7<361q!K$yp$t"E'5TROTO>,oT{y% sN /F'ZPM ^VTZjF&|8QJUXR2f;v%abJ_DDdnPRx[zETDg | |
ZKOIql>=36qTdaArFS | |
C#w6}PtTAUpnIb | |
_%2$ | |
>3dAo{M6<(yWKnbI$su | |
ETZb%fl[zE7D)R %gaQ6ypp"G>@U$eruD3 | |
C$(IM[o.hTq[#7u;nZ(9KJMy weNMyr5!Pl hTCT;C(z"T5;;'j =T'nm'orRBfi,1,GM :.JzTj9oBfjWz:QN1"-Rhf52<U%^%w*KkMm{QoHQP | |
6g_MF3 | |
%'R=cRj%F$eiO[aGx.6hFt&H$w%&Ld-8@xG TiA | |
&Wvr%i [.eM[?$I{: T&"S&K5<mb%&m%_r2r.B+99'm8Q*=# rKOIql>[ZuY8bRZ{3iB%:7^q;B%r(SpGk<WOP+r,>36m% ABJkU$W6.T)p | |
g%|f,6a'9:x&8&$6#j?g((7[msW/*[NhrRuxblRF6&Y7L,ZBM ,5.7w,bTTY;5R,J((V56.,HrR,JXrqT_8w2MWZl%g<# _jDpxsW:cZIFTb~{Zw3 a9coCyN-4,[xzGpAC_T9VG(| ,uT SyTYu25rR1;TKT},Pe|7R>+J<xTG^ps{{RvK6"]#)g}Ri&JsR%:c2aV%., | |
CvTxa | |
ZKOIqUII@h>-#4UPMm8$d[/qTTTTn_$4W{i|k%3kUZC$$D&?5"VTC[o(p$pc<s*STTTTTT]as<r)4pWKS5VRjDST5i8U<gzRff*r+4 5h_|d9[}lI,.TD/LW_mz!{+;,&g%FfOp@[++a.8H35 PbhTTT8+g<N1@xkWicW|(x.w[)7<361q!K$yp$t"E'5TROTO>,oT{y% sN /F'ZPM ^VTZjF&|8QJUXR2f;v% |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'Twas brillig, and the slithy toves | |
Did gyre and gimble in the wabe; | |
All mimsy were the borogoves, | |
And the mome raths outgrabe. | |
"Beware the Jabberwock, my son! | |
The jaws that bite, the claws that catch! | |
Beware the Jubjub bird, and shun | |
The frumious Bandersnatch!" | |
He took his vorpal sword in hand: | |
Long time the manxome foe he sought -- | |
So rested he by the Tumtum tree, | |
And stood awhile in thought | |
And as in uffish thought he stood, | |
The Jabberwock, with eyes of flame, | |
Came whiffling through the tulgey wood, | |
And burbled as it came! | |
One, two! One, two! and through and through | |
The vorpal blade went snicker-snack! | |
He left it dead, and with its head | |
He went galumphing back. | |
"And hast thou slain the Jabberwock? | |
Come to my arms, my beamish boy! | |
O frabjous day! Callooh! Callay!" | |
He chortled in his joy. | |
'Twas brillig, and the slithy toves | |
Did gyre and gimble in the wabe; | |
All mimsy were the borogoves, | |
And the mome raths outgrabe. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment