Encrypt/Decrypt With Custom Dictionary

Dear community,

I’ve created a code to Encrypt/Decrypt lists of words using a custom dictionary. Although the Encrypt function work as expected, the Decrypt option is causing me some problems, I’ll explain why.

The entire code:

#Dictionary
crypt = {
    "a": "100",
    "b": "101",
    "c": "102",
    "d": "103",
    "e": "104",
    "f": "105",
    "g": "106",
    "h": "107",
    "i": "108",
    "j": "109",
    "k": "110",
    "l": "111",
    "m": "112",
    "n": "113",
    "o": "114",
    "p": "115",
    "q": "116",
    "r": "117",
    "s": "118",
    "t": "119",
    "u": "120",
    "v": "121",
    "w": "122",
    "x": "123",
    "y": "124",
    "z": "125",
}

#Encrypt Function
def encrypt():
    encrypted = ''
    words = input('Enter text to encrypt:\n').lower()
    for char in words:
        if char in crypt:
            encrypted += crypt[char]
        else:
            encrypted += char
    print(encrypted)

#Decrypt Function
def decrypt():
    decrypted = ''
    code = input('Enter the code:\n')
    inv_crypt = {v: k for k, v in crypt.items()}

    for n1, n2, n3 in zip(code[::3], code[1::3], code[2::3]):
        splitted = (n1+n2+n3)
        if splitted in inv_crypt:
            decrypted += inv_crypt[splitted]
        else:
            decrypted += splitted
    print(decrypted)


#Ask user for encrypt or decrypt
while True:
    print('Select an option\n', '1 to Encrypt\n', '2 to Decrypt\n', '3 to Exit\n')
    option = input()

    if option == '1':
        encrypt()
        break
    elif option == '2':
        decrypt()
        break
    elif option == '3':
        break
    else:
        print('No correct option selected\n')

As I mentioned, the Encrypt function works properly. Running the code in the terminal would look like this:

Select an option
1 to Encrypt
2 to Decrypt
3 to Exit
1
Enter text to encrypt:
dog, cat, horse

The output at the end will be

103114106, 102100119, 107114117118104

Which correspond to the words dog, cat, horse encrypted with the custom dictionary. So far, so good.

The problem comes with the Decrypt function.

As you can notice, all the letters has an equivalent of three digit number, therefore it’s necessary to divide the numbers in groups of three digits. Additionally I inverted the dictionary to convert the keys in values and the values in keys, taking into account that for decryption the groups of three digits will be the necessary keys we will use to find the equivalent letter (value now) in the dictionary.

For Decrypt, running the code in the terminal would look like this:

Select an option
1 to Encrypt
2 to Decrypt
3 to Exit
2
Enter the code:
103114106, 102100119, 107114117118104

The output at the end will be

dog, 102100119, 10711411711810

As you can see, the word dog is decrypted but not the words cat and horse. Additionally the last digit (4) is lost.


I think the problem lies in the function where I divide the numbers into groups of three digits, the code that perform this function is

for n1, n2, n3 in zip(code[::3], code[1::3], code[2::3]):
        splitted = (n1+n2+n3)

If we print(splitted) the result is

103
114
106
, 1
021
001
19,
10
711
411
711
810

Which is an error, because the output should look something like this:

103
114
106
,

102
100
119
,

107
114
117
118
104

But I haven’t managed to organize the information in this way.

May someone give me a hand?

Thank you in advance.

Just a suggestion, based on the fact that each group of numbers will in fact decrypt correctly:

103114106 = dog
102100119 = cat
107114117118104 = horse

… why not have each group stored in a list eg: words = ["103114106","102100119","107114117118104"] and decrypt each in turn by looping though said list.

Split on commas to get the words, strip the spaces off the words, decrypt each word, and then join all the bits together to get the output.

I like your suggestion, to be honest, I never thought to convert it in a list.

I already did it and the Decrypt function now looks like this

def decrypt():
    decrypted = ''
    code = input('Enter the code:\n')
    code_list = list(code.split(', '))
    inv_crypt = {v: k for k, v in crypt.items()}

    for encrypted in code_list:
        for n1, n2, n3 in zip(encrypted[::3], encrypted[1::3], encrypted[2::3]):
            splitted = (n1+n2+n3)
            if splitted in inv_crypt:
                decrypted += inv_crypt[splitted]
            else:
                decrypted += splitted
    print(decrypted)

In this case the output is

dogcathorse

It decrypt but the words are not separated by commas. I think we are close to the final solution.

Personally, when decrypting a word, I’d do it something like this:

decrypted_parts = []

for start in range(0, len(word), 3):
    part = word[start : start + 3]
    decrypted_parts.append(inv_crypt.get(part, part))

decrypted_word = ''.join(decrypted_parts)

Build a list of the decrypted words and then join them with ', '.join(...).

[Edit]

I just noticed list(code.split(', ')). .split already returns a list.

Also, you’re assuming that the words will be exactly separated by ', '. I’d suggest splitting only on ',' and then stripping any spaces off.

I could not get your decrypt to work, so I’ve come up with this as a POC:

words = ["103114106", "102100119", "107114117118104"]
decrypted_word = ""
decrypted = []
key_values = list(crypt.keys()), list(crypt.values())

for word in words:
    numbers = [word[::-1][i:i + 3][::-1] for i in range(0, len(word), 3)][::-1]

    for number in numbers:
        letter_index = key_values[1].index(number)
        decrypted_word += key_values[0][letter_index]
    decrypted.append(decrypted_word)
    decrypted_word = ""

for word in decrypted:
    print(word, end=" ")

