3
\$\begingroup\$

I had this idea not to long ago on creating a secure chat for people who need to be not meddled with when talking (like whistleblowers, hackers, etc) talking to one of my friends about it he said I could use the One Time Pad Cipher, so I decided to write my own in Python.

If you don't know what the one time pad is, in a nutshell it's a cipher that generates a key for you that is as long as the string you pass to it, this key makes it theoretically impossible to crack the cipher without the key itself, due to their being so many possible combinations.

My cipher uses sqlite to store a database into memory to keep the keys unique, once the program is exited, the database is destroyed (theoretically). I would like someone to poke as many holes in this as possible, I would like to see if the string is possible to be cracked and would also like someone to break the database if possible. Basically I want to know how secure this is. Please keep in mind, this is not a finished product but just a fundamental understanding to a larger project. So critique away and have fun with it, thank you!

The code:

import random
import string
import sqlite3


PUNC = string.punctuation
ALPHABET = string.ascii_letters


def initialize():
    """
    initialize the database into memory so that it can be wiped upon exit
    """
    connection = sqlite3.connect(":memory:", isolation_level=None, check_same_thread=False)
    cursor = connection.cursor()
    cursor.execute(
        "CREATE TABLE used_keys ("
        "id INTEGER PRIMARY KEY AUTOINCREMENT,"
        "key TEXT"
        ")"
    )
    return cursor


def create_key(_string, db_cursor):
    """
    create the key from a provided string
    """
    retval = ""
    set_string = ""
    used_keys = db_cursor.execute("SELECT key FROM used_keys")
    id_number = len(used_keys.fetchall()) + 1
    for c in _string:
        if c in PUNC:
            c = ""
        if c == " " or c.isspace():
            c = ""
        set_string += c
    key_length = len(set_string)
    acceptable_key_characters = string.ascii_letters
    for _ in range(key_length):
        retval += random.choice(acceptable_key_characters)
    if retval not in used_keys:
        db_cursor.execute("INSERT INTO used_keys(id, key) VALUES (?, ?)", (id_number, retval))
        return retval, set_string
    else:
        create_key(_string, db_cursor)


def encode_cipher(_string, key):
    """
    encode the string using a generated unique key
    """
    retval = ""
    for k, v in zip(_string, key):
        c_index = ALPHABET.index(k)
        key_index = ALPHABET.index(v)
        cipher_index = c_index + key_index
        try:
            retval += ALPHABET[cipher_index]
        except IndexError:
            cipher_index -= 26
            retval += ALPHABET[cipher_index]
    return retval


def decode_cipher(encoded, key):
    """
    decode the encoded string using the encoded string and the key used to cipher it
    """
    retval = ""
    for k, v in zip(encoded, key):
        c_index = ALPHABET.index(k)
        key_index = ALPHABET.index(v)
        decode = c_index - key_index
        try:
            retval += ALPHABET[decode]
        except IndexError:
            decode += 26
            retval += ALPHABET[decode]
    return retval


def main():
    """
    main messy function
    """
    exited = False
    choices = {"1": "show keys", "2": "create new key", "3": "decode a cipher", "4": "exit"}
    cursor = initialize()
    seperator = "-" * 35
    print("database initialized, what would you like to do:")
    try:
        while not exited:
            for item in sorted(choices.keys()):
                print("[{}] {}".format(item, choices[item]))
            choice = raw_input(">> ")
            if choice == "1":
                keys = cursor.execute("SELECT key FROM used_keys")
                print(seperator)
                for key in keys.fetchall():
                    print(key[0])
                print(seperator)
            elif choice == "2":
                phrase = raw_input("Enter your secret phrase: ")
                key, set_string = create_key(phrase, cursor)
                encoded = encode_cipher(set_string, key)
                print(seperator)
                print("encoded message: '{}'".format(encoded))
                print(seperator)
            elif choice == "3":
                encoded_cipher = raw_input("enter and encoded cipher: ")
                encode_key = raw_input("enter the cipher key: ")
                decoded = decode_cipher(encoded_cipher, encode_key)
                print(seperator)
                print("decoded message: '{}'".format(decoded))
                print(seperator)
            elif choice == "4":
                print("database destroyed")
                exited = True
    except KeyboardInterrupt:
        print("database has been destroyed")


