Editor

Caesar Cipher in Python

Beginner · Text and words · about 40 minutes to build · 102 lines

Encrypt a message by shifting its letters, decrypt it again, or crack a secret message without the key by trying every shift.

The program

Press Run and answer its questions in the box that appears under the code.

caesar_cipher.py
"""Caesar cipher: encrypt, decrypt and crack messages by shifting letters."""
ALPHABET_SIZE = 26
# Short words that show up in almost any English sentence.
COMMON_WORDS = {"the", "and", "is", "to", "of", "a", "in", "it", "you",
"that", "at", "me", "we", "on", "for", "are", "be", "this",
"with", "was", "not", "have", "i"}
# --- The cipher itself ---
def shift_letter(letter, shift):
"""Move one letter along the alphabet, wrapping from Z back to A."""
if letter.isupper():
start = ord("A")
elif letter.islower():
start = ord("a")
else:
return letter # spaces, digits and punctuation stay as they are
position = ord(letter) - start
return chr(start + (position + shift) % ALPHABET_SIZE)
def caesar(text, shift):
"""Shift every letter in the text by the same amount."""
return "".join(shift_letter(letter, shift) for letter in text)
# --- Cracking a message without the key ---
def english_score(text):
"""Count how many words of the text are common English words."""
words = text.lower().split()
return sum(1 for word in words if word.strip(".,;:?'\"") in COMMON_WORDS)
def brute_force(text):
"""Try all 25 shifts, print each, return the most English one (or None)."""
best_shift, best_score = None, 0
for shift in range(1, ALPHABET_SIZE):
attempt = caesar(text, -shift)
print(f" {shift:>2}: {attempt}")
score = english_score(attempt)
if score > best_score:
best_shift, best_score = shift, score
return best_shift
# --- Asking the user ---
def ask_text(prompt):
"""Ask for a message until the user types something with letters in it."""
while True:
text = input(prompt).strip()
if any(char.isalpha() for char in text):
return text
print("Please type a message with at least one letter.")
def ask_shift():
"""Ask for a shift from 1 to 25 until the answer is valid."""
while True:
answer = input("Shift (1-25): ").strip()
try:
shift = int(answer)
except ValueError:
print("The shift must be a whole number, like 3.")
continue
if 1 <= shift < ALPHABET_SIZE:
return shift
print("Pick a number from 1 to 25.")
def main():
"""Show the menu and run the chosen action until the user quits."""
print("Caesar cipher")
while True:
print("\n1) Encrypt 2) Decrypt 3) Crack (try every shift) 4) Quit")
choice = input("Choose 1-4: ").strip()
if choice == "1":
text = ask_text("Message to encrypt: ")
print("Encrypted:", caesar(text, ask_shift()))
elif choice == "2":
text = ask_text("Message to decrypt: ")
print("Decrypted:", caesar(text, -ask_shift()))
elif choice == "3":
text = ask_text("Secret message: ")
best = brute_force(text)
if best is None:
print("No shift gave common English words. Read the list.")
else:
print(f"Most likely shift: {best}, which gives:")
print(" " + caesar(text, -best))
elif choice == "4":
print("Goodbye.")
break
else:
print("Please type 1, 2, 3 or 4.")
if __name__ == "__main__":
main()

Open it in the full editor to save it, share it and keep building.

Sample run

What the program printed, and what was typed (in yellow).

Caesar cipher

1) Encrypt  2) Decrypt  3) Crack (try every shift)  4) Quit
Choose 1-4: 1
Message to encrypt: Meet me at the park at noon
Shift (1-25): 3
Encrypted: Phhw ph dw wkh sdun dw qrrq

1) Encrypt  2) Decrypt  3) Crack (try every shift)  4) Quit
Choose 1-4: 2
Message to decrypt: Wkh fdvh lv forvhg
Shift (1-25): three
The shift must be a whole number, like 3.
Shift (1-25): 3
Decrypted: The case is closed

1) Encrypt  2) Decrypt  3) Crack (try every shift)  4) Quit
Choose 1-4: 3
Secret message: Dro dbokcebo sc exnob dro yvn yku dboo
   1: Cqn canjbdan rb dwmna cqn xum xjt cann
   2: Bpm bzmiaczm qa cvlmz bpm wtl wis bzmm
   3: Aol aylhzbyl pz bukly aol vsk vhr ayll
   4: Znk zxkgyaxk oy atjkx znk urj ugq zxkk
   5: Ymj ywjfxzwj nx zsijw ymj tqi tfp ywjj
   6: Xli xviewyvi mw yrhiv xli sph seo xvii
   7: Wkh wuhdvxuh lv xqghu wkh rog rdn wuhh
   8: Vjg vtgcuwtg ku wpfgt vjg qnf qcm vtgg
   9: Uif usfbtvsf jt voefs uif pme pbl usff
  10: The treasure is under the old oak tree
  11: Sgd sqdzrtqd hr tmcdq sgd nkc nzj sqdd
  12: Rfc rpcyqspc gq slbcp rfc mjb myi rpcc
  13: Qeb qobxprob fp rkabo qeb lia lxh qobb
  14: Pda pnawoqna eo qjzan pda khz kwg pnaa
  15: Ocz omzvnpmz dn piyzm ocz jgy jvf omzz
  16: Nby nlyumoly cm ohxyl nby ifx iue nlyy
  17: Max mkxtlnkx bl ngwxk max hew htd mkxx
  18: Lzw ljwskmjw ak mfvwj lzw gdv gsc ljww
  19: Kyv kivrjliv zj leuvi kyv fcu frb kivv
  20: Jxu jhuqikhu yi kdtuh jxu ebt eqa jhuu
  21: Iwt igtphjgt xh jcstg iwt das dpz igtt
  22: Hvs hfsogifs wg ibrsf hvs czr coy hfss
  23: Gur gernfher vf haqre gur byq bnx gerr
  24: Ftq fdqmegdq ue gzpqd ftq axp amw fdqq
  25: Esp ecpldfcp td fyopc esp zwo zlv ecpp
