how to decrypt a string that is encrypted using XOR - python-3.x

I have tried to encrypt a string using a XOR operator and took the output in alphabets. Now when I am trying to decrypt it I'm not getting the string again.
Encryption code:
string= "Onions"
keyword = "MELLON"
def xor(string, key):
st=[]
ke=[]
xored=[]
for i in string:
asc= (ord(i))
st.append(int(asc))
print(st)
for i in key:
asc= (ord(i))
ke.append(int(asc))
print(ke)
for i in range(len(string)):
s1=st[i]
k1=ke[i]
abc = s1^k1
le = ord('A')+abc
ch = chr(le)
if le> 90:
le= le-26
ch = chr(le)
print(s1,k1)
print('XOR =',abc)
print(ch)
xored.append(ch)
print(xored)
return("" . join(xored))
Need help!!

The algorithm does not perform a pure XOR, but maps values conditionally to another value, leading to a relation that is no longer bijective.
To illustrate this point. See what this script outputs:
keyword = "MELLON"
print(xor("Onions", keyword) == xor("OTGEHs", keyword))
It will output True!
So this means you have two words that are encrypted to the same string. This also means that if you need to do the reverse, there is no way to know which of these is the real original word.
If you want to decryption to be possible, make sure to only use operations that lead to a bijective mapping. For instance, if you only use a XOR, without adding or subtracting values, it will be OK.
Here is an approach where only lower and uppercase letters of the Latin alphabet are allowed (for both arguments):
def togglecrypt(string, key):
mapper = "gUMtuAqhaEDcsGjBbreSNJYdFTiOmHKwnXWxzClQLRVyvIkfPpoZ"
res = []
for i, ch in enumerate(string):
shift = mapper.index(key[i % len(key)]) % 26
i = mapper.index(ch)
if i < 26:
j = 26 + (i + shift) % 26
else:
j = (i - shift) % 26
res.append(mapper[j])
return("".join(res))
keyword = "MELLON"
encoded = togglecrypt("Onions", keyword)
print(encoded) # TdsDAn
print(togglecrypt(encoded, keyword)) # Onions

Related

How can I modify my code in order to output the same text that was input before encryption, after decryption?

I have written a code for Encryption and Decryption (without any use of key) and I want that after decryption of the message, the message that was input in the time of encryption should be printed as it is.
According to what I did, I am successfully able to get the message after the decryption algorithm is run but the result is not in the order I supplied it when giving the input. It means that: 'h' gets converted to 'H' and same is the case with other letters.
# Encryption
# Trial 4
in_text = input('Enter the text that you want to encrypt: ').lower()
out_text = []
for i in in_text:
if i == ' ':
out_text.append(i)
continue
elif i in 'aeiou':
out_text.append(ord(i) + 4)
continue
else:
out_text.append(i)
final = ''.join(str(e) for e in out_text)
print(final)
# Decryption
# Trial 2
import string
user_input = input('Enter the text that you want to decrypt: ')
d_out = []
z = ''
for i in user_input:
if i == ' ':
d_out.append(i)
continue
elif i in 'bcdfghjklmnpqrstvwxyz':
d_out.append(i)
continue
elif i in string.digits:
z = z + i
n = len(z)
if n == 3:
d_out.append(chr(int(z) - 4))
z = ''
my_str = ''
for a in d_out:
my_str = my_str + a
print(f'The decrypted message is: {my_str.title()}')
When I remove ".lower()" in my Encryption code and ".title()" in my Decryption code, the result differs after decryption and there are some special characters that are printed.
Please let me know on how can I proceed with the same.
Your response will be highly appreciated!
Case 1
ENCRYPTION
Enter the text that you want to encrypt: hello World
h105ll115 w115rld
DECRYPTION
Enter the text that you want to decrypt: h105ll115 w115rld
The decrypted message is: Hello World
Case 2
After removing ".lower()" and ".title()"
Making the encryption code to be as follows:
in_text = input('Enter the text that you want to encrypt: ')
out_text = []
for i in in_text:
if i == ' ':
out_text.append(i)
continue
elif i in 'aeiouAEIOU':
out_text.append(ord(i) + 4)
continue
else:
out_text.append(i)
final = ''.join(str(e) for e in out_text)
print(final)
Making the decryption code as follows:
import string
user_input = input('Enter the text that you want to decrypt: ')
d_out = []
z = ''
for i in user_input:
if i == ' ':
d_out.append(i)
continue
elif i in 'bcdfghjklmnpqrstvwxyz':
d_out.append(i)
continue
elif i in string.digits:
z = z + i
n = len(z)
if n == 3:
d_out.append(chr(int(z) - 4))
z = ''
my_str = ''
for a in d_out:
my_str = my_str + a
print(f'The decrypted message is: {my_str}')
The output comes as follows:
Encryption
Enter the text that you want to encrypt: Hello world
H105ll115 w115rld
Decryption
Enter the text that you want to decrypt: H105ll115 w115rld
The decrypted message is: ello world
Case 1 and Case 2 both have different outputs but the problem of 'h' being 'H' or 'W' being 'w' or other characters being the same order still exists.
There are multiple issues.
First, only your encryption procedure takes upper capitals into account.
Second, you go through your ciphertext character by character during decryption, while your encryption uses ord directly. ord will generate multiple digits per character.
These kind of issues can and should be found using a debugger rather than StackOverflow. This is not abnormal program execution, the code is doing exactly what you're telling it to do - but that's incorrect.
I'd first take a look at Viginere implementations and take hints from that if you don't know how to perform these kind of encodings.
You will have to solve the multi-digit problem in some way or other, otherwise you won't be able to distinguish where each number starts or ends if they are next to each other. For instance, you could put the digits in brackets, e.g. [69] for the "encryption" of A (hah, interesting position in your ciphertext), and escape brackets like this [[ and ]] if you require brackets to be part of the text.
This should be part of the description of your scheme, which you should probably think out before you (re-)start to program. Randomly trying stuff is not going to work.

