3
\$\begingroup\$

I'm rather fascinated by radio 'number stations', and in preparing for a D&D game, I wanted to use such a method to encrypt complex messages as seemingly random strings of words. I wanted to add obscurity by having every character alternate between 2 different words.

After making the one time pad used for encryption, I started encrypting the messages manually. 7 characters in and I knew I had already made at least one mistake.

The solution, make a Python program to encode it for me. Mostly as a fun programming challenge.

The resulting Python program doesn't import anything. It's just a giant matrix with the cipher substitutions, the corresponding character to replace, and a helper number to alternate.

matrix = [["A", "treatment", "snails", 1],  # massive 2d array. 
          ["B", "two", "texture", 1],  # Saner people would just do lower caps.
          ["C", "induce", "knotty", 1],
          ["D", "lowly", "unused", 1],
          ["E", "income", "grip", 1],
          ["F", "talented", "squeak", 1],
          ["G", "intelligent", "dependent", 1],
          ["H", "nest", "border", 1],
          ["I", "excuse", "delay", 1],
          ["J", "sudden", "gigantic", 1],
          ["K", "stew", "tooth", 1],
          ["L", "quack", "hole", 1],
          ["M", "eggs", "married", 1],
          ["N", "vessel", "punch", 1],
          ["O", "embarrass", "scatter", 1],
          ["P", "lace", "draconian", 1],
          ["Q", "lopsided", "attraction", 1],
          ["R", "blade", "average", 1],
          ["S", "workable", "way", 1],
          ["T", "harmony", "waste", 1],
          ["U", "thick", "warlike", 1],
          ["V", "island", "drop", 1],
          ["W", "rail", "null", 1],
          ["X", "misty", "earth", 1],
          ["Y", "camp", "clip", 1],
          ["Z", "fax", "stop", 1],
          ["a", "crooked", "left", 1],
          ["b", "thick", "cook", 1],
          ["c", "sense", "top", 1],
          ["d", "auspicious", "title", 1],
          ["e", "few", "error", 1],
          ["f", "longing", "fearless", 1],
          ["g", "scarce", "bone", 1],
          ["h", "eyes", "hushed", 1],
          ["i", "doctor", "save", 1],
          ["j", "puncture", "dashing", 1],
          ["k", "snotty", "foregoing", 1],
          ["l", "callous", "dolls", 1],
          ["m", "town", "wind", 1],
          ["n", "thirsty", "fear", 1],
          ["o", "thoughtful", "actually", 1],
          ["p", "ticket", "rings", 1],
          ["q", "view", "ancient", 1],
          ["r", "different", "hot", 1],
          ["s", "abrasive", "bag", 1],
          ["t", "excellent", "care", 1],
          ["u", "testy", "curious", 1],
          ["v", "complex", "reflective", 1],
          ["w", "smash", "kick", 1],
          ["x", "office", "helpful", 1],
          ["y", "noiseless", "gainful", 1],
          ["z", "minister", "false", 1],
          [" ", "linen", "power", 1],
          ["0", "subtract", "pathetic", 1],
          ["1", "employ", "bumpy", 1],
          ["2", "substance", "tow", 1],
          ["3", "towering", "probable", 1],
          ["4", "lake", "eye", 1],
          ["5", "moon", "yielding", 1],
          ["6", "crazy", "representative", 1],
          ["7", "actor", "rice", 1],
          ["8", "unwieldy", "manage", 1],
          ["9", "wing", "cowardly", 1],
          [".", "driving", "simplistic", 1],
          [",", "comb", "one", 1],
          ["?", "provide", "part", 1]]

text = "What a lovely night to post on Code Review. I'd be interested in having this as a program that asks for input " \
       "text. Maybe as an .exe file, so I wont have to run it from pycharm. "

result = ""
n = 0  #thought I'd also like to know how long the string was/how many words it generated.

for char in text:  # repeat all the stuff for each character in the original text.
    row = 0
    n += 1

    for row in range(66):  # for every character, go down through array until matching char found.
        if matrix[row][0] == char: 
            if matrix[row][3] % 2 == 0:
                alternate = 2
            else:
                alternate = 1
            matrix[row][3] += 1# the number at the end of each row is a counter for 
            # how many times the character has already been used.
            result += matrix[row][alternate] + " - "  # concatenate everything.
            # print(matrix[row][alternate])  # neatly list everything on single lines. Not needed. Currently off.
length = len(result)
result = result[:length - 3]
result += "." #removes the, now no longer needed ' - ' from the end of the string and add a'.'. Also not needed.
print(n) #List the length of the text/the number of words.
print(result)

I wonder how quickly I'd get a visit from law enforcement if I got a ham radio and started using this to broadcast messages.

