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
#2
in cryptography it is very unlikely your algorithm or code will even be looked at by the cryptography experts. don't expect to go anywhere. this is not like creating a new compression algorithm where specific goal parameters can be met and proven. "better" cryptography is basically unprovable. a very critical scientific analysis needs to be done to get anyone to look at it.

but it is fun to do. BTDT. great exercise for the mind.

there are many other areas to think on, too, such as video encoding.
keames likes this post
Tradition is peer pressure from dead people

What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
Reply
#3
*oops*
Tradition is peer pressure from dead people

What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
Reply
#4
Skaperen Wrote:it is very unlikely your algorithm or code will even be looked at by the cryptography experts.
There is a way, use the algorithm for very sensible data, critical data. Then it will be looked at carefuly by cryptography experts. They may not publish their conclusions however.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  I wrote an emulator! keames 8 4,860 Nov-13-2019, 10:13 PM
Last Post: keames
  four commands i wrote the other day Skaperen 4 2,909 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