Framework Thinking - Axon - Encrypt, Decrypt, Hash Code

Here is solutions for Encrypt, Decrypt, Hash Code.

1. Understand the problem

2. Clarify constraints, asks 4 - 5 questions including edge cases.

  1. Are all characters lowercase?
    ✅ Yes, all input characters are guaranteed to be in a–z.

  2. What if data is longer than secret?
    ✅ The secret must repeat cyclically.

  3. What if data is shorter than secret?
    ✅ Only the needed portion of the secret is used.

  4. Is padding required for hashing when block size does not divide the data?
    ✅ No padding is required for the base version.

  5. Should decrypt(encrypt(data)) always return the original input?
    ✅ Yes, this is a fundamental correctness requirement.

  6. Recognizing that the secret being length 1 is a Caesar Cipher is a cool bit of knowledge indication

  7. Ask about whitespace, capital letters, etc

3. Explore examples.

  1. Example 1 — Basic Encryption
secret = "abc"
data   = "abc"

a + a = a (0 + 0 = 0)
b + b = d (1 + 1 = 2)
c + c = e (2 + 2 = 4)

Result = "ade"

  1. Example 2 — Wrapping
secret = "z"
data   = "z"

z = 25, z = 25
25 + 25 = 50  50 % 26 = 24 = y

Result = "y"
  1. Example 3 — Multiple Blocks
secret = "abc"
data   = "abcdef"

Block 1: abc + abc  ade
Block 2: def + abc  d+a=e, e+b=g, f+c=h  egh

Result = "adeegh"

  1. Example 4 — Hash
secret = "abc"
blocks = ["def", "ghi"]

Step 1: abc + def = d+e+f  "dfh"
Step 2: dfh + ghi  d+g=j, f+h=n, h+i=p  "jnp"

Final hash = "jnp"

4. Brainstorm 2 - 3 solutions, naive solution first and optimize later. Explain the key idea of each solution.

class Fuzzcryption:
    ALPHABET_SIZE = 26
    BASE = ord('a')

    def __init__(self, secret: str):
        self.secret = secret
        self.block_size = len(secret)

    # ---------- Core Utilities ----------

    def _char_to_int(self, c: str) -> int:
        # a -> 1, b -> 2, ..., z -> 26
        return ord(c) - self.BASE + 1

    def _int_to_char(self, n: int) -> str:
        # 1 -> a, 2 -> b, ..., 26 -> z
        return chr(n - 1 + self.BASE)

    def _bound(self, n: int) -> int:
        # Wrap into 1..26
        n = n % self.ALPHABET_SIZE
        return 26 if n == 0 else n

    def _add_chars(self, c1: str, c2: str) -> str:
        v1 = self._char_to_int(c1)
        v2 = self._char_to_int(c2)
        return self._int_to_char(self._bound(v1 + v2))

    def _sub_chars(self, c1: str, c2: str) -> str:
        v1 = self._char_to_int(c1)
        v2 = self._char_to_int(c2)
        return self._int_to_char(self._bound(v1 - v2))

    # ---------- Encrypt / Decrypt ----------

    def encrypt(self, data: str) -> str:
        result = []

        for i, ch in enumerate(data):
            secret_ch = self.secret[i % self.block_size]
            result.append(self._add_chars(ch, secret_ch))

        return "".join(result)

    def decrypt(self, data: str) -> str:
        result = []

        for i, ch in enumerate(data):
            secret_ch = self.secret[i % self.block_size]
            result.append(self._sub_chars(ch, secret_ch))

        return "".join(result)

    # ---------- Hash ----------

    def hash(self, data: str) -> str:
        acc = self.secret

        for i in range(0, len(data), self.block_size):
            block = data[i:i + self.block_size]
            acc = self._add_strings(acc, block)

        return acc

    def _add_strings(self, s1: str, s2: str) -> str:
        result = []

        for c1, c2 in zip(s1, s2):
            result.append(self._add_chars(c1, c2))

        return "".join(result)


def test_base_case():
   """ Easy to ensure a single letter gets encrypted properly """
   fc = Fuzzcryption('a')
   assert(fc.encrypt('a') == 'b')
   assert(fc.encrypt('b') == 'c')
   assert(fc.encrypt('c') == 'd')

def test_ceasarcipher():
   """ Using only a single letter cipher """
   data = 'abcdefghijklmnopqrstuvwxyz'
   fc = Fuzzcryption('f')
   assert(fc.decrypt(fc.encrypt(data)) == data)

def test_identity():
   """ Weird identity property of the algorithm! """
   data = 'zzzzzzzz'
   fc = Fuzzcryption('z')
   assert(fc.encrypt(data) == data)

def test_random():
   """ Python has some good builtins for testing this problem, like random and string """
   import random
   import string
   data = ''.join(random.choice(string.ascii_lowercase) for x in range(20))
   secret = ''.join(random.choice(string.ascii_lowercase) for x in range(20))
   fc = Fuzzcryption(secret)
   assert(fc.decrypt(fc.encrypt(data)) == data)


if __name__ == "__main__":
   test_base_case()
   test_ceasarcipher()
   test_identity()
   [test_random() for i in range(4)]


  • Time: O(N).

  • Space: O(1) extra space.

5. Implement solutions.

6. Dry run testcases.

  1. Is this algorithm secure for encryption? Why or Why Not?
  • It is not secure because it contains an Identity Property and is Easily Reversible with a very limited state space
  1. What is the optimal time complexity of this cipher? Space complexity?
  • Linear Time complexity ‘O(n)‘ and Constant Space complexity ‘O(1)‘ - We should only need to traverse the data one time end to end
  1. If you only use a single letter in the secret, what existing cipher does this become?
  • Caesar Cipher!
  1. Is there something you could change or add to the algorithm to make it secure?
  • This is open ended, Im not sure if you could or not, but its a fun discussion!
December 7, 2025