itertools cycle in vigenere cipher causing problems with spaces python - python

In my code for vigenere cipher I use cycle from itertools to cycle through the key word. This works great until I use spaces in the message as it encrypts the space therefore making the encryption wrong. Here is the code.
message = input('enter message: ')
keyword = input('enter keyword: ')
def chr_to_int(char):
return 0 if char == 'z' else ord(char)-96
def int_to_chr(integer):
return 'z' if integer == 0 else chr(integer+96)
def add_chars(a, b):
return int_to_chr(( chr_to_int(a) + chr_to_int(b)) % 26 )
def vigenere(message, keyword):
keystream = cycle(keyword)
new = ''
for msg, key in zip(message, keystream):
if msg == ' ':
new += ' '
else:
new += add_chars(msg, key)
return new
new = vigenere(message, keyword)
print('your encrypted message is: ',new)
I think a solution to this problem would be to cycle through the space the same length of the message so it will carry on to the next letter as if the space wasn't there. I do not know how to go about how to do this .
example:
message: vignere cipher keyword: qwerty
encrypted mesasge (what it should be): mflahdib hajgvo

since cycle returns an iterable, you could instead use next instead of zip so that it only calls for the next char when asked:
>>> def vigenere(message, keyword):
keystream = cycle(keyword)
new = ''
for msg in message:
if msg == ' ':
new += ' '
else:
new += add_chars(msg, next(keystream))
return new
>>> new = vigenere('computing is fun', 'gcse')
>>> new
'jrfubwbsn ll kbq'
EDIT: per OP request, uses zip and offset variable
>>> def vigenere(message, keyword):
keystream = cycle(keyword)
new = ''
offset = 0
for msg, key in zip(message,keystream):
if msg == ' ':
new += ' '
offset += 1
else:
new += add_chars(msg, keyword[keyword.index(key)-offset])
return new
>>> new = vigenere('computing is fun', 'gcse')
>>> new
'jrfubwbsn ll kbq'

Related

How to replace a char from an arraylist to a string? [duplicate]

This question already has answers here:
Caesar Cipher Function in Python
(27 answers)
Closed 5 months ago.
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!?."
list = []
msg = 'Hello World'
key = input()
key = int(key)
print(alphabet[key])
for x in msg:
list.append(x)
print(list)
So basically i need to replace each letter of my array to a specify position (given by the key) of my string.
e.g. key = 3 so H becomes L , E becomes I , L becomes P etc...
it's basically a Caesar cipher
from collections import deque
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!?. "
msg = 'Hello World'
key = int(input()) #3
new_alphabet = deque([i for i in alphabet])
new_alphabet.rotate(key)
new_message_list = [new_alphabet[alphabet.index(m)] for m in msg]
print("".join(new_message_list)) # Ebiil!Tloia
Hello, maybe one of these 2 Solutions will help you:
# Solution 1 using the ASCII table as base:
msg = 'Hello World'
key = int(input())
res = ''
for letter in msg:
res += ''.join(chr(ord(letter) + key)) # ord(letter) gets the ascii value of the letter. then adding the key and chr() trandforming back from ascii value to character
print(res)
# Solution 2 using your alphabet as base
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!?."
msg = 'Hello World'
key = int(input())
res = ''
for letter in msg:
try:
position = alphabet.index(letter) + key # get the index position of each letter and adding the key
char_new = alphabet[position]
except ValueError: # handels cases where characters are used, that are not defined in alphabet
char_new = ' '
res += ''.join(char_new) # joins the new position to the result
print(res)
a space has been added to the end of alphabet to handle spaces in input.
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!?. "
lst = []
msg = 'Hello World'
key = int(input('enter key number: '))
for x in msg:
lst.append(alphabet[(alphabet.index(x) + key) % len(alphabet)])
print(''.join(lst))
import string
alphabet=[i for i in string.ascii_uppercase]
key=int(input("Enter key: "))
msg=input("Enter plain text message:").upper()
enc=[]
for i in msg:
if i == " " or i in string.punctuation:
enc.append(i)
if i in alphabet:
enc.append(alphabet[(alphabet.index(i)+key)%len(alphabet)])
print("Encrypted message is:"+"".join(enc))

