CryptoPy: Caesar Cipher aka Shift Cipher in Python

Hello!

Today I would like to show you little crypto script, that helped me solve Nested Easter Egg in WebSec 101: JuiceShop ⭐⭐⭐⭐ challenges 2/3!

It’s very basic python implementation of shift cipher, also known as Caesar Cipher, Polybius cipher or ROT 13 (depends on shifting value), which is primitive form of substitution cipher.

I was intending to start writing about things related to cryptography, so we will begin with “back to the roots”!

History & simple math

History of ciphers is longer than anybody could’ve expected – it began thousands of years ago. First intentional modification of the text to hide some information occurred about 4000 years ago in ancient Egypt. It is believed that ancient Egyptians used unusual symbols to obscure meaning of the inscriptions.

Old Greeks over 2000 years ago used Scytale– ciphering ‘machine’ to produce transposition ciphertext, and to Greeks we owe cryptology term – Cryptography, or cryptology (from Ancient Greek: κρυπτός, romanized: kryptós “hidden, secret”; and γράφειν graphein, “to write”, or -λογία -logia, “study”)

Caesar Cipher is one of most well know ciphers even today – as I mentioned, it was described by Greek writer Polyibus, but first recored use was by Julius Caesar.

“If he had anything confidential to say, he wrote it in cipher, that is, by so changing the order of the letters of the alphabet, that not a word could be made out. If anyone wishes to decipher these, and get at their meaning, he must substitute the fourth letter of the alphabet, namely D, for A, and so with the others.”

Suetonius, Life of Julius Caesar

Basically, to encrypt the message Caesar was substituting the letter in the text by one that is three positions to the right.

Caesar encryption – colored

If we would like to describe it using modern mathematic (or, to be more specific: modular arithmetic), we would write:

E_{n}(x)=(x+n)\mod {26}.

26 is the length of latin alphabet, and n=3 in Caesar version of this shifting cipher.

Decryption is performed similarly, but another way around:

D_{n}(x)=(x-n)\mod {26}.

Another version of this Caesar Cipher is ROT 13 – which rotates (shifts) the letter by 13 instead od 3. It has been reported that ROT13 was used by Netscape Communicator in 1990s to store user’s password.

 It’s worth notice that Caesar(ROT13(x)) = ROT16(x), because Caesar is adding +3, ROT is +13.

Common joke in cryptology community is ROT26 or double ROT 13 – notice, that shifting letter +26 mod 26 is actually turning around… and no shifting! So ROT 13 is involution – that is its own inverse fucntion, both encrypting and decrypting!

Those are very simple Additive/Substitution ciphers, but also must-know backgrounds 😊

Note: The cryptoanalysis of this cipher is trivial – there are some techniques based on frequency analysis, but also brute force requires up to 25 guesses.

Implementation

I’m going to write simple script in python to implement shift cipher function. To ease approach, I’m going to focus on lowercase alphabet only.

import string
alphabet = string.ascii_lowercase # 'abcdefghijklmnopqrstuvwxyz'

I’m want my function to take text (both plaintext or ciphertext), shift value, and mode (encrypt – enc or decrypt – dec)

def shift_cipher(text, shift, mode):

Then I’m going to lower my text, ‘modulo truncate’ shift (if somebody is going to try e.g. shift by 27 instead of 1…) and initialize output:

def shift_cipher(text, shift, mode):
    plain = text.lower()
    shift %= len(alphabet)
    ciphertext = ''

For each letter in text I would like to find corresponding position (for a -> 0, b->1 ,…) add shift and truncate it (modulo) with alphabet length.

