Python Forum
I wrote a cryptographic algorithm!
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
I wrote a cryptographic algorithm!
#1
I am a hobbyist programmer with an occasional fascination with cryptography. I'm in no way a cryptographer, and my algorithm hasn't been vetted or peer-reviewed in any way but I do hope somebody will find it interesting.

It's a symmetric algorithm that uses a key with 2**n number of bytes. On the surface, it seems secure with a 2**(n*8) key-space, but I can't be entirely sure what the period of the key hashing algorithm is. As far as I know, it is only vulnerable to frequency analysis in the case of very large messages, but I could be mistaken. Maybe somebody will find a way to defeat this encryption tomorrow.

The code:
import secrets

# Some textual user interface constants to avoid littering my main function with
# magic numbers.
TUI_KEYGEN:  str = "k"
TUI_ENCRYPT: str = "e"
TUI_DECRYPT: str = "d"
TUI_QUIT:    str = "q"
TUI_HELP:    str = "h"
TUI_PROMPT:  str = ":::: "
TUI_MODE:    str = "m"
TUI_MENU:    str = f"Please select your operation.\n\
Enter {TUI_KEYGEN} to generate a key.\n\
Enter {TUI_ENCRYPT} to encrypt a plaintext string.\n\
Enter {TUI_DECRYPT} to decrypt a cyphertext string.\n\
Enter {TUI_HELP} to view this menu again.\n\
Enter {TUI_MODE} to toggle between file and console input.\n\
Enter {TUI_QUIT} to quit the application.\n"

# A little code reused from a previous project. It's a fixed-width integer
# whose bits can be indexed individually or in slices.
class Register:
    def __init__ (self, bitwidth: int, initValue: int = 0) -> None:
        self.bitwidth: int = bitwidth
        self.mask: int = 2 ** bitwidth - 1
        self._value: int = initValue & self.mask
    
    def __getitem__ (self, idx: int) -> int:
        
        if idx >= self.bitwidth:
            raise IndexError(f"The index {idx} extends beyond the width of this {self.bitwidth}-bit register.")

        return (self._value & (1 << idx)) >> idx
    
    def __setitem__ (self, idx: int, v: int) -> None:
        
        if v & 1 != v:
            raise ValueError(f"A single bit cannot be set to {v}. It is too large.")

        if idx >= self.bitwidth:
            raise IndexError(f"The index {idx} extends beyond the width of this {self.bitwidth}-bit register.")

        self._value = self._value & ~(1 << idx) # Zero The bit to be changed.
        self._value = self._value | v << idx    # Move the value to the correct bit position and put it in self.value
    
    @property
    def value (self):
        return self._value
    
    @value.setter
    def value (self, v: int):
        self._value = v & self.mask
    
    # This is a temporary workaround. I can't remember what dunder methods
    # are used to implement slicing so Imma just hack in the functionality
    # I need and refactor later.
    def getMultiple (self, start: int, stop: int) -> int:
        if start > self.bitwidth or start < 0:
            raise IndexError(f"The index {start} extends beyond the width of this {self.bitwidth}-bit register.")
        
        if stop > self.bitwidth or stop < 0:
            raise IndexError(f"The index {stop} extends beyond the width of this {self.bitwidth}-bit register.")
        
        bitMask: int = ((2 ** (stop - start)) - 1) * (2**start)

        return (self._value & bitMask) // 2 ** start
    
    def __repr__ (self) -> str:
        return f"Register({self.bitwidth}, {hex(self._value)})"
    
    def getByte (self, idx: int) -> int:
        lsb: int = idx * 8
        msb: int = lsb + 8
        bitMask: int = ((2 ** (msb - lsb)) - 1) * (2**lsb)
        return (self._value & bitMask) // 2 ** lsb
    
    def setByte (self, idx: int, v: int) -> None:
        lsb: int = idx * 8
        msb: int = lsb + 8
        bitMask: int = ((2 ** (msb - lsb)) - 1) * (2**lsb)
        self._value &= ~bitMask
        self._value |= v * lsb

#####   Globals    #####
globalKey: Register = None
isConsoleMode: bool = True

def advanceKey (key: Register):
    key.value += 1
    byteLen: int = key.bitwidth // 8
    idxSize: int = byteLen - 1

    idx1: int    = key.getMultiple(0, idxSize)
    idx2: int    = key.getMultiple(byteLen, byteLen + idxSize)
    swp1: int    = key.getByte(idx1)
    swp2: int    = key.getByte(idx2)
    key.setByte(idx2, swp1)
    key.setByte(idx1, swp2)

def encryptStr (plaintext: str, key: Register) -> str:

    keyLen: int = key.bitwidth // 8
    ciphertext: str = ""
    for i in plaintext:
        b = ord(i)
        for j in range(0, keyLen):
            displacement: int = key.getByte(j)
            b -= displacement ^ j
            b %= 256

        # I want zero padding to 1 byte in the hex string and I don't
        # want the 0x, so I don't use the standard hex function.
        ciphertext += "0123456789ABCDEF"[(b & 0xF0) >> 4]
        ciphertext += "0123456789ABCDEF"[b & 0x0F]
        advanceKey(key)

    return ciphertext

