Modified Vigenere Cipher in python - Alphabet - python

This is what I have to do:
Write a script in Python that is an implementation of a version of the Vigenere cipher for English text. Your script should distinguish lowercase and uppercase letters (i.e., the encryption key and the plaintext are allowed to be composed of lowercase and uppercase letters but the ciphertext should be uppercase). In addition to letters, there will be four other characters in the plain text: comma (26), dot (27), dash (28), underscore (29) changing the encryption function to be under mod 30.
Your script should read from standard input and write to standard output. It should prompt the user for the encryption key of size k. The key is not allowed to be repeated as in the standard Vigenere cipher. Instead, we will follow a block cipher-based idea. Basically, the plaintext and ciphertext will have blocks of size k which is same as the key size. If the key length is shorter than the plaintext, the ciphertext of block size k of the previous block is concatenated to the key. Here is an example when the keyword is ”Carbondale” with k = 10:
Plaintext : SIU_CS-Department_is_the_best
Key : CarbondaleUIHAQBBDPTUZ,MUOUCX
Ciphertext: UIHAQBBDPTUZ,MUOUCXHTODQTPYUM
So, I want to know how to tackle the part of the extra characters "," "." "/" "_". This is the function that is making the encryption:
a = len(key)
b = len(text)
while (len(key1) <= len(text1)):
for i in range(0,a):
num1 = ord(text1[i+var])%97
num2 = ord(key1[i+var])%97
num3 = num1+num2
if (num3 > 25):
encr.append(num3%25)
else:
encr.append(num3)
i + 1
for i in range(0,a):
encr1.append(chr(encr[i+var]+97))
i + 1
for i in range(0,a):
key1.append(encr1[i+var])
i + 1
var = var + a

You code currently has the following problems (I am assuming that var = 0, encr = [], encr1 = [], key1 = list(key) and text1 = list(text) happen before the code you have posted):
Your while loop will never start if the key is longer than the plaintext, and never end otherwise, as you never shorten text1 (and doing so would break your indexing);
You don't need to manually increment the counter in a for loop (and if you wanted to, i + 1 without assignment effectively does nothing, you need i += 1);
When using mod (%), you don't need to check if e.g. num3 < 25; and
If you get around to including them, note that the extra characters you list aren't the same as those specified ("/" != "-").
Rather than using ord and chr, I would build my own alphabet to cycle over, e.g.
from string import ascii_uppercase
alphabet = list(ascii_uppercase) + [",", ".", "-", "_"]
You might find this implementation of the "standard" Vigenère helpful:
from itertools import cycle
def vigenere(plain, key, alphabet):
for p, k in zip(plain, cycle(key)):
index = alphabet.index(p) + alphabet.index(k)
yield alphabet[index % len(alphabet)]
Here is some pseudo code for your modified implementation:
convert all text (plain, key) to upper case
create a list to hold output
split plain into blocks of length len(key)
for each block in plain:
if it's the first block, use key to encode
otherwise, use the last block of encoded text in output

Related

How do you split duplicate letters with "x" in Python?

