The Challenge

  • Name: Mod 26
  • Description: Cryptography can be easy, do you know what ROT13 is cvpbPGS{arkg_gvzr_V'yy_gel_2_ebhaqf_bs_ebg13_jdJBFOXJ}

The Solution

  • This is a simple ROT13 decode/decrypt challenge.
  1. Iterate through each character in the string, shifting the character 13 times, making sure to ignore numbers and punctuation.
  • Using python
    def decode_shift_cipher(cipher: str, shift_amount: int = 13):
    	"""Shifts the alphabet characters in the provided string by `shift_amount` places"""
    	flag = ""
    	for c in cipher:
    		if c.isalpha():
    			base = ord('A') if c.isupper() else ord('a')
    			c_shifted = (ord(c) - base + shift_amount) % 26
    			c = chr(c_shifted + base)
    		flag = f"{flag}{c}"
    	return flag
    
    
    flag = "cvpbPGS{arkg_gvzr_V'yy_gel_2_ebhaqf_bs_ebg13_jdJBFOXJ}"
    print(decode_shift_cipher(flag))
    

The Steps

  • The description of the challenge gives us the cipher type of ROT13. ROT13 is a type of substitution cipher where each letter is shifted by 13 positions. For example, "Hello!" becomes "Uryyb!". todo()image for this example.

  • Mathematically we can convert a character to the encrypted character with the formula \(E_n(x)=(x+n)\text{ mod }26\), where \(x\) is the character's position in the alphabet, and decrypted with \(D_n(x)=(x-n)\text{ mod }26\). We can translate this to python like so:

    def rot13_chr(c):
    	base = ord('A') if c.isupper() else ord('a')
    	c_shifted = (ord(c) - base + 13) % 26
    	print(chr(c_shifted + base))
    
  • But as you may notice, we're given a string of several characters! So lets use that function and modify it to translate each character in the string.

    def rot13(flag: str):
    	for c in flag:
    		base = ord('A') if c.isupper() else ord('a')
    		c_shifted = (ord(c) - base + 13) % 26
    		# end='' prevents a new line from printing after each char
    		print(chr(c_shifted + base), end='')
    
  • But we forgot one thing and that's the punctuation. Since the shift cipher won't properly work with non alphabet characters (like punctuation and symbols) in this case, we need to make sure we don't try and translate those.

    def rot13(flag: str):
    	for c in flag:
    		if not c.isalpha():
    			print(c, end='')
    			continue
    		base = ord('A') if c.isupper() else ord('a')
    		c_shifted = (ord(c) - base + 13) % 26
    		print(chr(c_shifted + base), end='')
    
  • This function will now print out the flag correctly. But I wanna change it just slightly to make it a bit more usable.

    def decode_shift_cipher(cipher: str, shift_amount: int = 13):
    	"""Shifts the alphabet characters in the provided string by `shift_amount` places"""
    	flag = ""
    	for c in cipher:
    		if c.isalpha():
    			base = ord('A') if c.isupper() else ord('a')
    			c_shifted = (ord(c) - base + shift_amount) % 26
    			c = chr(c_shifted + base)
    		flag = f"{flag}{c}"
    	return flag
    
  • First we changed the definition and added another parameter shift_amount. While we were focused on ROT13 for this exercise, the function is the same for any shift amount so why not keep it flexible.

  • The second change we made is keeping the result in a string and building the result string with f-strings which are easy to understand and performant compared to other concatenation methods.

The Script - Line by Line

  1. flag = ""
    
    • This creates an empty string for us to add characters to
  2. for c in cipher:
    	if c.isalpha():
    
    • Next, we're going to iterate through each letter in the decoded string, and ensure we only shift characters in the alphabet (A-Z/a-z).
  3. base = ord('A') if c.isupper() else ord('a')
    
    • Each character in the string is going to be converted to an ASCII ordinal, since uppercase and lowercase letters have different ASCII ordinals, we're going to get the correct base.
    • c.isupper() - Checks if the character c is uppercase.
      • If c is uppercase, ord('A') (65) is saved to base
      • If c is lowercase, ord('a') (97) is saved to base
  4. c_shifted = (ord(c) - base + shift_amount) % 26
    
    • We're going to convert the character to its ordinal and subtract the base so that the number representing the character is its position in the alphabet starting at 0 (A:0, B:1, ..., Z:25) Then we add the shift amount.
    • The % 26 makes sure shifts wrap around. For example Z is 25. Z + 1 is 26. 26 % 26 = 0 which is now back to A.
    • Let's show an example of this:
      • If c is the letter 'Q' and shift_amount is 13:
      1. base will be equal to ord('A') which is 65.
      2. ord(c) which is Q is equal to 81
      3. So ord(c)-base or 81-65 will be equal to 16
      4. We add the shift amount, 13, to and add that to the previous result to get 29, which is modulo'd with 26, c_shifted = 29 % 26 = 3. c_shifted = 3, which represents D in the alphabet.
  5. c = chr(c_shifted + base)
    
    • Now we add back the base to the shifted letter, converting the it back to an ASCII ordinal, and save the result.
    • Continuing our example from Step 4:
      • base=65 and c_shifted=3:
      1. We add back the base to get the ASCII ordinal 3 + 65 = 68 (the ASCII ordinal of D)
      2. And convert the ordinal back to a character with chr(68).
  6. flag = f"{flag}{c}"
    
    • We combine our string saved in flag currently with the decoded letter c, appending a new character each iteration of the loop.
  7. return flag
    
    • After we decode every letter in cipher we return the result.