To be fair, I had trouble splitting the encoded words into groups of three numbers, but found this and used it.

There’s probably a better way to do a reverse look-up for the dictionary than the way that I’ve done it, but I’ve never had to do that before.

Maybe some of the concepts here will help you.

Does this help?:

def decrypt():
    ind = 0
    decrypted = ''
    code = input('Enter the code:\n')
    inv_crypt = {v: k for k, v in crypt.items()}
    while ind < len(code):
        if code[ind: ind + 3] in inv_crypt:
            decrypted += inv_crypt[code[ind: ind + 3]]
            ind += 3
        else:
            decrypted += code[ind]
            ind += 1
    print(decrypted)

EDIT:

Try out the above as a replacement for the original decrypt() function; it seems to work.

The above was written based on the premise that characters included in the input to be encrypted fall into two categories that should be handled as follows:

  1. Letters in the English alphabet - These should be encrypted.
  2. All other characters - These should be included as is in the encrypted code.

The prompt says nothing about separators or delimiters. That is why commas, other punctuation, and spaces have no special status distinct from other non-alphabetic characters. Therefore, they are all preserved as is.

The prompt for decryption also makes no mention of separators or delimiters. Accordingly, spaces, commas, other non-alphabetic characters, and extraneous digits are all preserved as is during the decryption process. From left to right, runs of three digits that form a key for decryption are translated into the corresponding lowercase letter.

However, there is a curious bug in my assumed encryption and decryption concept. What if the user enters a three-digit number, such as 123 in the text to be encrypted? It will be preserved as is in the encrypted code. Then if the result is entered as input for decryption, that 123 will be decrypted as a letter, namely an x.

As an example, let’s encrypt something:

Select an option
 1 to Encrypt
 2 to Decrypt
 3 to Exit

1
Enter text to encrypt:
ocelot123badger
114102104111114119123101100103106104117

Note the 123 in the input, which is included in the output.

Now, let’s decrypt that output:

Select an option
 1 to Encrypt
 2 to Decrypt
 3 to Exit

2
Enter the code:
114102104111114119123101100103106104117
ocelotxbadger

Note that, of course, the 123 has been decrypted as an x.

To eliminate this “curious bug”, you could encrypt both letters and digits. Use the revised decrypt() function from the previous post and include this in the crypt dictionary:

    "0": "200",
    "1": "201",
    "2": "202",
    "3": "203",
    "4": "204",
    "5": "205",
    "6": "206",
    "7": "207",
    "8": "208",
    "9": "209",

Here’s an example run of the program that encrypts a sentence:

Select an option
 1 to Encrypt
 2 to Decrypt
 3 to Exit

1
Enter text to encrypt:
11 squared is equal to 121.
201201 118116120100117104103 108118 104116120100111 119114 201202201.

Now, let’s decrypt the last line of that output:

Select an option
 1 to Encrypt
 2 to Decrypt
 3 to Exit

2
Enter the code:
201201 118116120100117104103 108118 104116120100111 119114 201202201.
11 squared is equal to 121.

Good idea; see the version below that uses lists. The encrypt() and decrypt() functions have been redesigned to each take an argument, and to handle one original word or encrypted word, respectively, at a time.

# dictionary
crypt = {
    "a": "100",
    "b": "101",
    "c": "102",
    "d": "103",
    "e": "104",
    "f": "105",
    "g": "106",
    "h": "107",
    "i": "108",
    "j": "109",
    "k": "110",
    "l": "111",
    "m": "112",
    "n": "113",
    "o": "114",
    "p": "115",
    "q": "116",
    "r": "117",
    "s": "118",
    "t": "119",
    "u": "120",
    "v": "121",
    "w": "122",
    "x": "123",
    "y": "124",
    "z": "125",
    "0": "200",
    "1": "201",
    "2": "202",
    "3": "203",
    "4": "204",
    "5": "205",
    "6": "206",
    "7": "207",
    "8": "208",
    "9": "209",
}
inv_crypt = {v: k for k, v in crypt.items()}

# encrypt function
def encrypt(word):
    encrypted = ''
    for char in word:
        if char in crypt:
            encrypted += crypt[char]
        else:
            encrypted += char
    return encrypted

# decrypt function
def decrypt(code):
    ind = 0
    decrypted = ''
    while ind < len(code):
        if code[ind: ind + 3] in inv_crypt:
            decrypted += inv_crypt[code[ind: ind + 3]]
            ind += 3
        else:
            decrypted += code[ind]
            ind += 1
    return decrypted

# ask user for Encrypt, Decrypt, or Exit
while True:
    print('\nSelect an option\n', '1 to Encrypt\n', '2 to Decrypt\n', '3 to Exit\n')
    option = input()

    if option == '1':
        words = input('Enter text to encrypt:\n').lower()
        words_list = [word.strip() for word in words.split(",")]
        print(", ".join([encrypt(word) for word in words_list]))
    elif option == '2':
        codes = input('Enter the code:\n')
        codes_list = [code.strip() for code in codes.split(",")]
        print(", ".join([decrypt(code) for code in codes_list]))
    elif option == '3':
        break
    else:
        print('No correct option selected\n')
1 Like

Manually entering cipher keys and values is prone to errors. Python is batteries included, computer is not a typewriter :-).

There is built-in string which provides string constants. So crypt dictionary can be created more automacigally, something along these lines:

from string import ascii_lowercase, digits

crypt = {value: str(i) for i, value in enumerate([*ascii_lowercase, *digits], start=100)}

This is of course solution for “ascii languages”. There are other languages out there which have non-ascii letters like ä, ö, õ, ü.