23

I'm trying to create a simple Caesar Cipher function in Python that shifts letters based on input from the user and creates a final, new string at the end. The only problem is that the final cipher text shows only the last shifted character, not an entire string with all the shifted characters.

Here's my code:

plainText = raw_input("What is your plaintext? ")
shift = int(raw_input("What is your shift? "))

def caesar(plainText, shift): 

    for ch in plainText:
        if ch.isalpha():
            stayInAlphabet = ord(ch) + shift 
            if stayInAlphabet > ord('z'):
                stayInAlphabet -= 26
            finalLetter = chr(stayInAlphabet)
        cipherText = ""
        cipherText += finalLetter

    print "Your ciphertext is: ", cipherText

    return cipherText

caesar(plainText, shift)
2
  • Python 3 Caesar cipher implementation Commented Oct 14, 2014 at 23:00
  • The problem in the original code is an elementary logic error that can be considered a typo because the erroneous code makes no sense when carefully examined - obviously doing cipherText += finalLetter immediately after cipherText = "" produces a result that only has the current finalLetter in it. If people interpreted the question as "how do I implement a cipher?" then the code should be removed and the question should be refocused with specific requirements. Commented Jul 28, 2023 at 5:32

29 Answers 29

62

I realize that this answer doesn't really answer your question, but I think it's helpful anyway. Here's an alternative way to implementing the caesar cipher with string methods:

def caesar(plaintext, shift):
    alphabet = string.ascii_lowercase
    shifted_alphabet = alphabet[shift:] + alphabet[:shift]
    table = string.maketrans(alphabet, shifted_alphabet)
    return plaintext.translate(table)

In fact, since string methods are implemented in C, we will see an increase in performance with this version. This is what I would consider the 'pythonic' way of doing this.

Sign up to request clarification or add additional context in comments.

2 Comments

The solution above works in Python 2 but in Python 3 use str.maketrans() instead of string.maketrans().
Improved version of this code for the case of several alphabets (lowercase, uppercase, digits, etc.): stackoverflow.com/a/54590077/7851470
21

You need to move cipherText = "" before the start of the for loop. You're resetting it each time through the loop.

def caesar(plainText, shift): 
  cipherText = ""
  for ch in plainText:
    if ch.isalpha():
      stayInAlphabet = ord(ch) + shift 
      if stayInAlphabet > ord('z'):
        stayInAlphabet -= 26
      finalLetter = chr(stayInAlphabet)
      cipherText += finalLetter
  print "Your ciphertext is: ", cipherText
  return cipherText

Comments

11

This is an improved version of the code in the answer of @amillerrhodes that works with different alphabets, not just lowercase:

def caesar(text, step, alphabets):

    def shift(alphabet):
        return alphabet[step:] + alphabet[:step]

    shifted_alphabets = tuple(map(shift, alphabets))
    joined_aphabets = ''.join(alphabets)
    joined_shifted_alphabets = ''.join(shifted_alphabets)
    table = str.maketrans(joined_aphabets, joined_shifted_alphabets)
    return text.translate(table)

Example of usage:

>>> import string
>>> alphabets = (string.ascii_lowercase, string.ascii_uppercase, string.digits)
>>> caesar('Abc-xyZ.012:789?жñç', step=4, alphabets=alphabets)
'Efg-bcD.456:123?жñç'

References:
Docs on str.maketrans.
Docs on str.translate.
Docs on the string library

Comments

5

Using some ascii number tricks:

# See http://ascii.cl/
upper = {ascii:chr(ascii) for ascii in range(65,91)}
lower = {ascii:chr(ascii) for ascii in range(97,123)}
digit = {ascii:chr(ascii) for ascii in range(48,58)}


def ceasar(s, k):
    for c in s:
        o = ord(c)
        # Do not change symbols and digits
        if (o not in upper and o not in lower) or o in digit:
            yield o
        else:
            # If it's in the upper case and
            # that the rotation is within the uppercase
            if o in upper and o + k % 26 in upper:
                yield o + k % 26
            # If it's in the lower case and
            # that the rotation is within the lowercase
            elif o in lower and o + k % 26 in lower:
                yield o + k % 26
            # Otherwise move back 26 spaces after rotation.
            else: # alphabet.
                yield o + k % 26 -26