def decryptStr (ciphertext: str, key: Register):
    keyLen: int = key.bitwidth // 8
    plaintext: str = ""
    for i in range(0, len(ciphertext), 2):
        b = eval("0x" + ciphertext[i: i + 2])
        for j in range(0, keyLen):
            displacement: int = key.getByte(j)
            b += displacement ^ j
            b %= 256
        
        plaintext += chr(b)
        advanceKey(key)
    
    return plaintext

def doUsrDrivenKeygen () -> bool:
    global globalKey
    try:
        SkeySize: str = input(f"Please enter the size of the key in bytes.\n\
(Must be a power of 2; i.e. 1, 2, 4, 8...)\n{TUI_PROMPT}")
        keySize: int = int(SkeySize)

        # Check whether the number of bytes is a power of 2.
        clone: int = keySize
        mask:int = 1
        if clone == 0:
            print("The key size must be non-zero!")
            return False
        
        while clone != 0:

            if mask & clone and ~mask & clone:
                print("The key size specified is not 2**n bytes!")
                return False
            
            clone &= ~mask

            mask <<= 1

    except ValueError:
        print(f"Input {SkeySize} not convertable to an integer.")
        return False
    
    dtaSrc: str = input(f"Please enter a random number or 'p' for a pseudorandom number.\n{TUI_PROMPT}").lower()

    key = Register(keySize * 8)
    if dtaSrc == 'p':
        key.value = secrets.randbits(keySize * 8)
    else:
        try:
            key.value = eval(dtaSrc)
        except SyntaxError:
            print(f"Input {dtaSrc} not convertable to an integer.")
            return False
    
    globalKey = key
    print(f"Using key {hex(globalKey.value)} for all cryptographic operations until key is reassigned.")

    return False

def doUsrDrivenEncrypt () -> bool: # Next TODO

    global globalKey
    if globalKey is None:
        doUsrDrivenKeygen()

    plaintext: str = ""
    if isConsoleMode:
        plaintext = input(f"Please enter the text you want to encrypt.\n{TUI_PROMPT}")
    else:
        filePath: str = input(f"Please enter the path to the file you want to encrypt.\n{TUI_PROMPT}")
        with open(filePath, "r") as f:
            plaintext = f.read()
    
    ciphertext: str = encryptStr(plaintext, globalKey)

    if isConsoleMode:
        print(ciphertext)
    else:
        filePath: str = input(f"Please enter the path of the file to which you want to write the ciphertext.\n{TUI_PROMPT}")
        with open(filePath, "w") as f:
            f.write(ciphertext)

    return False

def doUsrDrivenDecrypt () -> bool:
    global globalKey
    if globalKey is None:
        doUsrDrivenKeygen()

    ciphertext: str = ""
    if isConsoleMode:
        ciphertext = input(f"Please enter the text you want to decrypt.\n{TUI_PROMPT}")
    else:
        filePath: str = input(f"Please enter the path to the file you want to decrypt.\n{TUI_PROMPT}")
        with open(filePath, "r") as f:
            ciphertext = f.read()
    
    plaintext: str = decryptStr(ciphertext, globalKey)

    if isConsoleMode:
        print(plaintext)
    else:
        filePath: str = input(f"Please enter the path of the file to which you want to write the ciphertext.\n{TUI_PROMPT}")
        with open(filePath, "w") as f:
            f.write(plaintext)

    return False

def confirmQuit () -> bool:
    confirmation: bool = input(f"Are you sure you want to quit? (y/n)\n{TUI_PROMPT}").lower() != "n"
    return confirmation

def NOP () -> bool: return False

def toggleMode () -> bool:
    global isConsoleMode
    isConsoleMode = not isConsoleMode
    if isConsoleMode:
        print("Console Mode. User will be prompted to enter plaintext/ciphertext to the console.")
    else:
        print("File Mode. User will be prompted for paths to plaintext and ciphertext files.")

def main () -> None:
    print(TUI_MENU)
    usrCommand: str = "NOP"
    
    while True:

        usrWantsToQuit: bool = False
        # Synthetic switch statement
        try:
            usrWantsToQuit = {
                TUI_KEYGEN:  doUsrDrivenKeygen,
                TUI_ENCRYPT: doUsrDrivenEncrypt,
                TUI_DECRYPT: doUsrDrivenDecrypt,
                TUI_HELP:    lambda: print(TUI_MENU),
                TUI_QUIT:    confirmQuit,
                TUI_MODE:    toggleMode,
                "NOP":       NOP
            } [usrCommand] ()
        except KeyError:
            print(f"The input {usrCommand} is not a valid command.")
        
        if usrWantsToQuit: break
        usrCommand = input(TUI_PROMPT)

if __name__ == '__main__':
    main()
I hope my code isn't too messy. I look forward to seeing what you all think of it...
Reply


Messages In This Thread
I wrote a cryptographic algorithm! - by keames - May-09-2021, 07:03 PM

Possibly Related Threads…
Thread Author Replies Views Last Post
  I wrote an emulator! keames 8 5,009 Nov-13-2019, 10:13 PM
Last Post: keames
  four commands i wrote the other day Skaperen 4 2,984 Jun-20-2019, 05:47 AM
Last Post: Skaperen

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020