Most likely shift: 10, which gives:
  The treasure is under the old oak tree

1) Encrypt  2) Decrypt  3) Crack (try every shift)  4) Quit
Choose 1-4: 4
Goodbye.

How it is built

1. Shifting one letter

ord() turns a letter into a number and chr() turns a number back into a letter. Subtracting the code of A (or a) gives a position from 0 to 25, and % ALPHABET_SIZE wraps Z round to A. Anything that is not a letter comes back unchanged.

def shift_letter(letter, shift):
    """Move one letter along the alphabet, wrapping from Z back to A."""
    if letter.isupper():
        start = ord("A")
    elif letter.islower():
        start = ord("a")
    else:
        return letter  # spaces, digits and punctuation stay as they are
    position = ord(letter) - start
    return chr(start + (position + shift) % ALPHABET_SIZE)

2. The whole message

Encrypting and decrypting use the same function: decrypting shifts by the negative amount. "".join() glues the shifted letters back into one string.

def caesar(text, shift):
    """Shift every letter in the text by the same amount."""
    return "".join(shift_letter(letter, shift) for letter in text)

3. Telling English from nonsense

To crack a message without the key, the program needs a way to recognise English. It counts the words found in COMMON_WORDS, after stripping punctuation from each word.

def english_score(text):
    """Count how many words of the text are common English words."""
    words = text.lower().split()
    return sum(1 for word in words if word.strip(".,;:?'\"") in COMMON_WORDS)

4. Trying every shift

A Caesar cipher has only 25 possible keys, so the program tries them all, prints each attempt and keeps the shift with the best score. This is a brute-force attack, and it is why the Caesar cipher cannot protect real secrets.

def brute_force(text):
    """Try all 25 shifts, print each, return the most English one (or None)."""
    best_shift, best_score = None, 0
    for shift in range(1, ALPHABET_SIZE):
        attempt = caesar(text, -shift)
        print(f"  {shift:>2}: {attempt}")
        score = english_score(attempt)
        if score > best_score:
            best_shift, best_score = shift, score
    return best_shift

5. Asking for a shift

int() raises ValueError for text like three, so the except block prints a hint and the loop asks again. The range check turns away 0 and 26, which would leave the message unchanged.

def ask_shift():
    """Ask for a shift from 1 to 25 until the answer is valid."""
    while True:
        answer = input("Shift (1-25): ").strip()
        try:
            shift = int(answer)
        except ValueError:
            print("The shift must be a whole number, like 3.")
            continue
        if 1 <= shift < ALPHABET_SIZE:
            return shift
        print("Pick a number from 1 to 25.")

6. The menu

A while True loop shows the menu and runs the chosen action. Option 4 uses break to leave the loop, which ends the program.

def main():
    """Show the menu and run the chosen action until the user quits."""
    print("Caesar cipher")
    while True:
        print("\n1) Encrypt  2) Decrypt  3) Crack (try every shift)  4) Quit")
        choice = input("Choose 1-4: ").strip()
        if choice == "1":
            text = ask_text("Message to encrypt: ")
            print("Encrypted:", caesar(text, ask_shift()))
        elif choice == "2":
            text = ask_text("Message to decrypt: ")
            print("Decrypted:", caesar(text, -ask_shift()))
        elif choice == "3":
            text = ask_text("Secret message: ")
            best = brute_force(text)
            if best is None:
                print("No shift gave common English words. Read the list.")
            else:
                print(f"Most likely shift: {best}, which gives:")
                print("  " + caesar(text, -best))
        elif choice == "4":
            print("Goodbye.")
            break
        else:
            print("Please type 1, 2, 3 or 4.")

Make it bigger

  • Add a Vigenere cipher, which uses a keyword so each letter gets its own shift.
  • Count letter frequencies and guess the shift from the most common letter, which is usually e in English.
  • Show the three best guesses from the cracker instead of one.
  • Read the message from a text file and save the encrypted version to a new file.

What it practises