Side note. In the D&D game, someone has gotten lazy and is reusing the same one time pad. A new one for every encrypted clue players found would be impossible to decipher and the prep would take ages. However I wanted the program for consistency. I case they do want to try and decipher it.

The output would be rather ominous to have read out loud over the radio, with seemingly random intervals.

Output with my example string, if anyone is curious;

178
rail - eyes - crooked - excellent - linen - left - power - callous - thoughtful - complex - few - dolls - noiseless - linen - thirsty - doctor - scarce - hushed - care - power - excellent - actually - linen - ticket - thoughtful - abrasive - care - power - actually - fear - linen - induce - thoughtful - auspicious - error - power - blade - few - reflective - save - error - smash - driving - linen - excuse - title - power - thick - few - linen - doctor - thirsty - excellent - error - different - few - bag - care - error - auspicious - power - save - fear - linen - eyes - crooked - complex - doctor - thirsty - bone - power - excellent - hushed - save - abrasive - linen - left - bag - power - crooked - linen - rings - hot - actually - scarce - different - left - town - power - care - eyes - crooked - excellent - linen - left - abrasive - snotty - bag - power - longing - thoughtful - hot - linen - doctor - fear - ticket - testy - care - power - excellent - few - office - care - simplistic - linen - eggs - crooked - gainful - cook - error - power - left - abrasive - linen - crooked - thirsty - power - driving - few - helpful - error - linen - fearless - save - callous - few - comb - power - bag - actually - linen - delay - power - kick - thoughtful - fear - excellent - linen - hushed - left - reflective - error - power - care - actually - linen - different - curious - thirsty - power - doctor - excellent - linen - longing - hot - thoughtful - wind - power - rings - noiseless - sense - eyes - crooked - different - town - simplistic - linen.
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Don't comingle constants and variables. Your matrix is properly understood as a constant: it contains the data to convert any character to a word. But you also treat it as a variable by gluing tallying integers to the end of each inner list. Modifying things that should be constants is a bad idea. Ditch the integers and also give the constant a proper uppercase name:

MATRIX = [
    ['A', 'treatment', 'snails'],
    ['B', 'two', 'texture'],
    ...
]

Your program has too much algorithm, not enough data. It's frequently the case that smarter data makes for simpler algorithms. Your MATRIX has algorithmic assumptions embedded inside of it: for example, if we encounter the character A, we should emit either treatment or snails, depending on whether A has already appeared an even or odd number of times. There's no need for that complexity. Instead, you can assemble exactly the data structure you need -- namely a dict mapping (CHARACTER, FLAG) tuples to words, where FLAG is zero or one.

def build_lookup(matrix):
    return {
        (char, i) : w
        for char, *words in matrix
        for i, w in enumerate(words)
    }

Don't comingle algorithm and presentation. The encryption algorithm converts a sequence of characters to a sequence of words. Keep the encryption process focused tightly on that. At the end of the process you might want to glue those words together into a hyphen-delimited string for printing purposes -- but that's a different concern and it's a bad idea to muddy up the encryption code with presentation-oriented code.

If you want to count things, consider using a Counter. It's easy and it will help you avoid the temptation, noted above, to abuse a constant for tallying purposes.

Python has a built-in way to assemble delimited strings. It's a lot simpler to use than assembling a delimited string inside a loop and then taking awkward steps after the loop to remove the trailing delimiter.

Python sequences know their length. As a result, your n variable serves no purpose.

Start putting your code in functions. It's a time-tested idea: embrace it and you'll be standing on the shoulders of giants.

from collections import Counter

MATRIX = [
    ...
]

def main():
    text = 'AABBCCDDA'
    lookup = build_lookup(MATRIX)
    encrypted = encrypt(text, lookup)
    encrypted_message = ' - '.join(encrypted)
    print(encrypted_message)

def build_lookup(matrix):
    ...

def encrypt(text, lookup):
    tally = Counter()
    encrypted = []
    for char in text:
        e = lookup[char, tally[char] % 2]
        encrypted.append(e)
        tally[char] += 1
    return encrypted

if __name__ == '__main__':
    main()
\$\endgroup\$
1
  • \$\begingroup\$ Thanks FMc, a lot of information to unpack there. Might make a cleaner version. I agree that modifying the data in the constant matrix isn't ideal, but I understood that each character needed it own counter, or bit. When I tested adding to the tally integer, and printing it out, I wasn't expecting it to work, but it did. The only purpose the 'n' variable serves is counting the length of the message, and printing it out. Only learned len() when I wanted to remove the trailing characters. \$\endgroup\$ Commented Feb 5, 2022 at 13:26

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.