How would I fix my Vigenere Cipher Decryption

I'm having an issue with Vigenere Cipher Decryption, When an encrypted message is decrypted, the decrypted message is not printed, but the encrypted is?
I cant seem to see why this dose not work. But its probably a simple issue.
I think the issue is in the main decrypt function.
Help would be much appreciated
Encryption works fine
def encrypt_key(message, key):
i = 0
empty_key = '' # empty key to append to
# turn characters into index
for characters in message:
if characters.isalpha(): # checks if character is in the alphabet
empty_key = empty_key + key[i % len(key)] # append characters to empty key
i = i + 1 # increase integer value by 1
else:
empty_key = empty_key + ' ' # if character is a space or punctuation then add empty space
return empty_key
def encrypt_decrypt(message_char, key_char, choice='encrypt'):
if message_char.isalpha():
first_alphabet_letter = 'a' # assume character is lowercase
if message_char.isupper():
first_alphabet_letter = 'A' # assume character is upper, then replace with A
original_char_position = ord(message_char) - ord(first_alphabet_letter) # difference results in position of char in alphabet
key_char_position = ord(key_char.lower()) - ord('a')
if choice == 'encrypt':
new_char_position = (original_char_position + key_char_position) % 26 # encrypts & loops back around with % 26
else: # if choice == 'decrypt':
new_char_position = (original_char_position - key_char_position + 26) % 26 # decrypts & loops back around
return chr(new_char_position + ord(first_alphabet_letter))
return message_char
def encrypt(message, key):
cipher = ''
empty_key = encrypt_key(message, key)
for message_char, key_char in zip(message, empty_key):
cipher = cipher + encrypt_decrypt(message_char, key_char)
return cipher
def decrypt(cipher, key):
message = ''
empty_key = encrypt_key(cipher, key)
for cipher_char, key_char in zip(cipher, empty_key):
message = message + encrypt_decrypt(cipher_char, key_char, 'decrypt')
return message
message = input('Enter your message to encrypt here: ')
key = input('Enter your key: ')
cipher = encrypt(message, key) # inputs message and key to encrypt function
decrypt_message = decrypt(cipher, key)# inputs message and key to decrypt function
print(f'Cipher: {cipher}')
print(f'Decrypted: {decrypt_message}')
def choice():
user_choice = int(input('''
Enter your choice (1 or 2):
1. Encrypt
2. Decrypt
> '''))
if user_choice == 1:
print(f'Cipher: {cipher}')
print('')
elif user_choice == 2:
print('')
print(f'Decrypted: {decrypt_message}')
choice()

How can spaces be ignored in the input, but printed at the end?