To find letter’s position in alphabet I’m going to use find() function, which returns position of a letter if string. If letter cannot be found, it returns -1 value. So I’m going to check if the value is equal to -1 (for all special characters, such as space, slash, etc.) and leave it untouched, the rest I’m going to shift.

    for letter in plain:
        position = alphabet.find(letter)
        
        if position == -1:
            ciphertext += letter
            continue
        
        if mode == 'enc':
            ciphertext += alphabet[(position+shift)%len(alphabet)]
        elif mode == 'dec':
            ciphertext += alphabet[(position-shift)%len(alphabet)

Whole magic function after concatenation:

def shift_cipher(text, shift, mode):
    plain = text.lower()
    shift %= len(alphabet)
    ciphertext = ''
    
    for letter in plain:
        position = alphabet.find(letter)
        
        if position == -1:
            ciphertext += letter
            continue
        
        if mode == 'enc':
            ciphertext += alphabet[(position+shift)%len(alphabet)]
        elif mode == 'dec':
            ciphertext += alphabet[(position-shift)%len(alphabet)]
             
    return ciphertext 

Cracking the egg

We will try with a simple encryption example:

>> print(f'head full of ciphers – 
{shift_cipher("head full of ciphers",3,"enc")}')

head full of ciphers – khdg ixoo ri flskhuv

And decryption:

>> print(f'khdg ixoo ri flskhuv – 
{shift_cipher ("khdg ixoo ri flskhuv",3,"dec")}')

khdg ixoo ri flskhuv – head full of ciphers

Also, ROT13(ROT13(x)) aka ROT26 checked:

>> shift_cipher(shift_cipher('abcd',13,'enc'),13,'enc')
'abcd'

But we have an egg to crack!

In WebSec 101: JuiceShop ⭐⭐⭐⭐ challenges 2/3 I was challenged by Base64 string encoded. Let’s call it

egg = 'L2d1ci9xcmlmL25lci9mYi9zaGFhbC9ndX\
JsL3V2cS9uYS9ybmZncmUvcnR0L2p2Z3V2YS9ndXIvcm5mZ3JlL3J0dA=='

I’m planning to:

  1. assign base64 value to egg variable
  2. decode string with base64 decoder – base64.b64decode()
  3. cast bytes to string (base64 decoder outputs bytes) with str.decode("utf-8")
  4. bruteforce all possibile plaintextes with for loop in range (1,26)

Code is very simple:

egg = 'L2d1ci9xcmlmL25lci9mYi9zaGFhbC9ndX\
JsL3V2cS9uYS9ybmZncmUvcnR0L2p2Z3V2YS9ndXIvcm5mZ3JlL3J0dA==' #step1
egg_decoded = base64.b64decode(egg) #step 2
egg_decoded = egg_decoded.decode("utf-8") #step 3
print(f'step 1 - egg = {egg}')
print(f'step 2+3 - egg base64 decoded = {egg_decoded}')

for i in range(1,26): #step4
    print(f'#{i} - {shift_cipher(egg_decoded,i,"dec")}')

And output solves the puzzle:

step 1 - egg = L2d1ci9xcmlmL25lci9mYi9zaGFhbC9ndXJsL3V2cS9uYS9ybmZncmUvcnR0L2p2Z3V2YS9ndXIvcm5mZ3JlL3J0dA==
step 2+3 - egg base64 decoded = /gur/qrif/ner/fb/shaal/gurl/uvq/na/rnfgre/rtt/jvguva/gur/rnfgre/rtt
#1 - /ftq/pqhe/mdq/ea/rgzzk/ftqk/tup/mz/qmefqd/qss/iuftuz/ftq/qmefqd/qss
#2 - /esp/opgd/lcp/dz/qfyyj/espj/sto/ly/pldepc/prr/htesty/esp/pldepc/prr
#3 - /dro/nofc/kbo/cy/pexxi/droi/rsn/kx/okcdob/oqq/gsdrsx/dro/okcdob/oqq
#4 - /cqn/mneb/jan/bx/odwwh/cqnh/qrm/jw/njbcna/npp/frcqrw/cqn/njbcna/npp
#5 - /bpm/lmda/izm/aw/ncvvg/bpmg/pql/iv/miabmz/moo/eqbpqv/bpm/miabmz/moo
#6 - /aol/klcz/hyl/zv/mbuuf/aolf/opk/hu/lhzaly/lnn/dpaopu/aol/lhzaly/lnn
#7 - /znk/jkby/gxk/yu/latte/znke/noj/gt/kgyzkx/kmm/coznot/znk/kgyzkx/kmm
#8 - /ymj/ijax/fwj/xt/kzssd/ymjd/mni/fs/jfxyjw/jll/bnymns/ymj/jfxyjw/jll
#9 - /xli/hizw/evi/ws/jyrrc/xlic/lmh/er/iewxiv/ikk/amxlmr/xli/iewxiv/ikk
#10 - /wkh/ghyv/duh/vr/ixqqb/wkhb/klg/dq/hdvwhu/hjj/zlwklq/wkh/hdvwhu/hjj
#11 - /vjg/fgxu/ctg/uq/hwppa/vjga/jkf/cp/gcuvgt/gii/ykvjkp/vjg/gcuvgt/gii
#12 - /uif/efwt/bsf/tp/gvooz/uifz/ije/bo/fbtufs/fhh/xjuijo/uif/fbtufs/fhh
#13 - /the/devs/are/so/funny/they/hid/an/easter/egg/within/the/easter/egg
#14 - /sgd/cdur/zqd/rn/etmmx/sgdx/ghc/zm/dzrsdq/dff/vhsghm/sgd/dzrsdq/dff
#15 - /rfc/bctq/ypc/qm/dsllw/rfcw/fgb/yl/cyqrcp/cee/ugrfgl/rfc/cyqrcp/cee
#16 - /qeb/absp/xob/pl/crkkv/qebv/efa/xk/bxpqbo/bdd/tfqefk/qeb/bxpqbo/bdd
#17 - /pda/zaro/wna/ok/bqjju/pdau/dez/wj/awopan/acc/sepdej/pda/awopan/acc
#18 - /ocz/yzqn/vmz/nj/apiit/oczt/cdy/vi/zvnozm/zbb/rdocdi/ocz/zvnozm/zbb
#19 - /nby/xypm/uly/mi/zohhs/nbys/bcx/uh/yumnyl/yaa/qcnbch/nby/yumnyl/yaa
#20 - /max/wxol/tkx/lh/ynggr/maxr/abw/tg/xtlmxk/xzz/pbmabg/max/xtlmxk/xzz
#21 - /lzw/vwnk/sjw/kg/xmffq/lzwq/zav/sf/wsklwj/wyy/oalzaf/lzw/wsklwj/wyy
#22 - /kyv/uvmj/riv/jf/wleep/kyvp/yzu/re/vrjkvi/vxx/nzkyze/kyv/vrjkvi/vxx
#23 - /jxu/tuli/qhu/ie/vkddo/jxuo/xyt/qd/uqijuh/uww/myjxyd/jxu/uqijuh/uww
#24 - /iwt/stkh/pgt/hd/ujccn/iwtn/wxs/pc/tphitg/tvv/lxiwxc/iwt/tphitg/tvv
#25 - /hvs/rsjg/ofs/gc/tibbm/hvsm/vwr/ob/soghsf/suu/kwhvwb/hvs/soghsf/suu

One line looks interesting:

#13 - /the/devs/are/so/funny/they/hid/an/easter/egg/within/the/easter/egg

It’s human readable, and as we know from corresponding article, it also solves the Nested Easter Egg challenge 😉

Conclusion

It is really entertaining to get back from time to time to classical cryptography and solve this kind of puzzles!

If you want to see how to solve mentioned challenge from cover to cover in 33 lines, please see below:

import string
import base64

alphabet = string.ascii_lowercase

def shift_cipher(text, shift, mode):
    plain = text.lower()
    shift %= len(alphabet)
    ciphertext = ''
    
    for letter in plain:
        position = alphabet.find(letter)
        
        if position == -1:
            ciphertext += letter
            continue
        
        if mode == 'enc':
            ciphertext += alphabet[(position+shift)%len(alphabet)]
        elif mode == 'dec':
            ciphertext += alphabet[(position-shift)%len(alphabet)]
             
    return ciphertext 

egg = 'L2d1ci9xcmlmL25lci9mYi9zaGFhbC9ndX\
JsL3V2cS9uYS9ybmZncmUvcnR0L2p2Z3V2YS9ndXIvcm5mZ3JlL3J0dA==' #step1
egg_decoded = base64.b64decode(egg) #step 2
egg_decoded = egg_decoded.decode("utf-8") #step 3
print(f'step 1 - egg = {egg}')
print(f'step 2+3 - egg base64 decoded = {egg_decoded}')

for i in range(1,26):#step4
    print(f'#{i} - {shift_cipher(egg_decoded,i,"dec")}')

I truly believe that this post is another opening for very interesting cycle in cryptology area!

Source code for this project on my Github: HeadFullOfCiphers/CaesarCipher

That it for today 😊 if you liked this post, have any suggestions, questions or just want to contact me – do not hestiate!

Greeting from Greece 🇬🇷 – the cradle of cryptology!

Reference list:

  1. A Brief History of Cryptography – Cypher Research Laboratories
  2. Life of Julius Caesar, Suetonius (Bill Thayer‘s Web Site)
  3. Modern Cryptography: Applied Mathematics for Encryption and Information Security, Chuck Easttom

Check out similiar posts:

2 thoughts on “CryptoPy: Caesar Cipher aka Shift Cipher in Python

Add yours

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: