# String Encoding and Decoding Method

so I came across the following string encoding method in python.

``````def encode(the_string):
encoded_string = ''
rotations = ord(the_string[-1]) + 5

for i in range(0, len(the_string)):
value = ord(the_string[i])

for j in range(0, rotations):
value -= 1
if value < 65:
value = 25 + value

encoded_string += chr(value)
rotations += ord(the_string[i])-2
return encoded_string
``````

It looks like a Caesar Cipher where ASCII value of the letter determines the number of shifts of the alphabet, however 'Z' stays at the end. The initial number of rotations is the ASCII value of the last letter of the string plus 5.

``````def encode(the_string):
ALPHABET='ABCDEFGHIJKLMNOPQRSTUVWXYZ'

encoded_string = ''
rotations = (ord(the_string[-1]) + 5) % 25

for i in range(0, len(the_string)):
alphabet_rotate = ALPHABET
print('Rots:',rotations)
for j in range(0, rotations):
alphabet_rotate = alphabet_rotate[1:-1] + alphabet_rotate + alphabet_rotate[-1]

encoded_string += ALPHABET[alphabet_rotate.index(the_string[i])]
rotations += (ord(the_string[i]) - 2)
rotations %= 25

return encoded_string
``````

I am trying to write a decoding method however I am struggling to find the the correct offsets for the rotations. I am reversing the direction of the shifts. Also the lookup of the decoded letter works in reverse. Is this the correct approach? Any help will be appreciated.

``````def decode(the_string):
ALPHABET='ABCDEFGHIJKLMNOPQRSTUVWXYZ'

decoded_string = ''
rotations = (ord(the_string[-1]) + 3) % 25 # <== 1. PROBLEM HERE

for i in range(0, len(the_string)):
alphabet_rotate = ALPHABET

for j in range(0, rotations):
alphabet_rotate = alphabet_rotate[-2] + alphabet_rotate[0:-2] + alphabet_rotate[-1]

decoded_string += alphabet_rotate[ALPHABET.index(the_string[i])]

rotations += (ord(the_string[i]) +20 ) # <== 2. PROBLEM HERE
rotations %= 25

return decoded_string
``````
• This is not the right subreddit for the question, crypto.stackexchange.com would be better suited. However I had a short look at it and I don't think there's a way to decode the string directly. The first and all subsequent rotations depend on the last plaintext char. We do not know that char and it isn't part of the encoded string. That only leaves bruteforce until one decoding makes sense, if I didn't miss an obvious hint. May 8 '19 at 16:33
• (2nd comment due to space) Actually there is a correct decoding. You try all initial rotations until you have a plaintext for which the last character and the initial rotation + 5 match, but you have to try all offsets until you find the right one, so still bruteforce. May 8 '19 at 16:37

Some observations first:

• The initial rotations is based on a plaintext char we do not know, and so are all further rotations. Therefore, I don't think we can decode a given string directly.

• Plaintexts with characters outside A-Z can be encoded but I'm not sure we can decode them properly. For my analysis I assumed A-Z inputs only.

• There seems to be some oddness around rotations because effectively it does `idx % 25` instead of 26. If the input character is below 65 (='A'), it will be rotated to 'Y', not 'Z'.

That said, the following decodes a given encoded string and a given initial rotation which is not known:

``````def decode(string,initial):
alph = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

rot = initial

decoded = ""

for i in range(len(string)):
c = string[i]
plain = alph[(alph.index(c)+rot) % (len(alph)-1)]
decoded += plain

rot += ord(plain)-2
``````

If we add a line to the encode function to spit out the initial rotation we can test this:

``````print(decode(encode("HELLO"),84))
``````

prints `HELLO` correctly given the initial rotation of 84 as printed by the modified `encode` function.

Now we have one clue to verify if we got the proper decoding. The first rotation and the last plaintext character are in relation:

``````first rot = ord(last plaintext char) + 5
``````

So we can test all initial rotations and the verify the above.

However, the equality is under modulo 25. For example the 'HELLO' line can be properly decoded with the initial rotation 84 - but also with 9 (which you get by doing 84 % 25).

If we write a function to find a proper decoding like this:

``````def find_decoding(string):
for i in range(26):
decoded = decode(string,i)
if ((ord(decoded[-1])+5) % 25) == i:
print("Key %d valid: %s" % (i,decode(string,i)))
``````

we can then find initial rotations for which the mentioned relationship holds. Interestingly 'HELLO' seems to be a rare input with multiple possible keys in the range 0-25:

``````Key 4 valid: CTQVJ
Key 9 valid: HELLO
Key 14 valid: MOGBT
Key 19 valid: RYBQY
Key 24 valid: WJVGE
``````

but any other string I tried randomly only had one valid initial rotation.

• Has anyone added a n-gram model to this to select the right rotation? Dec 3 '20 at 0:07