x = (''.join(map(chr, ceasar(s, k))))
print (x)

1 Comment

Here s is the encrypted string and k is the shift in integer.
3

The problem is that you set cipherText to empty string at every cycle iteration, the line

cipherText = ""

must be moved before the loop.

Comments

3

As pointed by others, you were resetting the cipherText in the iteration of the for loop. Placing cipherText before the start of the for loop will solve your problem.

Additionally, there is an alternate approach to solving this problem using Python's Standard library. The Python Standard Library defines a function maketrans() and a method translate that operates on strings.

The function maketrans() creates translation tables that can be used with the translate method to change one set of characters to another more efficiently. (Quoted from The Python Standard Library by Example).

import string

def caesar(plaintext, shift): 

shift %= 26 # Values greater than 26 will wrap around

alphabet_lower = string.ascii_lowercase
alphabet_upper = string.ascii_uppercase

shifted_alphabet_lower = alphabet_lower[shift:] + alphabet_lower[:shift]
shifted_alphabet_upper = alphabet_upper[shift:] + alphabet_upper[:shift]

alphabet = alphabet_lower + alphabet_upper 
shifted_alphabet = shifted_alphabet_lower + shifted_alphabet_upper

table = string.maketrans(alphabet, shifted_alphabet) 

return plaintext.translate(table)

Comments

3

Batteries included

while 1:
    phrase = raw_input("Could you please give me a phrase to encrypt?\n")
    if phrase == "" : break
    print "Here it is your phrase, encrypted:"
    print phrase.encode("rot_13")
print "Have a nice afternoon!"

https://docs.python.org/2/library/codecs.html#python-specific-encodings

Python 3 update

The fine docs say

[Now the rot_13] codec provides a text transform: a str to str mapping. It is not supported by str.encode() (which only produces bytes output).

Or, in other words, you have to import encode from the codecs module and use it with the string to be encoded as its first argument

from codecs import decode
...
    print(encode(phrase, 'rot13'))

1 Comment

The rot13 cipher is a specific case of the caesar cipher with shift 13, so this would only work if the user chose a shift of 13.
2

Here, a more functional way: (if you use shift i to encode, then use -i to decode)

def ceasar(story, shift):
  return ''.join([ # concentrate list to string
            (lambda c, is_upper: c.upper() if is_upper else c) # if original char is upper case than convert result to upper case too
                (
                  ("abcdefghijklmnopqrstuvwxyz"*2)[ord(char.lower()) - ord('a') + shift % 26], # rotate char, this is extra easy since Python accepts list indexs below 0
                  char.isupper()
                )
            if char.isalpha() else char # if not in alphabet then don't change it
            for char in story 
        ])

Comments

1
plainText = raw_input("What is your plaintext? ")
shift = int(raw_input("What is your shift? "))

def caesar(plainText, shift): 
    for ch in plainText:
        if ch.isalpha():
            stayInAlphabet = ord(ch) + shift 
            if stayInAlphabet > ord('z'):
                stayInAlphabet -= 26
            finalLetter = chr(stayInAlphabet)
        #####HERE YOU RESET CIPHERTEXT IN EACH ITERATION#####
        cipherText = ""
        cipherText += finalLetter

    print "Your ciphertext is: ", cipherText

    return cipherText

caesar(plainText, shift)

As an else to if ch.isalpha() you can put finalLetter=ch.

You should remove the line: cipherText = ""

Cheers.

Comments

1

As @I82much said, you need to take cipherText = "" outside of your for loop. Place it at the beginning of the function. Also, your program has a bug which will cause it to generate encryption errors when you get capital letters as input. Try:

    if ch.isalpha(): 
        finalLetter = chr((ord(ch.lower()) - 97 + shift) % 26 + 97)

Comments