How can I make my function work for any number?

I am having some issues with some code I wrote for this problem:
“Write a function namedd calc that will evaluate a simple arithmetic expression. The input to your program will be a string of the form:
operand1 operator operand2
where operand1 and operand2 are non-negative integers and operator is a single-character operator, which is either +, -, or *. You may assume that there is a space between each operand and the operator. You may further assume that the input is a valid mathemat- ical expression, i.e. your program is not responsible for the case where the user enters gibberish.
Your function will return an integer, such that the returned value is equal to the value produced by applying the given operation to the given operands.
Sample execution:
calc("5 + 10") # 15
“You may not use the split or eval functions in your solution.
Hint: the hard part here is breaking the input string into its three component. You may use the find and rfind functions to find the position of the first and last space, and then use the slice operator (that is, s[startindex:endindex]) to extract the relevant range of characters. Be careful of off-by-one errors in using the slice operator.
Hint: it’s best to test your code as you work. The first step should be to break the input string into its three components. Write a program that does that, have it print out the operator and the two operands on separate lines, and test it until you are convinced that it works. Then, modifying it to perform the desired mathematical operation should be straightforward. Test your program with several different inputs to make sure it works as you expect.”
Here is my code:
def calc(exp):
operand1 = int(exp[:1])
operand2 = int(exp[4:6])
operator = exp[2:3]
if(operator == "+"):
addition = operand1+operand2
return addition
if(operator == "-"):
subtraction = operand1-operand2
return subtraction
if(operator == "*"):
multiplication = operand1*operand2
return multiplication
print(calc("5 + 10"))
print(calc("4 - 8"))
print(calc("4 * 3"))
My code does not fully meet the criteria of this question. It only works for single digit numbers. How can I make my code work for any number?
Like:
“504 + 507”
”5678 + 76890”
and so on?
Thank you. Any help is appreciated.
As the hint says, get the position of the first and last space of the expression, use it to extract the operand and the operators, and then evaluate accordingly.
def calc(exp):
#Get the position for first space with find
low_idx = exp.find(' ')
#Get the position for last space with rfind
high_idx = exp.rfind(' ')
#Extract operators and operand with slice, converting operands to int
operand1 = int(exp[0:low_idx])
operator = exp[low_idx+1:high_idx]
operand2 = int(exp[high_idx:])
result = 0
#Evaluate based on operator
if operator == '+':
result = operand1 + operand2
elif operator == '-':
result = operand1 - operand2
elif operator == '*':
result = operand1 * operand2
return result
print(calc("5 + 10"))
print(calc("4 - 8"))
print(calc("4 * 3"))
print(calc("504 + 507"))
print(calc("5678 + 76890"))
#15
#-4
#12
#1011
#82568
The answer is in the specification:
You may use the find and rfind functions to find the position of the first and last space, and then use the slice operator (that is, s[startindex:endindex]) to extract the relevant range of characters.
find and rfind are methods of string objects.
You could split it into three components using this code: (note: this doesn't use split or eval)
def splitExpression(e):
numbers = ["1","2","3","4","5","6","7","8","9","0"] # list of all numbers
operations = ["+","-","*","/"] # list of all operations
output = [] # output components
currentlyParsing = "number" # the component we're currently parsing
buildstring = "" # temporary variable
for c in e:
if c == " ":
continue # ignore whitespace
if currentlyParsing == "number": # we are currently parsing a number
if c in numbers:
buildstring += c # this is a number, continue
elif c in operations:
output.append(buildstring) # this component has reached it's end
buildstring = c
currentlyParsing = "operation" # we are expecting an operation now
else:
pass # unknown symbol!
elif currentlyParsing == "operation": # we are currently parsing an operation
if c in operations:
buildstring += c # this is an operation, continue
elif c in numbers:
output.append(buildstring) # this component has reached it's end
buildstring = c
currentlyParsing = "number" # we are expecting a number now
else:
pass # unknown symbol!
if buildstring: # anything left in the buffer?
output.append(buildstring)
buildstring = ""
return output
Usage: splitExpression("281*14") returns ["281","*","14"]
This function also accepts spaces between numbers and operations
You can simply take the string and use the split method for the string object, which will return a list of strings based on some separator.
For example:
stringList = "504 + 507".split(" ")
stringList will now be a list such as ["504", "+", "507"] due to the separator " " which is a whitespace. Then just use stringList[1] with your conditionals to solve the problem. Additionally, you can use int(stringList[0]) and int(stringList[2]) to convert the strings to int objects.
EDIT:
Now I realized that your problem said to use find() instead of split(). Simply use the logic above but instead find(" ") the first whitespace. You will then need to find the second whitespace by slicing past the first whitespace using the two additional arguments available for find().
You need to split the string out instead of hard coding the positions of the indexes.
When coding you want to try to make your code as dynamic as possible, that generally means not hard coding stuff that could be a variable or in this case could be grabbed from the spaces.
Also in the if statements I modified them to elif as it is all one contained statement and thus should be grouped.
def calc(exp):
vals = exp.split(' ')
operand1 = int(vals[0])
operand2 = int(vals[2])
operator = vals[1]
if operator == '+':
return operand1+operand2
elif operator == '-':
return operand1-operand2
else:
return operand1*operand2

Scrambling a string in Python without using random.shuffle()

I'm trying to scramble a string, "string", without using random.shuffle(), but my code keeps producing output that has missing and repeating characters, e.g. gtrgtg, gnrtnn, etc. I'm not sure what I'm doing wrong.
import random
s = "string"
new_s=[]
for c in s:
if random.choice(s) not in new_s:
new_s.append(random.choice(s))
print(''.join(new_s))
In its current state, your program checks whether the randomly chosen character is in a string. If it is, it doesn't do anything other than continuing the loop. Also since you don't assign random.choice(s) to a variable, you generate another character after you do the check.
A working version would be:
import random
s = "string"
new_s = []
for c in s:
char = random.choice(s) # assign it to a variable
while char in new_s: # until a new character comes, repeat the procedure
char = random.choice(s)
new_s.append(char)
print(''.join(new_s))
This generates strings like ngtsri, gsrnit, etc. Note that this won't work if you have duplicates in the original string.
The above code is highly inefficient. I only gave the correction assuming this was for learning purposes. Normally, if you want to repeatedly check if something is in a collection, that collection should be a set or a dictionary.
random.choice choses a random character out of string s, but doesn't remove it - so it's possible for the same character to be chosen multiple times, and for some characters to not be chosen at all.
import random
s = 'string'
new_s = []
# rather than choosing a character, chose an index, use it and slice it out
while s:
i = random.randint(0, len(s)-1)
new_s.append(s[i])
s = s[:i] + s[i+1:]
print(''.join(new_s))
# this is more elegant with lists:
s = list(s)
while s:
i = random.randint(0, len(s)-1)
new_s.append(s.pop(i))
print(''.join(new_s))
Neither option is very efficient... but for efficiency, use random.shuffle. :)
Using while, you could loop through s until the length of new_s matches with that of s and the resultant string has non-repeating characters.
import random
s = "string"
new_s = '' # So you will not need ''.join() when you print this result
while len(new_s) != len(s):
char = random.choice(s)
if char not in new_s:
new_s += char
print(new_s)
rntigs
>>>
try this:
from random import randint
def shuffle(sr):
n = len(sr)
s = list(sr)
for i in range(n):
cur, idx = s[i], randint(0, n - 1)
s[i], s[idx] = s[idx], cur
return ''.join(s)
print(shuffle("hello"))