I am trying to create a function which is able to detect when two letters back to back are duplicates, for example the ls in "hello", and split the duplicate letters with the letter "x". Here is my code:
plaintext = input("Enter plaintext here: ")
plaintext = plaintext.lower() # makes plaintext lowercase
plaintext = plaintext.replace(" ", "") # removes all spaces
# this separates all duplicate letters
i = 0 # sets i to 0
for letter in plaintext:
if plaintext[-1] == plaintext[-2]: # if the last letter is the same as the second to last
plaintext = plaintext[:-1] + "x" + plaintext[-1:] # separate them with an x
elif plaintext[i] == plaintext [i+1]: # if one letter is the same as the next letter
# the line above makes an error
plaintext = plaintext[:i+1] + "x" + plaintext[i+1:] #separate them with an x
i += 1
else:
i += 1
This code works when I enter hello there as the input; I receive helxlothere. However, when I test another input, such as heythere, IndexError: string index out of range shows up for elif line (line 12). How can I make this code work for all inputs?
You can use regex to achieve this.
For both approaches, it will work for hello there hey there
The difference comes when more than two character repetition happens.
approach 1
import re
string='hello there'
# find Any repeated character and add x in between them
answer = re.sub(r'(.)\1{1}', r'\1x\1', string)
print(answer)
Here for hellllo there text, you will get output helxllxlo there
approach 2
alternatively, you can use this method.
s="hello there"
for match in re.finditer(r"(.)\1+", s):
old=s[match.start():match.end()]
s=s.replace(old,'x'.join(old))
print(s)
here for hellllo there text, you will get output helxlxlxlo there as output.
I think the second approach will be more appropriate.
The IndexError is caused by the fact that you are looking at plaintext[i+1]. As you can see in the word heythere, there are no letters which match back to back, and therefore the code continues until it hits the end, and so you get an IndexError because there is no element i+1.
You can fix this by using this code instead:
plaintext = input("Enter plaintext here: ")
plaintext = plaintext.lower() # makes plaintext lowercase
plaintext = plaintext.replace(" ", "") # removes all spaces
# this separates all duplicate letters
i = 0 # sets i to 0
for letter in plaintext:
if plaintext[-1] == plaintext[-2]: # if the last letter is the same as the second to last
plaintext = plaintext[:-1] + "x" + plaintext[-1:] # separate them with an x
try:
elif plaintext[i] == plaintext [i+1]: # if one letter is the same as the next letter
# the line above makes an error
plaintext = plaintext[:i+1] + "x" + plaintext[i+1:] #separate them with an x
i += 1
else:
i += 1
except IndexError:
pass
This code should stop your code from crashing in the elif statement, while also completing properly.
Hope this helps, have a nice day!
You are receiving the IndexError because during iterating, when the loop reaches the last letter,
elif plaintext[i] == plaintext [i+1]:
this line checks for the letter after the last letter, which does not exist, which causes the program to run into IndexError.
You have to check till the second last letter, for it to work properly.
A simple and easier way to arrive at the same output with a bit easier logic.
Logic
Create a new string and insert all letters which are in the old string, plaintext, to the new string, newPlainText, and check only for one condition that is whether the last letter is same as current or not and if yes then also insert letter 'x' into the newPlainText and that's it!
plaintext = input("Enter plaintext here: ")
plaintext = plaintext.lower() # makes plaintext lowercase
plaintext = plaintext.replace(" ", "") # removes all spaces
# this separates all duplicate letters
newPlainText = plaintext[0] # copy the first letter of plaintext to the new string which will handle the duplicates and insert `x` between them
i = 1 # sets i to 1
while i < len(plaintext):
if plaintext[i] == plaintext[i-1]: # we check that if prev and current letters are same then also append x to the new string
newPlainText += "x"
newPlainText += plaintext[i] # we insert the required current letter to the new string in each iteration
i += 1
print(newPlainText)

python string.split() and loops

disclaimer im new to python
i need to split a string input send if to a function that substitutes a character in the string with a different character (like a substitution cipher) but i just dont know how to go about this
print('Welcome to the encryption protocol for top secret governemt cover ups')
string=input('whats your message?')
def encrypt(string):
alpha = "abcdefghijklmnopqrstuvwyz"
sub_alpha = "pokmenliuytrwqazxcvsdfgbhn"
index=0
while index < len(string):
letter=string[index]
im not really sure what im doing im really bad at python, this has had me stumped for 3 days now ive reviewed my course material and tried videos on youtube im probably just really really dumb
I think the key piece of knowledge you're missing is that strings are iterable. So you can do things like:
for c in "FOO":
print(c)
# prints "F\nO\nO\n"
And you can find the index of a character within a string with str.index. So you can build up your cyphertext like this:
alpha = "abcdefghijklmnopqrstuvwyz "
cypher = "pokmenliuytrw qazxcvsdfgbhn"
plaintext = "some string"
cyphertext = ""
for c in plaintext:
char_index = alpha.index(c)
cyphertext += cypher[char_index]
You can also iterate over things inline - this is called a comprehension. So to transform your string you can do this instead of using the for loop:
cyphertext = "".join(cypher[alpha.index(c)] for c in plaintext)
The example above uses the str.join function to concatenate each character of cyphertext.
Here is a solution that asks the question and then iterates through each letter, finding the index in the alpha key, and replacing it with the sub_alpha key equivalent.
Note this example also checks if it should be lowercase or uppercase.
EDIT: if the input character does not have a valid cipher, it doesn't get altered.
EDIT 2: expanded answer to convert both forwards and backwards.
alpha = "abcdefghijklmnopqrstuvwyz"
sub_alpha = "pokmenliuytrwqazxcvsdfgbhn"
def encrypt(in_char):
is_lower_case = in_char.islower()
index = alpha.find(in_char.lower())
if index < 0:
return in_char
elif is_lower_case:
return sub_alpha[index]
else:
return sub_alpha[index].upper()
def decrypt(in_char):
is_lower_case = in_char.islower()
index = sub_alpha.find(in_char.lower())
if index < 0:
return in_char
elif is_lower_case:
return alpha[index]
else:
return alpha[index].upper()
print('Welcome to the encryption protocol for top secret governemt cover ups')
input_str=input('whats your message? ')
output_str=""
for letter in input_str:
output_str += encrypt(letter)
print("Encrypted: ")
print(output_str)
input_str=""
for letter in output_str:
input_str+= decrypt(letter)
print("Decrypted: ")
print(input_str)