1
>>> def rotate(txt, key):
...   def cipher(i, low=range(97,123), upper=range(65,91)):
...     if i in low or i in upper:
...       s = 65 if i in upper else 97
...       i = (i - s + key) % 26 + s
...     return chr(i)
...   return ''.join([cipher(ord(s)) for s in txt])

# test
>>> rotate('abc', 2)
'cde'
>>> rotate('xyz', 2)
'zab'
>>> rotate('ab', 26)
'ab'
>>> rotate('Hello, World!', 7)
'Olssv, Dvysk!'

Comments

1

I have a hard time remember the char to int conversions so this could be optimized

def decryptCaesar(encrypted, shift):
    minRange = ord('a')
    decrypted = ""
    for char in encrypted:
        decrypted += chr(((ord(char) - minRange + shift) % 26) + minRange)

    return decrypted

Comments

1
def encrypt():
    plainText = input("What is your plaintext? ")
    shift = int(input("What is your shift? "))
    cipherText = ""
    for ch in plainText:
        if ch.isalpha():
            stayInAlphabet = ord(ch) + shift
        if stayInAlphabet > ord('z'):
            stayInAlphabet -= 26
        finalLetter = chr(stayInAlphabet)
        cipherText += finalLetter

    print ("Your ciphertext is: ", cipherText,"with a shift of",shift)


def decrypte():
    encryption=input("enter in your encrypted code")
    encryption_shift=int(input("enter in your encryption shift"))

    cipherText1 = ""
    for c in encryption:
        if c.isalpha():
            stayInAlphabet1 = ord(c) - encryption_shift
        if stayInAlphabet1 > ord('z'):
            stayInAlphabet1 += 26
        finalLetter1 = chr(stayInAlphabet1)
        cipherText1 += finalLetter1

    print ("Your ciphertext is: ", cipherText1,"with negative shift of",encryption_shift)

from tkinter import *

menu=Tk()
menu.title("menu")
menu.geometry("300x300")
button1= Button(menu,text="encrypt",command=encrypt)
button1.pack()

button2= Button(menu,text="decrypt",command=decrypte)
button2.pack()

button3= Button(menu,text="exit",command=exit)
button3.pack()

menu.mainloop()

Comments

1
message = 'The quick brown fox jumped over the lazy dog. 1234567890 !@#$%^&*()_+-'
encrypted = ''.join(chr(ord(char)+3) for char in message)
decrypted = ''.join(chr(ord(char)-3) for char in encrypted)
print(encrypted)
print(decrypted)
# Wkh#txlfn#eurzq#ir{#mxpshg#ryhu#wkh#od}|#grj1#456789:;<3#$C&'(a)-+,b.0
# The quick brown fox jumped over the lazy dog. 1234567890 !@#$%^&*()_+-

Comments

1
def encrypt(text,shift):
    '''
    INPUT: text as a string and an integer for the shift value.
    OUTPUT: The shifted text after being run through the Caeser cipher.
    '''

    # Create a placeholder list
    encrypted_text = list(range(len(text)))

    alphabet = string.ascii_lowercase

    # Create shifted alphabet
    first_half = alphabet[:shift]
    second_half = alphabet[shift:]
    shifted_alphabet = second_half+first_half

    for i,letter in enumerate(text.lower()):

        # Check for spaces or punctuation
        if letter in alphabet:
            # Find the original index position
            original_index = alphabet.index(letter)

            # Shifted letter
            new_letter = shifted_alphabet[original_index]

            encrypted_text[i] = new_letter

        # Punctuation or space
        else:
            encrypted_text[i] = letter

    return ''.join(encrypted_text)

2 Comments

Code dumps without any explanation are rarely helpful. Stack Overflow is about learning, not providing snippets to blindly copy and paste. Please edit your question and explain how it works better than what the OP provided.
You should also relate to the code provided by the asker of the question. It looks like their code is very close to working (even if being a bit un-elegant in places), so relating to it would strengthen learning.
1

For example, decod string:

"uo jxuhu! jxyi yi qd unqcfbu ev q squiqh syfxuh. muhu oek qrbu je tusetu yj? y xefu ie! iudt cu q cuiiqwu rqsa myjx jxu iqcu evviuj!".

