0

I have a program in python that takes two strings. One is the plain text string, another is the cipher key. what it does is go over each of the characters and xors the bits with the cipher characters. But when going back and forth a few of the letter do not seem to change properly. Here is the code:

//turns int into bin string length 8
def bitString(n):
    bin_string = bin(n)[2:]
    bin_string = ("0" * (8 - len(bin_string))) + bin_string
    return bin_string

//xors the bits
def bitXOR(b0, b1):
    nb = ""
    for x in range(min(len(b0), len(b1))):
        nb += "0" if b0[x] == b1[x] else "1"
    return nb

//takes 2 chars, turns them into bin strings, xors them, then returns the new char
def cypherChar(c0, c1):
    return chr(int(bitXOR(bitString(ord(c0)), bitString(ord(c1))), 2))

//takes s0 (the plaintext) and encrypts it using the cipher key (s1)
def cypherString(s0, s1):
    ns = ""
    for x in range(len(s0)):
        ns += cypherChar(s0[x], s1[x%len(s1)])
    return ns

For example sometimes in a long string the word 'test' will cipher back into 'eest', and stuff like that

I have checked over the code a dozen times and I can't figure out whats causing some of the characters to change. Is it possible some characters just behave strangely?

EDIT:

example:

This is a test

Due to the fact that in the last test

Some symbols: !@#$%^&*()

were not changed properly

I am retesting

END

using the cipher key : 'cypher key'

translates back to :

This is a test

Due to toe aact that in the last sest

Some symbols: !@#$%^&*()

were not changed properly

I am retestiig

END
  • Give a sample string for which the program fails. – thefourtheye Mar 17 '15 at 00:51
  • I think it may be something about the newline thing '\n' causing weird behavior. Can anyone confirm. – ThatOneGuyInXNA Mar 17 '15 at 00:52
  • Haven't checked the code but newlines can be represented with either `\r\n`, `\n`, or `\r` depending on platform convention. Be careful that you're working on the correct string type, either bytes string or unicode string. – Lie Ryan Mar 17 '15 at 01:03
  • Which version of Python do you use? You should also be careful about your text's encoding. Python 2 and 3 do not use the same handling for strings. Python's 3 str are 2 bytes wide (UTF-16). – Cilyan Mar 17 '15 at 01:09
  • Another thing going back and forth repeatedly does not cause more and more error. the mistakes like 'sest' stay the same. this makes me think that some combinations just do not work well but once they are weeded out it returns to a balance. I am using python 3.4 btw – ThatOneGuyInXNA Mar 17 '15 at 01:13
  • For what it's worth, I ran this on python 2 and your test case seems to work just fine. Also, suggest fixing the //'s in your code above (python uses #'s for comments and it meant I had to copy each function separately to skip the invalid //'s) – Foon Mar 17 '15 at 01:43
  • Also, I'd suggest modifying cypherChar to print out the two characters it's xor'ing, what the result is, and what is the result if you then xor the result with the key (second value) (which should be the first value) – Foon Mar 17 '15 at 01:50
  • @Cilyan: to be pedantic, that's incorrect. Python 3's str are conceptually Unicode characters without any encodings, not UTF-16. If the string needs to be encoded in a certain way for storage in RAM, that encoding is an implementation detail of that particular python implementation. – Lie Ryan Mar 17 '15 at 11:37
  • @ThatOneGuyInXNA does any of the solutions solve your problem? Does any of them shed some light? – matiasg Mar 19 '15 at 15:45

3 Answers3

0

Sorry it its a little messy, I put it together real quick

from binascii import hexlify, unhexlify
from sys import version_info

def bit_string(string):
    if version_info >= (3, 0):
        return bin(int.from_bytes(string.encode(), 'big'))
    else:
        return bin(int(hexlify(string), 16))

def bitXOR_encrypt(plain_text, key):
    encrypted_list = []
    for j in range(2, len(plain_text)):
        encrypted_list.append(int(plain_text[j]) ^ int(key[j])) #Assume the key and string are the same length
    return encrypted_list

def decrypt(cipher_text, key):
    decrypted_list = []
    for j in range(2, len(cipher_text)): #or xrange
        decrypted_list.append(int(cipher_text[j]) ^ int(key[j])) #Again assumes key is the same length as the string

    decrypted_list = [str(i) for i in decrypted_list]
    add_binary = "0b" + "".join(decrypted_list)
    decrypted_string = int(add_binary, 2)
    if version_info >= (3, 0):
        message =  decrypted_string.to_bytes((decrypted_string.bit_length() + 7) // 8, 'big').decode()
    else:
        message = unhexlify('%x' % decrypted_string)
    return message


def main():
    plain_text = "Hello"
    plain_text_to_bits = bit_string(plain_text)
    key_to_bits = bit_string("candy")

    #Encrypt
    cipher_text = bitXOR_encrypt(plain_text_to_bits, key_to_bits)

    #make Strings
    cipher_text_string = "".join([str(i) for i in cipher_text])
    key_string = "".join([str(i) for i in key_to_bits])

    #Decrypt
    decrypted_string = decrypt("0B"+cipher_text_string, key_string)

    print("plain text: %s" % plain_text)
    print("plain text to bits: % s" % plain_text_to_bits)
    print("key string in bits: %s" % key_string)
    print("Ciphered Message: %s" %cipher_text_string)
    print("Decrypted String: %s" % decrypted_string)

main()

for more details or example code you can visit my repository either on github https://github.com/marcsantiago/one_time_pad_encryption

Also, I know that in this example the key is the same length as the string. If you want to use a string that is smaller than the string try wrapping it like in a vigenere cipher (http://en.wikipedia.org/wiki/Vigenère_cipher)

reticentroot
  • 3,612
  • 2
  • 22
  • 39
  • I'm using the from sys import version_info to check for python 2 or 3. If python 3...etc or python 2 etc... because the code is slightly different in each version. – reticentroot Mar 17 '15 at 01:34
  • Also, I start the range call at 2 to make remove '0b' that is attached to the beginning of each string created using bin(int.from_bytes(string.encode(), 'big')) and bin(int(hexlify(string), 16)). You just have to remember to add it back the the key and cipher string to decrypt otherwise you'll get a decode error. – reticentroot Mar 17 '15 at 01:40
0

I think you are overcomplicating things:

def charxor(s1, s2):
    return chr(ord(s1) ^ ord(s2))

def wordxor(w1, w2):
    return ''.join(charxor(w1[i], w2[i]) for i in range(min(len(w1), len(w2))))

word = 'test'
key = 'what'
cyphered = wordxor(word, key)
uncyphered = wordxor(cyphered, key)

print(repr(cyphered))
print(uncyphered)

You get

'\x03\r\x12\x00'
test

There is a fairly good explanation of Python's bit arithmetic in How do you get the logical xor of two variables in Python?

Community
  • 1
  • 1
matiasg
  • 1,927
  • 2
  • 24
  • 37
0

I could find nothing wrong with the results of your functions when testing with your input data and key. To demonstrate, you could try this test code which should not fail:

import random

def random_string(n):
    return ''.join(chr(random.getrandbits(8)) for _ in range(n))

for i in range(1000):
    plaintext = random_string(500)
    key = random_string(random.randrange(1,100))
    ciphertext = cypherString(plaintext, key)
    assert cypherString(ciphertext, key) == plaintext

If you can provide a definitive sample of plain text, key, and cipher text that fails, I can look further.

mhawke
  • 84,695
  • 9
  • 117
  • 138