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):

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

   for i in range(0, len(the_string)):
       alphabet_rotate = ALPHABET
       for j in range(0, rotations):
           alphabet_rotate = alphabet_rotate[1:-1] + alphabet_rotate[0] + 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):

    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. Commented May 8, 2019 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. Commented May 8, 2019 at 16:37

1 Answer 1


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):

    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:


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? Commented Dec 3, 2020 at 0:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.