This message has an offset of 10.

Code below:

import string
alphabet = list(string.ascii_lowercase)
print(alphabet, len(alphabet))
messege = "xuo jxuhu! jxyi yi qd unqcfbu ev q squiqh syfxuh. muhu oek qrbu je tusetu yj? y xefu ie! iudt cu q cuiiqwu rqsa myjx jxu iqcu evviuj!"
messege_split = messege.split()
print(messege_split)

encrypted_messege = ""
position = 0
for i in messege_split:
    for j in i:
        if ord(j) < 65:
            encrypted_messege += j
        else:         
            for k in alphabet:
                if j == k:
                    position = alphabet.index(k)
                    if (position + 10) >= len(alphabet):
                        encrypted_messege += alphabet[abs((position + 10) - len(alphabet))]
                    else:
                        encrypted_messege += alphabet[position + 10]
    encrypted_messege += " "

print(encrypted_messege)

Decoded string:

"hey there! this is an example of a caesar cipher. were you able to decode it? i hope so! send me a message back with the same offset!"

TRY IT!

Comments

1

Using cyclic generator:

import string
from itertools import cycle


def caesarCipherEncryptor(s, key):
    def generate_letters():
        yield from cycle(string.ascii_lowercase)
    
    def find_next(v, g, c):
        # Eat up characters until we arrive at the plaintext character
        while True:
            if v == next(g):
                break
        
        # Increment the plaintext character by the count using the generator
        try:
            for _ in range(c):
                item = next(g)
            return item
        except UnboundLocalError:
            return v

    return "".join([find_next(i, generate_letters(), key) for i in s])

# Outputs
>>> caesarCipherEncryptor("xyz", 3)
>>> 'abc'

2 Comments

This is a really nice approach which is configurable and avoids mod arithmetic. Very pythonic, thanks!
One downside, though, is that it doesn't allow character decrementing. To reverse the operation you would have to increment by the complement of the initial encryption.
0
from string import ascii_lowercase as alphabet

class CaesarCypher:
    alpha_len = len(alphabet)
    min_guess_rate = 0.2

Encryption and decryption is a same stuff. when you want to decrypt for example with shift 10 that means that you can encrypt it with shift 26 - 10. In this case cycle will repeat at if you going to shift whole alphabet it will be the same. Also here i've proceed upper case and non chars

    def __call__(self, text, offset, encrypt=True):
        if not encrypt:
            offset = self.alpha_len - offset
        result = []
        for letter in text:
            if not letter.isalpha():
                result.append(letter)
                continue
            letter_to_process = letter.lower()
            processed_letter = self._encrypt_letter(letter_to_process, offset)
            if letter.isupper():
                processed_letter = processed_letter.upper()
            result.append(processed_letter)
        return ''.join(result)

all encryption goes here at most.

    def _encrypt_letter(self, letter, offset=0):
        position = (alphabet.find(letter) + offset) % self.alpha_len
        return alphabet[position]

this part is for broot force and guess throug dictionary frequency.

    @staticmethod
    def __how_many_do_i_know(text):
        clean_words = filter(lambda x: x.isalpha(), text.split())
        clean_words = ['\'{}\''.format(x) for x in clean_words]
        cursor = conn.cursor()
        query = 'SELECT COUNT(*) FROM mydictionary WHERE word IN ({})'.format(",".join(clean_words))
        cursor.execute(query)
        response = cursor.fetchone()[0]
        return response / len(clean_words)

    def guess_encode(self, text):
        options = [self(text, offset, encrypt=False) for offset in range(self.alpha_len)]
        best_option = [self.__how_many_do_i_know(option) for option in options]
        best_key, guess_rate = max(enumerate(best_option), key=lambda x: x[-1])
        guess_text = options[best_key]
        return best_key, guess_rate, guess_text

1 Comment

Are you sure you are answering the specific question asked by the OP? He wasn't asking for a way to rewrite the code, he just wanted some help at finding the bug.
0
import string
wrd=raw_input("Enter word").lower()
fwrd=""
for let in wrd:
    fwrd+=string.ascii_lowercase[(string.ascii_lowercase).index(let)+3]