Vigenere cipher in Python not working for uppercase/lowercase letter conversion

As part of the CS50 Harvard programming course, which I'm currently attending, I'm doing an exercise called "Vigenere".
The Cipher of Vigenere is supposed to have a key as input, for example "abcd". This will encode any plaintext, according to each character in the key, where a = 0 and b = 1 etc. So a key "abcd" with plaintext "aa!aa" would give "ab!cd". if the plaintext is longer than the key, the key is supposed to loop back to [0] and start over until plaintext has all been encoded. Non alphabetic letters are supposed to printed out normally however.
My program is doing everything right (it's going line by line and the expected behavior is met) except when I receive input that starts with an uppercase letter followed by a lowercase letter my program prints a different letter with the lowercase key then it should give me. EX: key: "Baz". Plaintext: "aaa". Result: "bgz" where it should return "baz".
Have been googling, debugging but just can't figure it out. Have tried doing it in a lot of other different ways too but I just can't get it to work. (sorry for kind of copy paste, as you might notice I've already posted a similar problem, however that was in C (this is python) and it was a different kind of bug)
Code:
import sys
if len(sys.argv) != 2 or not sys.argv[1].isalpha():
print("usage: python vigenere.py keyword")
sys.exit()
cipher = sys.argv[1]
plaintext = input("plaintext: ")
j = 0
def code(j):
for key in cipher:
if key.islower():
return ord(cipher[j]) - 97
if key.isupper():
return ord(cipher[j]) - 65
print("ciphertext: ", end="")
for letters in plaintext:
if letters.islower():
print(chr(((ord(letters) - 97 + code(j)) % 26) + 97), end="")
j += 1
if letters.isupper():
print(chr(((ord(letters) - 65 + code(j)) % 26) + 65), end="")
j += 1
if j == len(cipher):
j = 0
if not letters.isalpha():
print(letters, end="")
print("")
The problem in your code is caused by your code function.
In it, you use the line for key in cipher:, before checking it with if key.islower(): or if key.isupper():.
The problem is that every time we enter the code function, due to the for loop we only check if the first letter in the cipher is upper or lower case.
eg. for cipher 'Baz', j = 0, we check if B is upper/lower, and get upper. We immediately return uppercase B
For cipher 'Baz', j = 1, we check if B is upper/lower, and get upper. We immediately return upper A. (When we should be checking a for upper/lower, and returning lower a!)
This problem is fixed by checking the right letter of the cipher for upper or lower case, and can be fixed by replacing for key in cipher: with key = cipher[j], like in the block below:
def code(j):
key = cipher[j]
if key.islower():
return ord(key) - 97
if key.isupper():
return ord(key) - 65
After making the change, for the following inputs:
cipher = "BazBgh"
plaintext = "aaaaAa"
We get
ciphertext: bazbGh

Anyway to shorten this Python Caesar Cipher code into fewer lines?

def encrypt(text, key, direction):
if direction == 1: #The direction is either -1, or 1. If it is 1, it goes right. Otherwise, it will go left.
emptys=''
for x in text:
b = ord(x) #b is the individual characters after the ord() function
b+=key
if b<=122:
n = chr(b) #n is the converted letter of the encrypted ASCII number
emptys+=n
else:
o=b-90
q=chr(o)
emptys+=q
return emptys
else:
emptys=''
for x in text:
b = ord(x) #b is the individual characters after the ord() function
b=b-key
if b>=32:
n = chr(b) #n is the converted letter of the encrypted ASCII number
emptys+=n
else:
o=b+90
q=chr(o)
emptys+=q
return emptys
Your code as written blithely translates alphabetic characters to non-alphabetic and vice versa (e.g. encrypt('abc', 25, 1) gets 'z!"'). So it's wrong by most definitions of the traditional Caesar cipher, which should only modify alphabetic characters, and should rotate them within the alphabet.
That said, getting it right is easier than you're making it. The best approach is to avoiding rolling your own rotation code. Python already provides a really nice way to do one-to-one character mappings, str.translate. For example:
from string import ascii_letters, ascii_uppercase, ascii_lowercase
def encrypt(text, key, direction):
# direction == -1 is trivially converted to direction == 1 case
if direction == -1:
key = 26 - key
# On Py2, you'd use the string module's string.maketrans instead of
# str.maketrans
trans = str.maketrans(ascii_letters, ascii_lowercase[key:] + ascii_lowercase[:key] + ascii_uppercase[key:] + ascii_uppercase[:key])
return text.translate(trans)

Repeating a word to match the length of a string [duplicate]

This question already has answers here:
Repeat string to certain length
(15 answers)
Closed 5 months ago.
In school we are currently using python to create a Caeser Cipher, and a Keyword Cipher. I need help with certain parts of the Keyword cipher, mainly repeating a word to match the length of a string that has been entered, For example:
Entered String: Hello I am Jacob Key: bye Printed text: byebyebyebyeb
I'm okay at Python, but this is very hard for me. Currently this is as far as I got:
def repeat_to_length(key, Input):
return (key * ((ord(Input)/len(key))+1))[:ord(Input)]
Since It's a string I thought if I used ord It would turn it to a number, but I realised when using the ord command you can only have a single character, as I realised when I repeatedly got this error:
TypeError: ord() expected a character, but string of length 16 found
I found some code that did a keyword cipher, but I am not sure which part does the process I am trying to code:
def createVigenereSquare():
start = ord('a')
end = start + 26
index = start
sq = [''] * 256
for i in range(start, end):
row = [''] * 256
for j in range(start, end):
if index > (end - 1):
index = start
row[j] = chr(index)
index += 1
sq[i] = row
index = i + 1
return sq
def createKey(message, keyword):
n = 0
key = ""
for i in range(0, len(message)):
if n >= len(keyword):
n = 0
key += keyword[n]
n += 1
return key
def createCipherText(message, key):
vsquare = createVigenereSquare()
cipher = ""
for i in range(0, len(key)):
cipher += vsquare[ord(key[i])][ord(message[i])]
return cipher
message = str(input("Please input a message using lowercase letters: "))
keyword = str(input("Please input a single word with lowercase letters: "))
key = createKey(message, keyword)
ciphertext = createCipherText(message, key)
print ("Message: " + message)
print ("Keyword: " + keyword)
print ("Key: " + key)
print ("Ciphertext: " + ciphertext)
As I've said I'm only okay at Python, so I don't really understand all the code in the above code, and I really want to be able to write most of it myself.
This is my code so far:
def getMode():
while True:
print("Enter encrypt, e, decrypt or d")
mode = input('Do you want to encrypt the message or decrypt? ') #changes whether the code encrypts or decrypts message
if mode in 'encrypt e decrypt d ENCRYPT E DECRYPT D'.split(): #defines the various inputs that are valid
return mode
else:
print('Enter either "encrypt" or "e" or "decrypt" or "d".') #restarts this string of code if the input the user enters is invalid
Input = input('Enter your message: ')
key = input('Enter the one word key: ')
def repeat_to_length(key, Input):
return (key * ((ord(Input)/len(key))+1))[:ord(Input)]
encryptedKey = repeat_to_length(key, Input)
print(encryptedKey)
I know I've been pretty long winded but if anyone could provide any information on this topic, like explaining the code for the keyword cipher, or just answering my question, I would appreciate it!
AnimeDeamon
Another possibility is to repeat the key enough times to get a string at least as long as the message, and then just grab as many characters as you need from the beginning:
>>> message='Hello I am Jacob'
>>> key='bye'
>>> times=len(message)//len(key)+1
>>> print((times*key)[:len(message)])
byebyebyebyebyeb
We compute times by dividing the length of the message by the length of the string, but we have to add 1, because any remainder will be dropped. times*key is just key repeated times times. This may be longer than we want, so we just take the first len(message) characters.
A simple one-liner could be
''.join(key[i % len(key)] for i in range(len(message)))
What it does, inside out:
We iterate over the indices of the letters in the string message
For each index, we take the remainder after division with the key length (using the % operator) and get the corresponding letter from the key
A list of these letters is constructed (using list comprehension) and joined together to form a string (the join method of an empty string)
Example:
>>> message = "Hello I am Jacob"
>>> key = "bye"
>>> print ''.join(key[i % len(key)] for i in range(len(message)))
byebyebyebyebyeb

Categories

Resources