I need to count how many times the same character is repeated and then use that count as an offset in ASCII. For example: ** * should give B A. Because 2 *s mean B (the second letter) and 1 * means A (the first letter).
My problem is that spaces are considered as a character and translated as A. How can I ignore spaces in the input, but include them in my print statement? I currently get BAA instead. This is my code:
def main():
encrypted = "** *"
#input("Enter an encrypted message: ")
count = 0
decoded = ''
for i, ch in enumerate(encrypted):
if i == 0 or ch == encrypted[i-1]:
count += 1
else:
decoded += chr(count + 64)
count = 1
if count > 0:
decoded += chr(count + 64)
print(decoded)
main()
Another example: the input:
********bbbbb&&&&&&&&&&&&999999999999zzzzzzzzzzzzzzz ********bbbbb&&&&&&&&&&&&999999999999zzzzzzzzzzzzzzz
should print out HELLO HELLO.
To solve your problem with minimal changes, you can just add a special check for the space:
...
for i, ch in enumerate(encrypted):
if encrypted[i-1] == ' ':
decoded += ' '
elif i == 0 or ch == encrypted[i - 1]:
...
But you can opt for a simpler way by using groupby:
from itertools import groupby
def main():
encrypted = "** *"
# input("Enter an encrypted message: ")
decoded = ""
for key, group in groupby(encrypted):
if key == ' ':
decoded += ' '
else:
decoded += chr(sum(1 for _ in group) + 64)
print(decoded)
main()
how about a little different approach? Every char is seperated by a space, so just split the input to a list and check the length of each char.
Like this:
def main():
encrypted = "** *"
#input("Enter an encrypted message: ")
count = 0
inp_list= encrypted.split(' ')
decoded = [chr(len(elem) + 64) for elem in inp_list]
for ch in decoded:
print(ch, end=' ')
main()
Output: B A
I like the approach by Tomerikoo, though this is a different approach using what you've already written.
Since your code works without spaces, just run it once per word and join the result together.
def decode_word(word):
decoded = ''
count = 0
for i, ch in enumerate(word):
if i == 0 or ch == word[i-1]:
count += 1
else:
decoded += chr(count + 64)
count = 1
if count > 0:
decoded += chr(count + 64)
return decoded
def decode_string(encrypted):
return ' '.join(map(decode_word, encrypted.split()))
print(decode_string("** *"))
# B A
print(decode_string("********bbbbb&&&&&&&&&&&&999999999999zzzzzzzzzzzzzzz ********bbbbb&&&&&&&&&&&&999999999999zzzzzzzzzzzzzzz"))
# HELLO HELLO

Improving Encryption Algorithm