print"Original word",wrd
print"New word",fwrd

1 Comment

Please use the edit link to explain how this code works and don't just give the code, as an explanation is more likely to help future readers. See also How to Answer. source
0

according to me this answer is useful for you:

def casear(a,key):
str=""
if key>26:
    key%=26
for i in range(0,len(a)):
    if a[i].isalpha():
        b=ord(a[i])
        b+=key
        #if b>90:                   #if upper case letter ppear in your string
        #    c=b-90                 #if upper case letter ppear in your string
        #    str+=chr(64+c)         #if upper case letter ppear in your string
        if b>122:
            c=b-122
            str+=chr(96+c)
        else:
            str+=chr(b)
    else:
        str+=a[i]
print str

a=raw_input()
key=int(input())
casear(a,key)

This function shifts all letter to right according to given key.

Comments

0

Why not use the function reverse on the shift input, and and join the plain_text with the shift, and input it as the cipher text:

Plain = int(input("enter a number ")) 
Rev = plain[::-1]
Cipher = " ".join(for cipher_text in Rev) 

Comments

0

The code is very large, but easy to understand. I think it fits your situation.

alphabet = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
class CaesarCipher(object):    
    def __init__(self, shift):
        self.shift = shift
    def encode(self, str):
        encode = ''
        str = str.lower()
        for i in str:
            if i in alphabet:
                encode += alphabet[alphabet.index(i) + self.shift]
            else:
                encode += i
    
        return encode.upper()


    def decode(self, str):
        decode = ''
        str = str.lower()
        for i in str:
            if i in alphabet:
                decode += alphabet[alphabet.index(i) - self.shift]
            else:
                decode += i
        return decode.upper()
    

Comments

0

Using map:

def caesar(text, key):
    return ''.join(map(lambda c: 
        chr((ord(c.lower()) - ord('a') + key) % 26 + ord('a')) if c.isalpha() else ''
    , text))

Comments

0

This solution is more intuitively without the use of ord function:

def caesar_cipher(raw_text, key):
    alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    shifted_alphabet = alphabet[26-key:]+alphabet[0:(26-key)]
    cipher_text = ""
    for i in range(len(raw_text)):
        char = raw_text[i]
        idx = alphabet.find(char.upper())
        if idx == -1:
            cipher_text = cipher_text + char
        elif char.islower():
            cipher_text = cipher_text + shifted_alphabet[idx].lower()
        else:
            cipher_text = cipher_text + shifted_alphabet[idx] 
    return(cipher_text)

And an example:

plain_text = "The quick brown fox jumps over the lazy dog!"
caesar_cipher(plain_text,3)

And we get:

'Qeb nrfzh yoltk clu grjmp lsbo qeb ixwv ald!'

If we want to decrypt it:

caesar_cipher(caesar_cipher(plain_text,3),26-3)

and we get:

'The quick brown fox jumps over the lazy dog!'

More details here:https://predictivehacks.com/caesar-cipher-in-python/

Comments

0

caesar-cipher

message = str(input("Enter you message:"))
shift = int(input("Enter a number:"))
# encode

stringValue = [ord(message) - 96 for message in message]
print(stringValue)
encode_msg_val = []


[encode_msg_val.append(int(stringValue[i])+shift) for i in 
range(len(stringValue))]

encode_msg_array = []
for i in range(len(encode_msg_val)):
    encode_val = encode_msg_val[i] + 96
    encode_msg_array.append(chr(encode_val))

print(encode_msg_array)
encode_msg = ''.join(encode_msg_array)


# dedcode