immutable strings, typeerror: 'str' object does not support item assignment

I am using someone else code to make a Cipher solver but it gives me
typeerror: 'str' object does not support item assignment"
at
key[keyIndex] = cipherletter
Is there a way to keep the same meaning with the error? :)
def decryptWithCipherletterMapping(ciphertext, letterMapping):
# Return a string of the ciphertext decrypted with the letter mapping,
# with any ambiguous decrypted letters replaced with an _ underscore.
# First create a simple sub key from the letterMapping mapping.
key = ['x'] * len(LETTERS)
for cipherletter in LETTERS:
if len(letterMapping[cipherletter]) == 1:
# If there's only one letter, add it to the key.
keyIndex = LETTERS.find(letterMapping[cipherletter][0])
key[keyIndex] = cipherletter
else:
ciphertext = ciphertext.replace(cipherletter.lower(), '_')
ciphertext = ciphertext.replace(cipherletter.upper(), '_')
key = ''.join(key)
# With the key we've created, decrypt the ciphertext.
return simpleSubCipher.decryptMessage(key, ciphertext)
The short answer is that you've got an indentation error. Instead of:
# First create a simple sub key from the letterMapping mapping.
key = ['x'] * len(LETTERS)
for cipherletter in LETTERS:
if len(letterMapping[cipherletter]) == 1:
# If there's only one letter, add it to the key.
keyIndex = LETTERS.find(letterMapping[cipherletter][0])
key[keyIndex] = cipherletter
else:
ciphertext = ciphertext.replace(cipherletter.lower(), '_')
ciphertext = ciphertext.replace(cipherletter.upper(), '_')
key = ''.join(key)
you should have:
# First create a simple sub key from the letterMapping mapping.
keyList = ['x'] * len(LETTERS)
for cipherletter in LETTERS:
if len(letterMapping[cipherletter]) == 1:
# If there's only one letter, add it to the key.
keyIndex = LETTERS.find(letterMapping[cipherletter][0])
keyList[keyIndex] = cipherletter
else:
ciphertext = ciphertext.replace(cipherletter.lower(), '_')
ciphertext = ciphertext.replace(cipherletter.upper(), '_')
key = ''.join(keyList)
The deeper problem is that you're using key, in your original code, at both a string and a list. Obviously that's confusing, as witness the fact that you ended up confused! In my version, above, I separated these into two different variables, which I think you'll find clearer.
Because key is type of str.
In Python, strings does not support item assignment like a list (as the error message explicitly said).
To update the character of a string to a given index, you can do something like:
string = "foobar"
charindex = 2 # so you wants to replace the second "o"
print(string[0:charindex] + 'O' + string[charindex+1:])
# gives foObar
Or turn it in function:
def replace_at_index(string, replace, index):
return string[0:index] + replace + string[index+1:]
print(replace_at_index('EggsandBacon', 'A', 4))
# gives EggsAndBacon
So you will use it like:
key = replace_at_index(key, cipherletter, keyIndex)

Python - Iterate through a list, swapping alphbets by indexes?

I need to write a code that makes alphabets rotate, through 2 lists.
So I need to define a function, let's say it is called rotate_text.
2 parameters are passed, 1 is string and 1 is integer.
This is my code so far:
def rotate_text(text, n):
plaintext = ['ABCDEFGHIJKLMNOPQRSTUVWXYZ']
ciphertext = ['FGHIJKLMNOPQRSTUVWYXZABCDE']
rotated_text = []
for i in plaintext:
rotated_text = ciphertext[plaintext[i + n]]
result = ''.join(rotated_text)
return result
So what it needs to do is, if I put ABC for the parameter text and for 2 for the parameter n,
A should return CDE as the result. Or DOG and 11 should return OBK. I don't really think I need that cipertext list so I think I will take that out, but how do I make this code work?
If the program gets ABC as the text, it should find A's index from plaintext list and + n to that index, and find the letter satisfies with plused n index from plaintext list and then.... I am getting a headache.
Can anyone help?
How about this code? A text should be only capital character.
def rotate_text(text, n):
for i in len(text):
number = ord(text[i]) - ord('A')
number = (number + n) % 26
text[i] = chr(number + ord('A'))
return text
If you want to use lower case too, you should use if statements.

Resources