Recently I had started this assignment just meant for school purposes but I have recently decided that I would want to continue this as a new project and wanted suggestions on how I can improve my algorithm. I had an idea where I wanted the encryption key to change per character to make my encryption more secure but I have been having difficulty with this as It wouldn't work out and I couldnt decrypt my final encrypted text
Here is the encrypter:
text = input('Enter A Word : ') ##This asks a user for an input of text integers etc
encrypt = '' ##Empty Value used to store the encrypted value
temp = '' ##Empty temp value replaced at every iteration of the encryption process
temp2 =0 ##Empty temp value replaced at every iteration of the encryption process
temp_val=0
temp_char=''
rtext= text[::-1]
key=int(input('Enter your key (Your Encrypted Sentence Will Further be Encrypted Later) : '))##key used to shift the letters
for a in range (0,len(rtext)):
if len(rtext) % 2==0:
hlength=len(rtext)/2
else:
hlength=(len(rtext)+1)/2
print(hlength)
for i in range(int(hlength),len(rtext)):
##Rearranges text in a caps switch
if str.islower(rtext[i])==True:
temp=temp+str.upper(rtext[i])
elif str.isupper(rtext[i])==True:
temp=temp+str.lower(rtext[i])
else:
temp=temp+rtext[i]
for b in range(0,int(hlength)):
##Rearranges text in a caps switch
if str.islower(rtext[b])==True:
temp=temp+str.upper(rtext[b])
elif str.isupper(rtext[b])==True:
temp=temp+str.lower(rtext[b])
else:
temp=temp+rtext[b]
for j in range(0,len(temp)):
temp_val=0
temp2=0
temp_val=ord(temp[j])
temp2=temp2+temp_val+int(key)
temp_char=temp_char+chr(temp2)
encrypt=temp_char
print(encrypt)
print(temp)
print(temp2)
The Decrypter:
text = input('Enter A Word : ') ##This asks a user for an input of text integers etc
decrypt = '' ##Empty Value used to store the encrypted value
order=0
characters=''
temp=''
rtext=text[::-1]
key=int(input('Enter your key (decryption) : '))##key used to shift the letters
for i in range (0,len(rtext)):
order=0
order=order+ord(rtext[i])-int(key)
characters=characters+chr(order)
for a in range (0,len(rtext)):
if len(rtext) % 2==0:
hlength=len(rtext)/2
else:
hlength=(len(rtext)+1)/2
for j in range (int(hlength),len(characters)):
if str.islower(characters[j])==True:
temp=temp+str.upper(characters[j])
elif str.isupper(characters[j])==True:
temp=temp+str.lower(characters[j])
else:
temp=temp+characters[j]
for b in range (0,int(hlength)):
if str.islower(characters[b])==True:
temp=temp+str.upper(characters[b])
elif str.isupper(characters[b])==True:
temp=temp+str.lower(characters[b])
else:
temp=temp+characters[b]
print(temp)
I specifically want to change the variable key.
ord() - Turns characters into its Ascii equivalent
chr() - Turns Ascii numbers into its character equivalent
rtext - gets the inverse of the users input
If we simplify the code in the encryptor a little, we get:
def encrypt_text(text: str, key: int):
print("TEXT:", text, "KEY:", key)
temp = ''
temp2 = 0
temp_val = 0
temp_char = ''
rtext = text[::-1]
print("RTEXT:", rtext)
hlength = len(rtext) // 2 + len(rtext) % 2 # half, round up
print("HLENGTH:", hlength)
for i in range(hlength, len(rtext)):
# Rearrange text in a caps switch
if rtext[i].islower():
temp += rtext[i].upper()
elif rtext[i].isupper():
temp += rtext[i].lower()
else:
temp += rtext[i]
print("TEMP:", temp)
for b in range(0, int(hlength)):
# Rearrange text in a caps switch
if rtext[b].islower():
temp += rtext[b].upper()
elif rtext[b].isupper():
temp += rtext[b].lower()
else:
temp += rtext[b]
for j in range(len(temp)):
temp_val = 0
temp2 = 0
temp_val = ord(temp[j])
temp2 = temp2 + temp_val + int(key)
temp_char = temp_char + chr(temp2)
encrypt = temp_char
print("ENCRYPT:", encrypt)
print("TEMP:", temp)
print("TEMP2:", temp2)
return encrypt
text = "hello world"
key = 42
print("ENCRYPTED:", encrypt_text(text, key))
I've put it inside a function (and added some print statements), so it becomes easier to work with while developing. The code is essentially the same as yours, except
for a in range (0,len(rtext)):
if len(rtext) % 2==0:
hlength=len(rtext)/2
else:
hlength=(len(rtext)+1)/2
is replaced by
hlength = len(rtext) // 2 + len(rtext) % 2 # half, round up
which gives the same result (except hlength is an integer).
Your first two for loops do the same operation (switches case on a string). We can write a function for that:
def swap_case(str):
res = ''
for ch in str:
if ch.islower():
res += ch.upper()
elif ch.isupper():
res += ch.lower()
else:
res += ch
return res
and now we can replace the first two for loops with calls to our function:
temp += swap_case(rtext[hlength:len(rtext)]) # or shorter rtext[hlength:]
temp += swap_case(rtext[0:hlength]) # or shorter rtext[:hlength]
it just happend that .swapcase() is already a string method, so we didn't really need our swap_case function, and could just write:
temp += rtext[hlength:].swapcase()
temp += rtext[:hlength].swapcase()
Your third for-loop:
for j in range(len(temp)):
temp_val = 0 # this value is not used (it's overwritten 2 lines down)
temp2 = 0
temp_val = ord(temp[j])
temp2 = temp2 + temp_val + int(key) # temp2 is always zero at this point
temp_char = temp_char + chr(temp2)
encrypt = temp_char
can be simplified to (the initial value of temp_char is set to the empty string above):
for j in range(len(temp)): # for each index position (j)
temp_val = ord(temp[j]) # use the character at from temp at index j
temp2 = temp_val + int(key) # key is already an int from your: key=int(input('Enter your key (decryption) : '))
temp_char += chr(temp2)
encrypt = temp_char # hmm... just overwriting encrypt on every iteration
the comments mean that it could be even simpler:
encrypt = ''
for character in temp:
temp_val = ord(character)
temp2 = temp_val + key
encrypt += chr(temp2)
This leaves us with (the comments enumerate the steps taken):
def encrypt_text(text: str, key: int):
temp = ''
rtext = text[::-1] # (1) reverse the string
hlength = len(rtext) // 2 + len(rtext) % 2 # (2) split the string on hlength
second_part = rtext[hlength:].swapcase() # .. and swap case on the parts
first_part = rtext[:hlength].swapcase()
temp += second_part # (3) and put the second part..
temp += first_part # .. before the first part
encrypt = ''
for character in temp:
temp_val = ord(character)
temp2 = temp_val + key # (4) add key to every character
encrypt += chr(temp2)
return encrypt
to decrypt a string encrypted with this function, we need to do the operations "backwards and opposite":
def decrypt_text(encrypted, key):
temp = ''
for ch in encrypted:
temp += chr(ord(ch) - key) # subtract key from every character (4)
hlength = len(encrypted) // 2 + len(encrypted) % 2
half = len(encrypted) - hlength # the split point is a mirror image of what it is in encrypt_text (3)
rtext = ''
rtext += temp[half:].swapcase() # re-assemble the string and swap case (2)
rtext += temp[:half].swapcase()
text = rtext[::-1] # finally reverse (1)
return text
The standard way of using longer keys (similar to your one-key-per-character), is to use the xor function, which in Python is written as ^ (pronounced either 'hat' or 'xor'), as in:
a ^ b # true if either a, or b are true, but not both
Here is some background on how it works, although you don't really need to understand this to use it...
This operator work on bits. To see what is happening, lets define a
function to print the bit representation of an integer (you don't need
to understand this):
def bits(n):
return bin(n)[2:].zfill(4)
then we have we can show the bit patterns of integers 5 and 9, and the
operation 5 ^ 9:
bits(5) => 0101
bits(9) => 1001
--------------------
bits(5 ^ 9) => 1100
====================
if you look at the bit patterns, there is a 1 in the result where
there is exactly one 1 in the column above, so from left to right (0 ^
1 = 1, 1 ^ 0 = 1, 0 ^ 0 = 0, and 1 ^ 1 = 0).
Knowing the above, you can verify that for any number k ^ k == 0,
and n ^ 0 == n, and therefore n ^ k ^ k == n.
The useful thing about xor is that for any number n:
n ^ key ^ key == n
ie. xor-ing the number with key, twice, gives you back the number.
Let's use this to encrypt (zip(text, key) returns one character from text and key at a time, in lock-step, until one of them is "used up"):
def encrypt_xor(text: str, key: str):
if len(key) < len(text):
# key must be longer because of how zip works..
raise ValueError("Key must be at least as long as text")
res = ''
for ch, k in zip(text, key):
res += chr(ord(ch) ^ ord(k))
return res
if you try to print(encrypt_text('hello', 'world')) you'll get gibberish printed to your screen (since the value you get by xor-ing two characters isn't necessarily printable). The cool thing about xor is that the decrypt function is exactly the same as the encrypt function, so encrypting twice gives you the original value:
text = 'hello'
key = 'world'
cipher = encrypt_xor(text, key) # encrypted text is often called cipher
print(encrypt_xor(cipher, key)) # prints 'hello'
You can use a similar structure for shift-type encryption (but without the convenience that the decrypt function is the same as the encrypt), e.g.:
def encrypt_shift(text: str, key: str):
res = ''
for ch, k in zip(text, key):
res += chr(ord(ch) + ord(k)) # add the char from the key
return res
def decrypt_shift(text: str, key: str):
res = ''
for ch, k in zip(text, key):
res += chr(ord(ch) - ord(k)) # subtract the char from the key
return res
text = 'hello'
key = 'world'
cipher = encrypt_shift(text, key)
print(decrypt_shift(cipher, key)) # prints 'hello
to avoid the unpleasantness of needing a key that is longer than the text, we can start using the key from the beginning again if there is more text left. The itertools.cycle(..) function does this for us:
import itertools
def encrypt_shift(text: str, key: str):
res = ''
for ch, k in zip(text, itertools.cycle(key)):
res += chr(ord(ch) + ord(k))
return res
def decrypt_shift(text: str, key: str):
res = ''
for ch, k in zip(text, itertools.cycle(key)):
res += chr(ord(ch) - ord(k))
return res
now
text = 'hello world'
key = 'world'
cipher = encrypt_shift(text, key)
print(decrypt_shift(cipher, key)) # prints 'hello world' (not just 'hello' -- the first len(key) characters)
This can be plugged into the encrypt_text and decrypt_text functions from the other answer:
def encrypt_text(text: str, key: str): # key is now a string
temp = ''
rtext = text[::-1] # (1) reverse the string
hlength = len(rtext) // 2 + len(rtext) % 2 # (2) split the string on hlength
second_part = rtext[hlength:].swapcase() # .. and swap case on the parts
first_part = rtext[:hlength].swapcase()
temp += second_part # (3) and put the second part..
temp += first_part # .. before the first part
encrypt = encrypt_shift(temp, key) # (4) shift each char using key
return encrypt
and
def decrypt_text(encrypted, key):
temp = decrypt_shift(encrypted, key) # unshift each char using key
hlength = len(encrypted) // 2 + len(encrypted) % 2
half = len(encrypted) - hlength # the split point is a mirror image of what it is in encrypt_text (3)
rtext = ''
rtext += temp[half:].swapcase() # re-assemble the string and swap case (2)
rtext += temp[:half].swapcase()
text = rtext[::-1] # finally reverse (1)
return text