[deocde_msg_val = [ord(encode_msg) - 96 for encode_msg in encode_msg]

decode_val = []
[decode_val.append(deocde_msg_val[i] - shift) for i in 
range(len(deocde_msg_val))]

decode_msg_array = []
[decode_msg_array.append(decode_val[i] + 96) for i in range(len(decode_val))]

decode_msg_list = []
[decode_msg_list.append(chr(decode_msg_array[i])) for i in 
range(len(decode_msg_array))]

decode_msg = ''.join(decode_msg_list)
print(decode_msg)

Comments

0
alph = 'abcdefghijklmnopqrstuvwxyz'

# shift = int(input("Please enter the number of places to shift:"))
shift = 15
text = "python is fun!"
alph_len = len(alph)

if shift >=0 and shift <= alph_len:
#     text = input("Please enter a sentence:")
    shifted_alph = alph[shift:] + alph[:shift] # rotate
    text = text.lower()

    crypted_text = ""

    for letter in text:
        if letter in alph:
            ind = alph.index(letter)
            crypted_letter = shifted_alph[ind]
        else:
            crypted_letter = letter

        crypted_text += crypted_letter

    print(crypted_text)
else:
    print(f"You need to enter a number between 0 and {alph_len}!")

# eniwdc xh ujc! # output

Comments

0

Here is a recursive one. It does not change symbols.

def caesar(t,s=12,crypt=True):
    if not t: return ""
    r = 1 if crypt else -1
    tx = t[0] 
    if tx.isalpha():
        A = ord('a') if tx.islower() else ord('A')
        tx = chr((ord(tx) - A + (s*r)) % 26 + A)  
    elif tx.isnumeric(): 
        A = ord('0')
        tx = chr((ord(tx) - A + (s*r)) % 10 + A)   
    return tx + caesar(t[1:],s,crypt) 

off = 17
T = input("Enter text> ") 

C = caesar(T,off) 
print(f"Caesar cipher is: '{C}'") 

D = caesar(C,off,crypt=False)
print(f"Decrypted  '{D}'") 

$ python caeser.py 
Enter text> 234
Caesar cipher is: '901'
Decrypted  '234'

$ python caeser.py 
Enter text> Hello World!
Caesar cipher is: 'Yvccf Nficu!'
Decrypted  'Hello World!'

Comments

-1
key = 3

def wub():
    def choice():
        choice = input("Do you wish to Encrypt of Decrypt?")
        choice = choice.lower()
        if choice == "e" or "encrypt":
            return choice
        elif choice == "d" or "decrypt":
            return choice
        else:
            print("Invalid response, please try again.")
            choice()

    def message():
        user = input("Enter your message: ")
        return user

    def waffle(choice, message, key):
        translated = ""
        if choice == "e" or "encrypt":
            for character in message:
                num = ord(character)
                num += key
                translated += chr(num)

                derek = open('Encrypted.txt', 'w')
                derek.write(translated)
            derek.close()
            return translated
        else:
            for character in message:
                num = ord(character)
                num -= key
                translated += chr(num)
            return translated

    choice = choice() #Runs function for encrypt/decrypt selection. Saves choice made.
    message = message() #Run function for user to enter message. Saves message.
    final = waffle(choice, message, key) #Runs function to translate message, using the choice, message and key variables)
    print("\n Operation complete!")
    print(final)

wub()

Comments

-1
alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
def caesar(original_text, shift_amount, encode_or_decode):
    output_text = ""
    if encode_or_decode == "decode":
        shift_amount *= -1
    for letter in original_text:
        if letter not in alphabet:
            output_text += letter
        else:
            shifted_position = alphabet.index(letter) + shift_amount
            shifted_position %= len(alphabet)
            output_text += alphabet[shifted_position]
    print(f"Here is the {encode_or_decode}d result: {output_text}")


should_continue = True
while should_continue:

    direction = input("Type 'encode' to encrypt, type 'decode' to decrypt:\n").lower()
    text = input("Type your message:\n").lower()
    shift = int(input("Type the shift number:\n"))
    print(f"You have shifted {shift} in {direction}d direction.")
    caesar(original_text=text, shift_amount=shift, encode_or_decode=direction)
    restart = input("Type 'yes' if you want to go again. Otherwise, type 'no'.\n").lower()
    if restart == "no":
        should_continue = False
        print("Thanks for playing!")

1 Comment

I'm not sure this is answer useful among all the other answers that exist here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.