if __name__ == "__main__":
    main()
\$\endgroup\$
4
+50
\$\begingroup\$

The hard problems for one-time pads are

  1. randomly generating the pad, and
  2. securely sharing the pad between the participants (and nobody else).

The first problem is not solved, because the random package is a pseudo-random generator - it can't produce enough randomness for this purpose. We'd need a true random number generator, which (in general) requires a hardware source of randomness. Deterministic arithmetic is not a good source of crypto-grade randomness.

The second problem doesn't appear to be solved - to my understanding, we store the OTP, but don't distribute it, so the only way to decode a message is by returning it to the place where it was encoded. This might work for transmission across time (i.e. storage), but it's unsuitable for transmission over distance (i.e. communication).

\$\endgroup\$
  • \$\begingroup\$ Isn't PyCrypto's random number generator acceptable to use? \$\endgroup\$ – 13aal Nov 13 '18 at 15:30
  • \$\begingroup\$ Also couldn't I theoretically just use the os.urandom call for a cryptographically secure number? \$\endgroup\$ – 13aal Nov 13 '18 at 15:31
  • 3
    \$\begingroup\$ That's a better source. In Python 3.6 onwards, consider secrets.SystemRandom. \$\endgroup\$ – Toby Speight Nov 13 '18 at 16:08
  • \$\begingroup\$ Hey, just letting you know in python 2.7 you can use random.SystemRandom() \$\endgroup\$ – 13aal Nov 14 '18 at 15:45
  • \$\begingroup\$ @TobySpeight secrets.SystemRandomuses urandom internally in the random() and getrandbits(). \$\endgroup\$ – Pedro Lobito Dec 2 '18 at 22:17
1
\$\begingroup\$

Your plaintext, ciphertext and key all just alphabets, it is not difficult(or just say easy) for bruteforce, only need 56 ** length

Why not using bytes crack difficult will raise up to 256 ** length

And you can use base64 for user friendly ciphertext/key output.

create_key

You can use os.urandom to generate random bytes as OTP key, and no need for filter input strings, as they can all be encrypted

def create_key(_string, db_cursor):
    ...
    retval = base64.b64encode(os.urandom(len(_string)))
    ...

encode_cipher

def encode_cipher(_string, key):
    key = base64.b64decode(key)
    retval = ""
    for k, v in zip(_string, key):
        retval += chr(ord(k) ^ ord(v))
    return base64.b64encode(retval)

decode_cipher

def decode_cipher(encoded, key):
    retval = ""
    encoded = base64.b64decode(encoded)
    key = base64.b64decode(key)
    for k, v in zip(encoded, key):
        retval += chr(ord(k) ^ ord(v))
    return retval
\$\endgroup\$
  • \$\begingroup\$ The base64 defeats the purpose of a one time pad. Also its not possible to get the string without the key, you can try to bruteforce it if you want to, you may or may not get the correct output try it with this one: zLFREvoTtPM. I see no proof that the original string can be bruteforced without the key itself. \$\endgroup\$ – 13aal Nov 21 '18 at 17:41
  • \$\begingroup\$ It is meaningless to crack a random message with length just 11 and encrypted by OTP. Think of such a threat mode, the hacker know 2 mins ago you send your friend and an important encryped file, and you also send him the password encrypted using OTP, then hacker just need to crack messages around 2 mins ago and try to decrypt that file to verify it. And I don't get why base64 defeat the purpose of OTP, it is just encoding nothing with encryption. \$\endgroup\$ – Aries_is_there Nov 22 '18 at 0:37
  • \$\begingroup\$ Yes OTP is an highly safe encrypt way, it is quite difficult to crack all or any random ciphertexts. I suggest using bytes instead of just alphabets just for let it be more difficult to crack. and the key isn't something like here bank use, user can only input numbers (and you limit it to alphabets here), why not let it be more choice and be more tough for hackers \$\endgroup\$ – Aries_is_there Nov 22 '18 at 0:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.