encrypting upper-case letters python vigenere

I'm having trouble encrypting upper-case letters e.g. if the message is COMPUTING IS FUN the keyword is GCSE i should get JRFUBWBSN LL KBQ but my actual result is xftipkpgb zz ype. This result neither has the correct letters nor is capital. Any help appreciated
message = input('\nenter message: ')
keyword = input('enter keyword: ')
def chr_to_inta(char):
return 0 if char == 'Z' else ord(char)-64
def int_to_chra(integer):
return 'Z' if integer == 0 else chr(integer+64)
def add_charsa(msg, key):
return int_to_chr(( chr_to_int(msg) + chr_to_int(key)) % 26 )
def chr_to_int(char):
return 0 if char == 'z' else ord(char)-96
def int_to_chr(integer):
return 'z' if integer == 0 else chr(integer+96)
def add_chars(msg, key):
return int_to_chr(( chr_to_int(msg) + chr_to_int(key)) % 26 )
def vigenere(message, keyword):
keystream = cycle(keyword)
new = ''
for msg in message:
if msg == ' ': # adds a space
new += ' '
elif 96 < ord(msg) < 123: # if lowercase
new += add_chars(msg, next(keystream))
else: # if uppercase
new += add_charsa(msg, next(keystream))
return new
new = vigenere(message, keyword)
print('your encrypted message is: ',new)
since you dont seem to get what i am saying:
def add_charsa(msg, key):
return int_to_chr(( chr_to_int(msg) + chr_to_int(key)) % 26 )
is what you currently have. with this, you get your bad output:
>>> vigenere('COMPUTING IS FUN','GCSE')
'xftipkpgb zz ype'
This is because you have not changed the return statement for this function to call your new uppercase functions. if you change the return statement to:
def add_charsa(msg, key):
return int_to_chra(( chr_to_inta(msg) + chr_to_inta(key)) % 26 )
#notice the change in function calls from int_to_chr -> int_to_chra, and chr_to_int -> chr_to_inta
you will then get the expected:
>>> vigenere('COMPUTING IS FUN','GCSE')
'JRFUBWBSN LL KBQ'
It is worth it to know that if your key has a mix of upper and lower case letters, this will not work. very well. i would instead change your keystream to be : keystream = cycle(keyword.lower()) then your function would be:
def add_charsa(msg, key):
return int_to_chra(( chr_to_inta(msg) + chr_to_int(key)) % 26 )
#notice the call to chr_to_int(key) because key will always be lower case

Categories

Resources